您是否应该提前了解每个测试的依赖关系

时间:2018-01-31 15:03:17

标签: unit-testing dependency-injection mocking tdd solid-principles

我正在探索TDD和SOLID原则。假设我有一项服务,我在编写实现之前创建了一个失败的测试。

public interface IService {

    bool DoSomething();

}

public class Service : IService {

    bool DoSomething() {
    ...
    }

}

[TestMethod]
public void ServiceImplementationTest()
{
    var implementation = new Service();
    bool result = implementation.DoSomething();
    Assert.IsTrue(result);
}

当我第一次编写测试时,我并不知道这个服务的依赖关系,所以构造函数不需要参数,因为我不需要注入任何依赖项。

但是,当我编写实现时,我意识到我需要一个特定的依赖项,因此在构造函数中添加对该依赖项的引用。为了保持测试代码的编译和失败,我必须回到测试,并修改它以创建一个虚假的实现。

public class Service : IService {
    public Service(IDependency dependency) {
        _dependency = dependency;
    }
    bool DoSomething() {
        ... use _dependency ...
        return result;
    }

}

[TestMethod]
public void ServiceImplementationTest()
{
    var implementation = new Service(*** new MockDependency() ***);
    bool result = implementation.DoSomething();
    Assert.IsTrue(result);
}

这只是生活中的一个事实吗?在编写测试之前我应该​​知道所有依赖项吗?当我想编写一个具有不同依赖关系的新实现时会发生什么,是否有必要为每个实现编写一个新的测试,即使实现的正确性没有改变?

1 个答案:

答案 0 :(得分:4)

  

您是否应该事先知道每个测试的依赖性

不一定,没有。

当你设计"先测试时,你在做什么"是你正在探索一个可能的API。所以下面的代码

var implementation = new Service();
bool result = implementation.DoSomething();
Assert.IsTrue(result);
除其他事项外,

表示 public API 应该允许您创建Service的实例,而不了解其依赖关系。

  

但是,当我编写实现时,我意识到我需要一个特定的依赖项,因此在构造函数中添加对该依赖项的引用。为了保持测试代码的编译和失败,我必须回到测试,并修改它以创建一个虚假的实现。

请注意这里的两件事

  1. 这不是一个向后兼容的变化......
  2. 这意味着它不是重构
  3. 因此,部分问题在于您以向后不兼容的方式介绍所需的更改。如果你是重构,你将有一个步骤,你的构造函数看起来像

    public Service() {
        this(new IDependency() {
            // default IDependencyImplementation here
        });
    }
    
    Service(IDependency dependency) {
        this.dependency = dependency;
    }
    

    此时,您有两个独立的决定

    • 应该Service(IDependency)成为公共API的一部分

    如果是这样,那么你开始编写强制你公开构造函数的测试

    • 应该弃用Service()

    如果它应该,那么当您从公共API中删除Service()时,您计划删除依赖它的测试

    注意:如果您还没有共享/发布公共API,那么这是很多不必要的步骤。除非您故意采用校准测试不可变的规则,否则通常更实际的是破解测试以反映最新的API草案。

    但是,不要忘记在更改之后重新校准测试;对测试的任何更改都必须触发刷新红/绿循环,以确保修订后的测试仍在测量您的预期。您永远不应该发布您尚未校准的测试。

    有时有用的模式是将被测系统的组成与自动检查分开

    public void ServiceImplementationTest()
    {
        var implementation = new Service();
        check(implementation);
    }
    
    void check(Service implementation) {
        bool result = implementation.DoSomething();
        Assert.IsTrue(result);
    }
    

    另一种选择是写一个spike - 一个没有测试的可能实现的草案,目的是探索更好地理解问题的约束,当它被抛弃时学习练习已经完成。

      

    您能快速描述测试校准的含义吗?谷歌并没有多大帮助。是否确保测试失败?

    是的:确保失败时应该这样做,确保他们失败时报告的消息是合适的,当然也要确保他们应该通过。

    我写了一些关于here的想法。