简体   繁体   English

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

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

I am learning about Unit Testing and want to know how to write testable code. 我正在学习单元测试,并想知道如何编写可测试的代码。 But, I'm not sure how to write testable code without making it complex. 但是,我不确定如何在不使其复杂的情况下编写可测试代码。 I'll take famous Car and Engine problem to describe the problem. 我将采用着名的汽车和发动机问题来描述问题。

class Car
{
private:
   Engine m_engine;

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

I came up with following solutions to make the above code testable. 我提出了以下解决方案,以使上述代码可测试。

  1. Changing the Car's constructor to take Engine as a parameter. 更改Car的构造函数以将Engine作为参数。 Then mock the Engine and do the testing. 然后模拟引擎并进行测试。 But, if I don't have different kinds of Engines, it seems inappropriate to parameterize the constructor just to make it testable. 但是,如果我没有不同类型的引擎,那么参数化构造函数似乎是不合适的,只是为了使它可测试。

  2. Using a setter and then pass a mock Engine to the setter. 使用setter然后将模拟引擎传递给setter。 Same flow as the above. 与上述相同的流程。

  3. Testing the Engine first and then testing the Car with proven Engine (or using a stub Engine). 首先测试引擎,然后使用经过验证的引擎(或使用存根引擎)测试汽车。

What are the alternatives I have to make above code testable? 我必须在代码上测试哪些替代方案? What are the strenghts and weaknesses of each method? 每种方法的优点和缺点是什么?

Look at it from a different (Test-Driven Development) viewpoint: code that is easy to test is easy to use. 从不同的(测试驱动开发)观点来看:易于测试的代码易于使用。 Writing the unit tests is actually you testing the "public interface" of your code. 编写单元测试实际上是在测试代码的“公共接口”。 If it's hard to test, that's because you've got some dependencies in there that make it hard. 如果它很难测试,那是因为你在那里有一些依赖,这使得它很难。 Do you really need a containment relationship, or would an associative relationship make more sense? 你真的需要一个遏制关系,还是一个联想关系会更有意义?

In your case I personally think it would be more testable to pass the Engine in the constructor, so I'd refactor the constructor as in your suggestion #1. 在你的情况下,我个人认为在构造函数中传递Engine会更容易测试,所以我会像你的建议#1那样重构构造函数。 You can test the Engine in one test suite, and provide a mock Engine to test the Car in another test suite. 您可以在一个测试套件中测试引擎,并提供一个模拟引擎来测试另一个测试套件中的Car。 Testing it is now easy, meaning the interface is easy to use. 现在测试它很简单,这意味着界面易于使用。 That's a good thing. 这是好事。

Now think about how you would use that implementation in a real project. 现在考虑如何在实际项目中使用该实现。 You'd create a CarFactory class, and the factory would create an Engine and put it in the Car before delivering it to you. 您将创建一个CarFactory类,工厂将创建一个引擎并将其放入Car中,然后再将其交付给您。 (Also notice how this ends up more closely modeling the real world of cars and engines and factories, but I digress.) (还要注意这最终如何更接近地模拟汽车,发动机和工厂的真实世界,但我离题了。)

Therefore the TDD answer would be to refactor the code to take an Engine pointer on the constructor. 因此,TDD的答案是重构代码以在构造函数上获取Engine指针。

If you only have one Engine type, why are you trying to make it a new object? 如果您只有一个引擎类型,为什么要尝试将其作为新对象? If you don't plan on swapping engines, don't create another abstraction layer. 如果您不打算交换引擎,请不要创建另一个抽象层。 Just make the engine part of the car. 只需将发动机作为汽车的一部分。

You might be decomposing to reduce complexity, rather than to reuse components. 您可能正在分解以降低复杂性,而不是重用组件。 Good call. 好决定。 In which case, I'd say that 3 is your best bet - validate your lower level components, then use higher level code that calls the lower level objects. 在这种情况下,我会说3是你最好的选择 - 验证你的低级组件,然后使用调用较低级别对象的更高级代码。

In reality, Engine is more likely to be something like Database. 实际上,Engine更像是数据库。 And you will want to change your constructors to use a different Database (for test reasons, or other reasons), but you can leave that lie for a while. 并且您将需要更改构造函数以使用不同的数据库(出于测试原因或其他原因),但您可以暂时搁置该谎言。

Unit Testing, as it's name implies, is about testing the Units to confirm that they work as prescribed. 正如其名称所暗示的,单元测试是关于测试单元以确认它们按照规定工作。 This means, you should Test engine separately. 这意味着,您应该单独测试引擎。

System Testing or Integration Testing is about testing that they all 'glue' together correctly. 系统测试或集成测试是关于测试它们是否正确“粘合”在一起。

Of course, it is more complex than just this, but it should point you in the right direction. 当然,它比这更复杂,但它应该指向正确的方向。

The option 1 is generally the right way. 选项1通常是正确的方式。

By being able to have full control of the engine you give to the car, you are able to test the car v. well. 通过完全控制您给汽车的发动机,您可以很好地测试汽车。

You can more easily test how the car behaves with all the different outputs the engine gives to the car. 您可以使用引擎为汽车提供的所有不同输出,更轻松地测试汽车的行为方式。 You can also make sure the car its making the appropriate calls to the engine. 您还可以确保汽车对发动机进行适当的调用。

Having it in the constructor makes it really clear that the Car depends on an Engine to work. 在构造函数中使用它可以清楚地表明Car依赖于引擎来工作。 Use it with a dependency injection framework, and the constructor issue isn't really a problem at all. 将它与依赖注入框架一起使用,构造函数问题根本不是问题。

The real question is, what are the requirements? 真正的问题是,有哪些要求? If the goal is to simply implement a "Car" object then it doesn't even need an engine. 如果目标是简单地实现“汽车”对象,那么它甚至不需要引擎。

Tests should always be related to requirements. 测试应始终与要求相关。 Any object model is going to be some generalization of reality, so the issue is what aspects are required to be represented. 任何对象模型都将是对现实的一些概括,因此问题在于需要表示哪些方面。

Once you have your requirements down pat, then you should write your tests, at a generic high level. 一旦你的需求下降,那么你应该在通用的高级别上编写测试。 Then, the OO design should be done in such a way that these tests can be implemented. 然后,OO设计应该以这样的方式完成,即可以实现这些测试。

Misko Hevery writes frequently on this topic. Misko Hevery经常写这个话题。 Here's a presentation from October 2009. He argues that the dependency graph should be explicit in the constructor. 这是 2009年10月的演示文稿 。他认为依赖图应该在构造函数中是显式的。

it seems inappropriate to parameterize the constructor just to make it testable 参数化构造函数以使其可测试似乎是不合适的

I think that's a fascinating comment. 我认为这是一个引人入胜的评论。 Under what sorts of conditions would including testability as part of the design be inappropriate? 在什么样的条件下,包括可测性作为设计的一部分是不合适的? Sacrificing correctness for testability is clearly wrong, though in practice I have never seen that choice forced. 牺牲可测试性的正确性显然是错误的,尽管在实践中我从未见过这种选择被迫。 Sacrificing performance for testability... perhaps that, in some specific cases. 牺牲可测性的性能......也许在某些特定情况下。 Sacrificing code uniformity? 牺牲代码一致性? I personally would rather change the coding standard, and gradually bring the legacy code into compliance. 我个人宁愿改变编码标准,并逐步将遗留代码纳入合规性。

One possibility in C++ is to use friendship: 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
};

