繁体   English   中英

如何对代码进行分解以简化可测试性?

[英]How do I factor code to ease testability?

我正在学习单元测试,并想知道如何编写可测试的代码。 但是,我不确定如何在不使其复杂的情况下编写可测试代码。 我将采用着名的汽车和发动机问题来描述问题。

class Car
{
private:
   Engine m_engine;

public:
   Car();
   // Rest of the car
}

我提出了以下解决方案,以使上述代码可测试。

  1. 更改Car的构造函数以将Engine作为参数。 然后模拟引擎并进行测试。 但是,如果我没有不同类型的引擎,那么参数化构造函数似乎是不合适的,只是为了使它可测试。

  2. 使用setter然后将模拟引擎传递给setter。 与上述相同的流程。

  3. 首先测试引擎,然后使用经过验证的引擎(或使用存根引擎)测试汽车。

我必须在代码上测试哪些替代方案? 每种方法的优点和缺点是什么?

从不同的(测试驱动开发)观点来看:易于测试的代码易于使用。 编写单元测试实际上是在测试代码的“公共接口”。 如果它很难测试,那是因为你在那里有一些依赖,这使得它很难。 你真的需要一个遏制关系,还是一个联想关系会更有意义?

在你的情况下,我个人认为在构造函数中传递Engine会更容易测试,所以我会像你的建议#1那样重构构造函数。 您可以在一个测试套件中测试引擎,并提供一个模拟引擎来测试另一个测试套件中的Car。 现在测试它很简单,这意味着界面易于使用。 这是好事。

现在考虑如何在实际项目中使用该实现。 您将创建一个CarFactory类,工厂将创建一个引擎并将其放入Car中,然后再将其交付给您。 (还要注意这最终如何更接近地模拟汽车,发动机和工厂的真实世界,但我离题了。)

因此,TDD的答案是重构代码以在构造函数上获取Engine指针。

如果您只有一个引擎类型,为什么要尝试将其作为新对象? 如果您不打算交换引擎,请不要创建另一个抽象层。 只需将发动机作为汽车的一部分。

您可能正在分解以降低复杂性,而不是重用组件。 好决定。 在这种情况下,我会说3是你最好的选择 - 验证你的低级组件,然后使用调用较低级别对象的更高级代码。

实际上,Engine更像是数据库。 并且您将需要更改构造函数以使用不同的数据库(出于测试原因或其他原因),但您可以暂时搁置该谎言。

正如其名称所暗示的,单元测试是关于测试单元以确认它们按照规定工作。 这意味着,您应该单独测试引擎。

系统测试或集成测试是关于测试它们是否正确“粘合”在一起。

当然,它比这更复杂,但它应该指向正确的方向。

选项1通常是正确的方式。

通过完全控制您给汽车的发动机,您可以很好地测试汽车。

您可以使用引擎为汽车提供的所有不同输出,更轻松地测试汽车的行为方式。 您还可以确保汽车对发动机进行适当的调用。

在构造函数中使用它可以清楚地表明Car依赖于引擎来工作。 将它与依赖注入框架一起使用,构造函数问题根本不是问题。

真正的问题是,有哪些要求? 如果目标是简单地实现“汽车”对象,那么它甚至不需要引擎。

测试应始终与要求相关。 任何对象模型都将是对现实的一些概括,因此问题在于需要表示哪些方面。

一旦你的需求下降,那么你应该在通用的高级别上编写测试。 然后,OO设计应该以这样的方式完成,即可以实现这些测试。

Misko Hevery经常写这个话题。 这是 2009年10月的演示文稿 。他认为依赖图应该在构造函数中是显式的。

参数化构造函数以使其可测试似乎是不合适的

我认为这是一个引人入胜的评论。 在什么样的条件下,包括可测性作为设计的一部分是不合适的? 牺牲可测试性的正确性显然是错误的,尽管在实践中我从未见过这种选择被迫。 牺牲可测性的性能......也许在某些特定情况下。 牺牲代码一致性? 我个人宁愿改变编码标准,并逐步将遗留代码纳入合规性。

C ++的一个可能性是使用友谊:

class Car
{
private: //for unit testing
    friend class TestCar; //this class is the unit test suite for the Car class
    Car(Engine* mockEngine); //this constructor is only used by the TestCar class
private:
    Engine* m_engine;
public:
    Car();
    // Rest of the car
};

第二种可能性是构造函数的实现使用全局/静态方法,例如如下所示,您可以通过一些配置文件或通过链接(可能是动态链接)到此不同版本来更改此方法的实现方法:

Car::Car()
{
    m_engine = Engine::create();
}

我主张允许(通过构造函数或属性)将Engine的实现添加到Car(最好是IEngine)。

汽车测试实际上并不关心引擎的作用,只要它能够正确响应调用引擎的结果。 然后,使用假引擎进行测试,以便您可以控制发送到汽车的信号,您应该好好去。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM