C# 重构使用新运算符的代码,使其更易于测试
我有以下代码,正在尝试对其进行单元测试:C# 重构使用新运算符的代码,使其更易于测试,c#,.net,unit-testing,tdd,factory-pattern,C#,.net,Unit Testing,Tdd,Factory Pattern,我有以下代码,正在尝试对其进行单元测试: 公共重写IRenderable GetRenderable() { var val=SomeCalculationUsingClassMemberVariables(); 返回新的equalationRenderable(val); } 看起来我想在这里使用一个工厂,这样我就可以将IRenderable的创建从这个类中分离出来。问题是,我有许多这样的类,它们创建不同的iUnderable,这些iUnderable以不同的方式构造,因此我需要为每个类实
公共重写IRenderable GetRenderable()
{
var val=SomeCalculationUsingClassMemberVariables();
返回新的equalationRenderable(val);
}
看起来我想在这里使用一个工厂,这样我就可以将IRenderable的创建从这个类中分离出来。问题是,我有许多这样的类,它们创建不同的iUnderable,这些iUnderable以不同的方式构造,因此我需要为每个类实现一个新的工厂方法。解决此问题的最佳方法是什么?根据混凝土IRenderable构造函数的均匀性,您可以使用以下模式进行工厂创建
public IRenderable CreateInstance<T>(object calculation) where T : IRenderable
{
Activator.CreateInstance<T>(new[] { calculation });
}
更好的方法可能是简单地按原样对代码进行单元测试,而不是重构代码。从技术上讲,这可以通过使用合适的模拟工具来实现,如TypeMock隔离器或Microsoft Moles(还有第三个工具,我现在不记得了)。问得好 首先,到处使用AbstractFactorys的感觉有点异味,因为DI容器的使用方式不“正确”,或者设计可能会有所改进 但有时我也会遇到这个问题。我看到以下情况:
Prod-Code:
...
public override IRenderable GetRenderable()
{
var val = SomeCalculationUsingClassMemberVariables();
return new EquationRenderable(val);
}
Test Case:
...
[Test]
public void test_new()
{
SUT sut = ...;
IRenderable r = sut.GetRenderable();
assertTrue(r instanceof EquationRenderable);
}
Stub调用“new”本身
只有当您以某种方式将其作为返回值时,才能测试上面的直接输出。如果代码的“副作用”是间接输出,而您无法通过返回值直接感知,那么事情就会变得更加复杂。若是这样的话,我经常提取新创建的方法,然后在测试中控制它。这很恶心,我更多地使用它来保证测试的安全,并在以后开始更多的重构(DI和工厂)。我有时会在遗留代码中这样做,在遗留代码中,直接使用“new”创建服务,而在没有测试的情况下重构到DI风险太大
Prod-Code:
...
public override IRenderable GetRenderable()
{
var val = SomeCalculationUsingClassMemberVariables();
return createEquationRenderable();
}
public IRenderable createEquationRenderable()
{
return new EquationRenderable(val);
}
Test Case:
...
class Stubbed : SUT
{
boolean called = false;
public override EquationRenderable createEquationRenderable()
{
called=true;
return MyMock();
}
}
[Test]
public void test_new()
{
Stubbed sut = new Stubbed();
sut.GetRenderable();
assertTrue(sut.called);
// do further stuff on MyMock
}
我知道,这个例子有些过分,有点毫无意义,只是为了描述这个想法。我确信上面提到的C#模拟框架可以简化。无论如何,在这里测试直接输出的返回值是更简单和更好的方法
也许您有一个更详细的示例?我也考虑过这种方法,但如果传入错误的参数,似乎会导致查找运行时错误变得更困难。此外,客户无法知道创建特定实例需要哪些参数,因为具体的构造函数根本不是很统一。也许我在这里要求的太多了。当客户端调用CreateInstance方法时,他们必须传递一个类型,并且在这一点上了解实现(rtfm:)。确实,您不能对参数执行编译器检查,但这与客户端无法在文档中读取要传递的参数不同。我想说的是,除非你限制自己在IRenderable类中允许使用什么构造函数,否则没有办法在工厂模式中对参数进行编译器检查。我添加了一个小片段,所以运行时错误可能会使无效参数更有意义。好的,我也差不多就是在那里结束的。工厂模式是否通常用于这种场景,其唯一目的是增加可测试性?是的,工厂模式或依赖项注入是使事情更易于插入和测试的目的。我可以向您保证,DI将采用相同的方法,在运行时将参数匹配到正确的构造函数,因此切换到DI没有任何帮助。。。这有点棘手,因为您没有提供太多的代码,但是您没有让需要IRenderable对象的类自己创建它,而是将这样的对象传递给它。通过这种方式,您可以实例化有问题的类,将一些伪IRenderable对象传递给它,并测试您需要测试的任何方法。GetRenderable函数基本上返回调用它的类的字符串版本,因此它几乎就像序列化对象一样。我不确定如何从它所在的对象中提取,因为Renderable是基于成员变量创建的。顺便说一句,谢谢你的意见!现在我要测试的是返回类型是否正确,如第一个示例中所示。问题是,大多数逻辑都是在计算传递到所创建对象中的参数时进行的,因此不会捕获其中的错误。我并不是完全反对你的第二个想法,但嘲笑我正在测试的东西似乎有点不礼貌。正如你提到的,我也感觉到设计可以改进,但我很难看到如何修复它。我也可以测试返回类型上的值是否正确,但我也在测试该类型的实现,如果这种情况发生变化,它将破坏不同领域的测试。现在,我想我只是要将传入参数的属性设置为公共的(我想我的API将来无论如何都需要),这样我就可以在返回的对象上针对该公共属性进行测试。不过,我会继续看看我能做些什么来改进设计。我真的很感谢你的详细回复!没问题:)对于单元测试和处理代码库,我真的可以推荐一些书(xUnit测试模式)和(有效地处理遗留代码)
Prod-Code:
...
public override IRenderable GetRenderable()
{
var val = SomeCalculationUsingClassMemberVariables();
return new EquationRenderable(val);
}
Test Case:
...
[Test]
public void test_new()
{
SUT sut = ...;
IRenderable r = sut.GetRenderable();
assertTrue(r instanceof EquationRenderable);
}
Prod-Code:
...
public override IRenderable GetRenderable()
{
var val = SomeCalculationUsingClassMemberVariables();
return createEquationRenderable();
}
public IRenderable createEquationRenderable()
{
return new EquationRenderable(val);
}
Test Case:
...
class Stubbed : SUT
{
boolean called = false;
public override EquationRenderable createEquationRenderable()
{
called=true;
return MyMock();
}
}
[Test]
public void test_new()
{
Stubbed sut = new Stubbed();
sut.GetRenderable();
assertTrue(sut.called);
// do further stuff on MyMock
}