A second possibility is for the constructor's implementation to use a global/static method for example as follows, and you can change the implementation of this method, either via some config file, or by linking (perhaps dynamic-linking) to different versions of this method: 第二种可能性是构造函数的实现使用全局/静态方法,例如如下所示,您可以通过一些配置文件或通过链接(可能是动态链接)到此不同版本来更改此方法的实现方法:

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

I'd argue in favor of allowing (via constructor or property) the ability to add an implementation of an Engine to the Car (which is preferably an IEngine). 我主张允许(通过构造函数或属性)将Engine的实现添加到Car(最好是IEngine)。

The Car tests shouldn't actually care what the Engine does, so long as it responds properly to the results from calls to the Engine. 汽车测试实际上并不关心引擎的作用,只要它能够正确响应调用引擎的结果。 Then, use a fake Engine to test so that you can control the signals sent to the car, and you should be good to go. 然后,使用假引擎进行测试,以便您可以控制发送到汽车的信号,您应该好好去。

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

相关问题 同时优化和可测试性 - 如何将代码分解为更小的单元 - Optimization and testability at the same time - how to break up code into smaller units 什么是全球国家?它们如何影响可测试性以及如何避免它们? - What are global states? How do they affect testability and how to avoid them? 如何通过代码在亮度上将蓝到黄渐变应用于位图 - How do I apply a blue to yellow gradient on a bitmap through code, brightness as factor 如何分解嵌套的 for 循环? - How do I factor out nested for loops? 如何有效地确定两个平行向量的比例因子? - How do I efficiently determine the scale factor of two parallel vectors? 在 SFML 中,如何在没有缩放因子的情况下应用转换? - In SFML, how do I apply a transformation without the scaling factor? 如何缓解嵌套循环? - How to ease nested looping? 如何在没有因子的情况下打印数字的因子数请查看我的代码 - How Can I print The number of Factor of a Number without there factors please have a look on my code 如何删除分支因子高达 30,40 的树 - How do I delete a tree with an inconsistant branching factor up to 30,40 如何正确计算使用单独链接的哈希表的负载因子? - How do I properly calculate the load factor of a hash table that uses separate chaining?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM