从您打算测试的类继承它总是次优的吗?

Ear*_*rlz 1 c# inheritance unit-testing design-patterns mocking

所以,我一直在阅读有效使用遗留代码.它非常棒,涵盖了许多很酷的技术,这些技术在未经测试的代码上也是"安全的".

我最喜欢的一个部分是建议在有麻烦的依赖项时扩展你打算测试的类.例如

class FooBar
{
  public List<Foo> ReadFoos()
  {
    var s=new SqlConnection(Config.SomeConntectionStirng);
    s.Read("Foo")....
  }
}
Run Code Online (Sandbox Code Playgroud)

被重构成

class FooBar
{
  public List<Foo> ReadFoos()
  {
    var s=MyConnection();
    s.Read("Foo")....
  }
  protected virtual IConnection MyConnection()
  {
    return new SqlConnection(Config.SomeConnectionString);   
  }
}
Run Code Online (Sandbox Code Playgroud)

然后测试它,你会继承它:

class TestableFooBar : FooBar
{
  protected override IConnection MyConnection()
  {
    return new MockConnection();
  }
}
Run Code Online (Sandbox Code Playgroud)

并对扩展课程进行测试.

现在,我的问题.这样做可以,而不是以后重构吗?我说这个的主要原因是因为这对于一些通常很难进行单元测试而不会使调用API更难使用的代码非常有用.在公共API上,您肯定希望它尽可能简单.那么,这是一个保持设计的好事吗?

Ant*_*ram 5

从设计的角度来看,"提取和覆盖"可能不是您选择完成的地方,但它取决于您更改现有代码的预算,而不是添加新功能.虽然您也可能不热衷于更改公共API,但有一些技术可以保留API,同时还允许更合适的依赖注入.我相信这本书讨论了一种这样的技术,它是一个默认构造函数,它提供了对重载的依赖(下面的示例),另一种可以简单地使用依赖注入容器.

public class Foo
{
     IDependency dependency;

     public Foo() // preserves API
          : this(new Dependency())
     {
     }

     internal Foo(IDependency dependency) 
     {
         this.dependency = dependency;
     }
Run Code Online (Sandbox Code Playgroud)

注意:在此示例中,我将第二个构造函数标记为内部,但您可以将其保留为公共,特别是如果您希望程序集外的类可以选择提供该依赖项.为了简单的可测试性,我喜欢内部修饰符,因为我会通过"InternalsVisibleTo"程序集属性将内部暴露给我的单元测试项目.这使您可以保持API的清洁,但对于此类技术以及内部帮助程序类也具有最大可测试性.