Miscellaneous Test Double Topics

In this final section, you’ll learn a few odds and ends about using test doubles, including generally accepted terminology, where to define them, whether to mock concrete classes, and their potential impact on performance.

What Do You Call Them?

So far, this chapter has used the terms test double, mock, and stub. Most of the TDD community has accepted common definitions for these terms plus a few others that you might find useful. You will often hear the word mock used in place of test double. Most of the time, that’s appropriate, since most developers use mock tools. Still, if you want to communicate more effectively, use the term most appropriate to your circumstance. xUnit Test Patterns [Mes07] acts as the definitive guide for these definitions.

Test double:

An element that emulates a production element for testing purposes

Stub:

A test double that returns hard-coded values

Spy:

A test double that captures information sent to it for later verification

Mock:

A test double that self-verifies based on expectations sent to it

Fake:

A test double that provides a light-weight implementation of a production class

Our handcrafted test double implementation for get acted as both a stub and a spy. It acted as a spy by verifying that the URL sent to it contained an accurate HTTP GET request URL. It acted as a stub by returning hard-coded JSON text. We turned it into a mock by using Google Mock to capture expectations and automatically verify whether they were met.

The canonical example for a fake is an in-memory database. Since interacting with file-system-based databases is inherently slow, many teams have implemented a test double class to emulate much of the interaction with the database. The underlying implementation is typically a hash-based structure that provides simple and rapid key-based lookup.

The challenge with fakes is that they become first-rate classes, often growing into complex implementations that contain their own defects. With a database fake, for example, you must properly replicate all the semantics of database interaction using a hash table implementation. It’s not impossible, but there are many easy mistakes to make.

Avoid fakes. You will otherwise undoubtedly waste half an afternoon at some point fixing a problem caused by a subtle defect in the fake. (I’ve wasted several such hours.) Your test suite exists to simplify and speed up development, not to waste time by creating problems of its own.

If you do employ fakes, you’ll want to ensure that they are themselves unit tested. Tests for the fake will need to prove that the logic matches behavior in the emulated object.

Where Do They Go?

Start by defining your test double within the same file as the tests that use it. Developers can then readily see the test double declaration that the tests use. Once multiple fixtures use the same test double, you will want to move the declaration to a separate header file. You should move your test doubles out of sight once they become so dumb that you never need to look at them again.

Remember that changes to the production interface will break tests that use derived test doubles. If you’re all on the same team and following collective code ownership guidelines (everyone has to right to change any code), the developer changing the production interface is responsible for running all tests and fixing any that break. In other circumstances, sending out a message that clearly communicates the change (and implications) is prudent.

Vtables and Performance

You introduce test doubles to support test-driving a class with a problematic dependency. Many techniques for creating test doubles involve creating derived types that override virtual member functions. If the production class previously contained no virtual methods, it now does and thus now contains a vtable. The vtable carries the overhead of an extra level of indirection.

The introduction of a vtable represents a performance concern, since C++ now requires an additional lookup into the vtable (instead of simply calling a function). Also, the compiler can no longer inline a virtual function.

But in most cases, the performance impact of vtables is negligible or even nonexistent. The compiler is able to optimize away some of the cost in certain cases. You’ll want the better, polymorphic design most of the time.

However, if you must call the mocked production function extensively, you will want to first profile performance. If the measured degradation is unacceptable, consider a different mocking form (perhaps a template-based solution), rework the design (and possibly recoup the performance loss by optimizing elsewhere), or introduce integration tests to compensate for the loss of the ability to unit test. Visit TDD and Performance for more discussion.

Mocking Concrete Classes

We created a mock by implementing the pure virtual Http interface. Many systems predominantly consist of concrete classes with few such interfaces. From a design stance, introducing interfaces can start to provide you with means of isolating one part of the system from another. The Dependency Inversion Principle (DIP)[29] promotes breaking dependencies by having clients depend on abstractions, not concrete implementations. Introducing these abstractions in the form of pure virtual classes can improve build times and isolate complexity. More importantly, they can make testing simpler.

You can, if you must, create a mock that derives from a concrete class. The problem is that the resulting class represents a mix of production and mocked behavior, a beast referred to as a partial mock. First, a partial mock is usually a sign that the class you’re mocking is too large—if you need to stub some of its elements but not all, you can likely split the class into two around these boundaries. Second, you will likely get into trouble when working with partial mocks. You can quickly end up in “mock hell.”

For example, if you were to mock CurlHttp directly by defining a derived class for it, you’d invoke its destructor by default. Is that a problem? Maybe, because it happens to directly interact with the cURL library. That’s probably not behavior you want your test to be exercising. In some cases, you can end up with devious defects: “Aha! I thought the code was interacting with the mock method at this point, but it looks like it’s interacting with the real method.” You don’t want to waste the kind of time often needed to get to the “aha!”

When you reach for a clever tool like a partial mock, your design is giving off smells. A cleaner design might, for example, adapt the concrete class with a class that derives from an interface. Tests would no longer require a partial mock, instead creating a test double for the interface.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.48.204