重构使用new运算符的代码更易于测试

时间:2010-12-12 19:20:51

标签: c# .net unit-testing tdd factory-pattern

我有以下代码,我正在尝试对其进行单元测试:
public override IRenderable GetRenderable()
{
var val = SomeCalculationUsingClassMemberVariables();
return new EquationRenderable(val);
}

好像我想在这里使用工厂,所以我可以将IRenderable的创建与这个类分开。问题是我有许多这些类创建了以不同方式构造的不同IRenderables,因此我需要为每个类实现一个新的工厂方法。解决这个问题的最佳方法是什么?

3 个答案:

答案 0 :(得分:1)

取决于具体IRenderable构造函数的一致性,您可以使用以下模式进行工厂创建

public IRenderable CreateInstance<T>(object calculation) where T : IRenderable 
{
   Activator.CreateInstance<T>(new[] { calculation });
}

或者如果你有许多不同的构造函数,你可以使用params关键字来传递任意数量的参数

public IRenderable CreateInstance<T>(params object[] args) where T : IRenderable 
{
   Activator.CreateInstance<T>(args);
}

为了能够在调用Activator.CreateInstance之前对您使用此代码的参数进行某种运行时检查

var types = args.Select(o => o.GetType()).ToArray();

var c = typeof(T).GetConstructor(types);
if (c == null)
{
   throw new InvalidOperationException("No matched constructor")
}

答案 1 :(得分:1)

好问题。

首先,由于DI容器没有以“正确”的方式使用或者设计可以改进,因此感觉诱惑使用AbstractFactories到处都有点闻。

但有时候我也会遇到这个问题。我看到以下内容:

  1. 使用AbstractFactory / Factory并注入它。对于C#,您必须能够传递委托,充当创建实例的接口。
  2. 'new'即可,只需测试'new'的输出。
  3. 在提取的方法中记住'new'的调用(hacky !!)
  4. 注射已经提到了,所以我不再重复了。我更喜欢Java,所以请原谅一些语法错误。

    测试'new'

    的输出

    如果'new'创建的实例是域对象而不是服务,我经常使用它。因为它是直接在方法中返回的,所以可以用我的测试来测试直接输出。

    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);
    }
    
    

    记住“新”本身的呼叫

    只有在以某种方式将其作为返回值时,才能测试上面的直接输出。如果代码的“副作用”是间接输出,那么事情变得更加复杂,你无法直接通过返回值来感知。如果是这样,我经常提取新创作的方法,然后在我的测试中控制它。这很令人讨厌,我更多地使用它来安全地进行测试,然后开始更多的重构(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#的模拟框架来缩短。无论如何,测试返回值直接输出更简单,更方便。

    也许你有一个更详细的例子?

答案 2 :(得分:0)

更好的方法可能是简单地对代码进行单元测试,而不是重构代码。从技术上讲,这可以通过使用合适的模拟工具来完成,例如TypeMock Isolator或Microsoft Moles(还有第三个我现在不记得了)。