测试应该是: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
and之外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的设置已失效,但所有工厂方法都简单明了。无需查看它们的定义——它们一目了然。
是否可以进一步提高编写和维护的便利性?
一个很酷的想法是在您的测试项目中提供扩展方法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();
我希望这种工厂的代码很容易推断所以我不会在这里写,因为这个答案似乎有点长;)