Introductionnbsp;
您可能已经听说过拓展署最近有多大等,但大多数的时间,我们对项目有一个大的代码库,并我们是不允许剧变的工作。工具{A1}
的情况下我们面临的重构遗留代码时的情景,我的经验:我们是免费申请的变化,我们希望,世界是完美的。 :)我们可以申请,但只有与单元测试的目的,我们想要的变化,生产代码应该像往常一样工作。我们不允许改变基础设施以外的任何部分的代码,但记住的东西必须一如既往地工作,允许没有重大的变动。
让我们在每一个这些场景的阐述,我们如何履行我们的变化,使传统的代码更容易测试。具体的例子 public static class DataBaseHelper
{
public static void Persist(object instance)
{
Console.WriteLine("Persisting object " + instance);
}
}
{C}
正如你可以看到,坚持从DataBaseHelper什么也不做的方法,但我们会承担我们一些与数据库的硬依赖也许一个SqlConnection的一个SqlCommand的,等等。
现在,这段代码什么的那么糟糕吗?我们大多数人都见过,做maintainednbsp;多年这样的东西,如果你这个罚款,勇往直前,我不会在这里告诉你什么是错的,什么是完美的,是没有完美的解决方案。不过,我想向你展示的另一种方式,一种更好的方式,在我看来
我问什么是坏,一段代码,我没有回答,还... ...让我们来看看:{BR }
你能真正确保在以下天/周/月/年的代码将如预期?
,如果你在未来发现系统中的一个错误,你将如何丢弃问题的原因是不相关的这种方法?和检查这一事实时,你需要有你的调试器/ Visual Studio实例运行起来?
我想答案不那么令人愉快的所有,但不要担心,这篇文章的想法是,以帮助您找到一个合理的路径,使用TDD开发软件时。
应该我们的测试覆盖
好了,有几件事情,我们必须确保在我们的测试:
该产品通过服务方法进入其与真值的Enabled属性,和False值必须退出。
服务要坚持更新的产品实例。
现在让我们回到场景小节和每一个这些点提供一种解决方法。
我们免费申请的变化,我们希望,世界是完美的。 :)
这使我们能够使用一个奇妙的图案,我对{A2}
使用该谈论,我们可以做出如下介绍了以下测试:
public class Scenario1Tests
{
#region Mocks
private class NoOpProductRepository : IProductRepository
{
public void Persist(Product product)
{
// no-op
}
}
private class VerifiableProductRepository : IProductRepository
{
public event Action<Product> ProductPersisted;
public void Persist(Product product)
{
var productPersistedEvent = ProductPersisted;
if (productPersistedEvent != null)
productPersistedEvent(product);
}
}
#endregion
[Test]
public void when_disabling_a_product_its_enabled_property_is_set_to_false()
{
// Arrange
var product = new Product
{
ProductId = 1,
ProductName = "Car",
Enabled = true
};
// here we should use a mocking framework like Moq or Rhino.Mocks.
IProductRepository mockedRepository = new NoOpProductRepository();
var service = new ProductService(mockedRepository);
// Act
service.DisableProduct(product);
// Assert
Assert.AreEqual(false, product.Enabled);
}
[Test]
public void when_disabling_a_product_it_persist_the_changes_using_the_product_repository()
{
// Arrange
var product = new Product
{
ProductId = 1,
ProductName = "Car",
Enabled = true
};
Product productPassedToPersistMethod = null;
var valueOfProductEnabledPropertyPassedToPersistMethod = false;
// here we should use a mocking framework like Moq or Rhino.Mocks.
var mockedRepository = new VerifiableProductRepository();
mockedRepository.ProductPersisted +=
instance =>
{
productPassedToPersistMethod = instance;
valueOfProductEnabledPropertyPassedToPersistMethod = instance.Enabled;
};
var service = new ProductService(mockedRepository);
// Act
service.DisableProduct(product);
// Assert
Assert.IsNotNull(productPassedToPersistMethod);
Assert.AreSame(product, productPassedToPersistMethod);
Assert.AreEqual(false, valueOfProductEnabledPropertyPassedToPersistMethod);
}
}
正如你可以看到,这些都是相当巨大的变化,以便再次使用我们的ProductService现在我们要提供一个IProductRepository实施,因为它的构造要求之一。静态辅助类来访问数据库的使用已经移到我们的ProductRepository这是很可能是在运行时使用的具体实施。这是像StructureMap的IOC / DI工具的使用真正的亮点,因为它将帮助我们配置这样的顾虑(即连接ProductRepository IProductRepository)和透明其usage.nbsp;
现在我们获得了什么做这?好了,现在我们可以测试嘲讽它的依赖关系,并确保一切都如预期的在我们的荣幸ProductService.DisableProduct方法。如果将来有人惹尝试这种方法的实施,或如果我们引入我们的系统的变化,我们将有这两个单元测试,以弥补我们的背上,他们将让我们在晚上睡觉紧。我认为你能想出什么测试只是在代码,这是相当简单,它不超过10分钟来创建它们。我们希望,我们可以申请,但只有与单元测试的目的的变化,生产代码应该像往常一样工作。
在这里,我们可以使用被称为穷人的依赖注入的东西,你可以读取{A5},并让我告诉你,我同意麦Bogard让我们在这篇文章中的思想。我真的觉得,亲爱的开发者,我们应该努力的所有选项头号,推动很难让管理变革的基础设施,使用适当的架构和DI / IOC工具。但是,世界并不完美,管理或我们的工作环境并不总是太客气了接受改变。
所以我们做什么?,我们采取的第一种方法的代码,并修改ProductService类,并引入一个无参数的构造函数调用需要在一个IProductRepository实例中的构造和传递给它的ProductRepository类的一个实例,因此,删除一个DI / IOC工具需要建立在我们方面的实例。
下面是代码说明什么,我是在上段喃喃自语:
public class ProductService
{
private readonly IProductRepository _productRepository;
public ProductService()
: this(new ProductRepository())
{
}
public ProductService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public void DisableProduct(Product product)
{
product.Enabled = false;
_productRepository.Persist(product);
}
}
测试和所有其他的东西,我们保持同样的,我们仍然有一个可测试的ProductService类。我们不允许改变基础设施以外的任何部分的代码,但记住的东西必须一如既往地工作,允许没有重大的变动。
这意味着,没有接口,没有库,没有什么,因此,没有新的概念,允许将主要代码库介绍,你应该保持你的修改尽可能低的
,我必须承认,没有让我们多的选择,挑选... ...但是,即使在这种僵化环境中,我们可以用简单的方法更好。
我们将要做的是把我们的帮手,并使其具有足够的灵活性,让我们检查工作预计在未来的ProductService。当然,这种技术是不是我的,此刻我能找到的资源,我第一次读它,所以,如果有人有你一个链接,是我的客人。 public static class DataBaseHelper
{
private static Action<object> _persistenceDelegate;
static DataBaseHelper()
{
_persistenceDelegate = instance => Console.WriteLine("Persisting object " + instance);
}
public static void SetPersistenceDelegate(Action<object> persistenceDelegate)
{
_persistenceDelegate = persistenceDelegate;
}
public static Action<object> GetPersistenceDelegate()
{
return _persistenceDelegate;
}
public static void Persist(object instance)
{
_persistenceDelegate(instance);
}
}
可以看到,现在我们的静态辅助persist方法使用的SetPersistenceDelegate方法方式,可以交换的逻辑,GetPersistenceDelegate是有只是为了满足单元测试的需要,我们想对每个测试复位代表的价值(唐"不用担心,我将展示这个版本)。另一个要注意的是,这两种方法都是公开的,好了,我想我们可以让他们内部只允许单元测试组件使用它们,是向读者做太起见,我觉得这个演示,它是确定的(提示:{A6})。
现在,让我们的看到如何我们的单元测试看起来像:
public class Scenario3Tests
{
#region Init, Before and After each test
private Action<object> _originalDelegate;
[TestFixtureSetUp]
protected void Init()
{
_originalDelegate = DataBaseHelper.GetPersistenceDelegate();
}
[SetUp]
protected void BeforeEachTest()
{
DataBaseHelper.SetPersistenceDelegate(_originalDelegate);
}
[TearDown]
protected void AfterEachTest()
{
DataBaseHelper.SetPersistenceDelegate(_originalDelegate);
}
#endregion
[Test]
public void when_disabling_a_product_its_enabled_property_is_set_to_false()
{
// Arrange
var product = new Product
{
ProductId = 1,
ProductName = "Car",
Enabled = true
};
var service = new ProductService();
// Act
service.DisableProduct(product);
DataBaseHelper.SetPersistenceDelegate(instance =>
{
// no-op!
});
// Assert
Assert.AreEqual(false, product.Enabled);
}
[Test]
public void when_disabling_a_product_it_persist_the_changes_using_the_product_repository()
{
// Arrange
var product = new Product
{
ProductId = 1,
ProductName = "Car",
Enabled = true
};
Product productPassedToPersistMethod = null;
var valueOfProductEnabledPropertyPassedToPersistMethod = false;
DataBaseHelper.SetPersistenceDelegate(
instance =>
{
productPassedToPersistMethod = (Product) instance;
valueOfProductEnabledPropertyPassedToPersistMethod = productPassedToPersistMethod.Enabled;
});
var service = new ProductService();
// Act
service.DisableProduct(product);
// Assert
Assert.IsNotNull(productPassedToPersistMethod);
Assert.AreSame(product, productPassedToPersistMethod);
Assert.AreEqual(false, valueOfProductEnabledPropertyPassedToPersistMethod);
}
}
正如你可以看到,在测试的基本想法仍然相同,
,而不是用嘲笑库,我们使用的是定制的代表,并更新每个测试运行,以适应测试需求的帮手委托。终曲
好了,我希望这篇文章给亲爱的读者,您是如何的想法单元测试遗留代码。如果我做了一些愚蠢的声明,请与我承担,如果是的话,请让我知道我可以学到一些技巧;)
另外,我不太很清楚如何解释应使用一个DI / IOC工具来帮助我们解决在运行时依赖,我会尝试另一篇文章来解释这一,如果你不能等待在那里可以学到,有很多文章中的净这个问题
您可以下载示例代码{A7}