c# – 在TestMethod和TestInitialize中创建测试对象
目前,我正在通过在每个TestMethod中创建测试对象来编写单元测试.这样做的主要原因是为了促进自我包容,易于阅读/调试,并且还可能在构造期间定制依赖性.
public class MyClass { public MyClass(ISomeService serviceA,ISomeOtherService serviceB) { ... } } [TestMethod] public void it_should_do_something() { var myClass = new MyClass(serviceA,serviceB); myClass.DoSomething(); ... } 然而,这正在成为维护的噩梦,特别是当可能在类上进行许多测试时,我想在构造函数中添加另一个依赖项.它要求我改变我的测试类中的每一行构造. 我希望通过在TestInitialize方法中初始化对象来保持DRY,这将改善维护,同时仍然保持自包含方面. private MyClass _myClass; [TestInitialize] public void Init() { _myClass = new MyClass(serviceA,serviceB); } 我可以看到这样做的唯一缺点是,如果我需要注入一组不同的依赖项,我可能需要重新初始化对象.对于第一次查看代码的另一个开发人员来说,读取/调试也可能稍微不那么清楚.我没有想到这种方法还有其他任何负面影响吗? >这种情况的最佳做法是什么? 解决方法
测试应该是:1)可读,2)易于编写/维护. “可读”是什么意思?这意味着它可以最大限度地减少跳过代码的数量,以了解在特定代码行上执行的操作.
考虑在测试项目中创建工厂.我认为这是测试安排可读性的最佳方式之一: public static class MyClassFactory { public static MyClass Create(ISomeService serviceA,ISomeOtherService serviceB) { return new MyClass(serviceA,serviceB); } public static MyClass CreateEmpty() { return new MyClass(); } //Just an example,but in general not really recommended. //May be acceptable for simple projects,but for large ones //remembering what does "Default" mean is an unnecessary burden //and it somehow reduces readability. public static MyClass CreateWithDefaultServices() { return new MyClass(/*...*/); } //... } (第二种选择将在后面解释).然后你可以像这样使用它: [TestMethod] public void it_should_do_something() { //Arrange var myClass = MyClassFactory.Create(serviceA,serviceB); //Act myClass.DoSomething(); //Assert //... } 请注意它如何提高可读性.除了serviceA和serviceB,我只是为了符合你的例子,你需要理解的一切都在这一行.理想情况下,它看起来像这样: [TestMethod] public void it_should_do_something() { //Arrange var myClass = MyClassFactory.Create( MyServiceFactory.CreateStubWithTemperature(45),MyOtherServiceFactory.CreateStubWithContents("test string")); //Act myClass.DoSomething(); //Assert //... } 这提高了可读性.请注意,即使设置了DRY-ied,所有工厂方法都很简单明了.没有必要查看他们的定义 – 他们从一见钟情就清楚了. 写作和维护是否可以进一步改进? 一个很酷的想法是在MyClass的测试项目中提供扩展方法,以允许可读的配置: public static MyClassTestExtensions { public static MyClass WithService( this MyClass @this,ISomeService service) { @this.SomeService = service; //or something like this return @this; } public static MyClass WithOtherService( this MyClass @this,ISomeOtherService service) { @this.SomeOtherService = service; //or something like this return @this; } } 那么你当然可以这样使用它: [TestMethod] public void it_should_do_something() { //Arrange var serviceA = MyServiceFactory.CreateStubWithTemperature(45); var serviceB = MyOtherServiceFactory.CreateStubWithContents("test string"); var myClass = MyClassFactory .CreateEmpty() .WithService(serviceA) .WithOtherService(serviceB); //Act myClass.DoSomething(); //Assert //... } 当然,服务不是这种配置的一个很好的例子(通常没有它们就无法使用对象,所以没有提供默认的构造函数)但是对于任何其他属性它会给你一个巨大的优势:使用N个属性来配置你保持干净和可读使用N扩展方法创建具有所有可能配置的测试对象的方法,而不是N x N工厂方法,并且在构造函数或工厂方法调用中没有N长参数列表. 如果您的类不像上面假设的那样方便,一种可能的解决方案是将其子类化,并定义子类的工厂和扩展. 如果对象仅与其部分属性一起存在并且它们不可设置没有意义,则可以流畅地配置工厂,然后创建对象: //serviceA and serviceB creation var myClass = MyClassFactory .WithServiceA(serviceA) .WithServiceB(serviceB) .CreateMyClass(); 我希望这个工厂的代码很容易推断,所以我不会在这里写,因为这个答案似乎有点长;) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |