使用依赖注入时,我是否针对接口或实现进行测试?

时间:2014-05-08 23:44:39

标签: unit-testing dependency-injection

我有一个接口和实现类,我在构造类时使用依赖注入:

class House {
public:
    virtual void blah() = 0; //etc. all the interface of my class
};

class HouseImpl : public House {
protected:
    Door *front_door;
public:
    HouseImpl(Door *door) : front_door(door) {}
};

现在,在我的工厂方法或实际构建类及其依赖项的任何方法中,我是否传入依赖项,或者动态创建它们?

选项1

通过传入依赖项,我的单元测试可以使用相同的工厂传入一个模拟对象,我的测试只针对接口编写。

House* get_house(Door* door) {
    return new HouseImpl(door);
}

void unit_test_method_on_house() {
    MockDoor mock_door;
    House* class_under_test = get_house(mock_door);
}

在这种方法中,我认为我的工厂方法基本上是我的接口类型的构造函数的抽象。我看到的缺点是,当我添加依赖项时,它们都必须添加到工厂方法签名中,这实际上只是将降压传递给客户端。

选项2

另一种方法是工厂是生产代码中使用的工厂,单元测试直接实例化实现类。

House* get_house() {
    return new HouseImpl(new Door());  //ignore the memmory management details here
}

void unit_test_method_on_house() {
    MockDoor mock_door;
    HouseImpl class_under_test(mock_door);
}

这里的问题是我打开单元测试取决于实现类,而不是仅依赖于接口。显然,我可以非常小心地编写不能做出特定实现假设的好测试,但是在一个大型项目中,我不想假设所有开发人员都会像我一样小心。

那么,推荐的方法是什么?

2 个答案:

答案 0 :(得分:4)

当您进行单元测试时,您的单元测试本质上是tightly coupled到SUT。这对于单元测试能够识别和限制系统中的运动部件是必要的。换句话说,单元测试应该直接与SUT类一起使用,以便将它与任何虚假对象区分开来。

另一种看待它的方法是测试需要知道SUT的实际类型,以便知道要测试什么。界面侧重于"什么",而实现侧重于"如何"。如果"怎么"如果发生变化,可能需要调整单元测试以对其进行调整。

工厂对象仅存在于protect the consumer与紧密耦合到创建的对象。由于单元测试并不担心与SUT紧密耦合,因此使用工厂并不会带来好处(它确实会妨碍工作)。


编辑:你还是应该只测试SUT的公共界面。如果单元测试对SUT的内部工作原理有太多的了解,那么在不破坏测试的情况下重构代码会变得更加困难。

单元测试与其SUT之间的紧密耦合并不仅仅是一种负担;它可以用于你的优势。使用interface as an access modifier,您可以将某些方法公开给其他消费者不一定会看到的测试。

SUT的构造函数属于这一类。例如,选项1 中的工厂方法假定返回的任何房屋必须使用门,但选项2 中的工厂方法不允许。 Door的存在是工厂可能需要隐藏的实现细节(例如,House {{1}}实现不需要门。

如果测试直接实例化SUT,您不必担心这些类型的实施细节会泄漏到您的设计中。

答案 1 :(得分:2)

单元测试依赖于实现是很好的,这样它就可以构造一个要测试的实例。这是单元测试的用途:测试该类。 (选项2。)

但你是对的,单元测试应该关注实现是否实现它应该的接口。您可以通过将测试实例声明为接口类型而不是实现类型来强调这一点。

然而,有时候你也会想要测试哪些是私有的方法,这些方法非常棘手,需要自己测试。在这种情况下,测试实例需要是实现类型。

我发现第二种单元测试不太常见,所以当我写一篇时,我只需写一条评论就可以清楚地表明它是特定于实现的。