10
TESTING

“How could [the computer] pick up a picture of Ender’s brother and put it into the graphics in this Fairyland routine?” “Colonel Graff, I wasn’t there when it was programmed. All I know is that the computer’s never taken anyone to this place before.”
—Orson Scott Card
, Ender’s Game

Image

Many ways are available to you to test your software. The common thread running through all these testing methods is that each test provides some kind of input to your code and you evaluate the test’s output for suitability. The nature of the environment, the scope of the investigation, and the form of the evaluation vary widely among testing types. This chapter covers how to perform testing with a few different frameworks, but the material is extensible to other testing varieties. Before diving in, let’s take a quick survey of several kinds of testing.

Unit Tests

Unit tests verify that a focused, cohesive collection of code—a unit, such as a function or a class—behaves exactly as the programmer intended. Good unit tests isolate the unit being tested from its dependencies. Sometimes this can be hard to do: the unit might depend on other units. In such situations, you use mocks to stand in for these dependencies. Mocks are fake objects you use solely during testing to provide you with fine-grained control over how a unit’s dependencies behave during the test. Mocks can also record how a unit interacted with them, so you can test whether a unit is interacting with its dependencies as expected. You can also use mocks to simulate rare events, such as a system running out of memory, by programming them to throw an exception.

Integration Tests

Testing a collection of units together is called an integration test. Integration tests can also refer to testing interactions between software and hardware, which system programmers deal with often. Integration tests are an important layer on top of unit tests, because they ensure that the software you’ve written works together as a system. These tests complement, but don’t replace, unit tests.

Acceptance Tests

Acceptance tests ensure that your software meets all of your customers’ requirements. High-performing software teams can use acceptance tests to guide development. Once all of the acceptance tests pass, your software is deliverable. Because these acceptance tests become part of the code base, there is built-in protection against refactoring or feature regression, where you break an existing feature in the process of adding a new one.

Performance Tests

Performance tests evaluate whether software meets effectiveness requirements, such as speed of execution or memory/power consumption. Optimizing code is a fundamentally empirical exercise. You can (and should) have ideas about which parts of your code are causing performance bottlenecks but can’t know for sure unless you measure. Also, you cannot know whether the code changes you implement with the intent of optimizing are improving performance unless you measure again. You can use performance tests to instrument your code and provide relevant measures. Instrumentation is a technique for measuring product performance, detecting errors, and logging how a program executes. Sometimes customers have strict performance requirements (for example, computation cannot take more than 100 milliseconds or the system cannot allocate more than 1MB of memory). You can automate testing such requirements and make sure that future code changes don’t violate them.

Code testing can be an abstract, dry subject. To avoid this, the next section introduces an extended example that lends context to the discussion.

An Extended Example: Taking a Brake

Suppose you’re programming the software for an autonomous vehicle. Your team’s software is very complicated and involves hundreds of thousands of code lines. The entire software solution is composed of several binaries. To deploy your software, you must upload the binaries into a car (using a relatively time-consuming process). Making a change to your code, compiling, uploading, and executing it in a live vehicle takes several hours per iteration.

The monumental task of writing all the vehicle’s software is broken out into teams. Each team is responsible for a service, such as the steering wheel control, audio/video, or vehicle detection. Services interact with each other via a service bus, where each service publishes events. Other services subscribe to these events as needed. This design pattern is called a service bus architecture.

Your team is responsible for the autonomous braking service. The service must determine whether a collision is about to happen and, if so, tell the car to brake. Your service subscribes to two event types: the SpeedUpdate class, which tells you that the car’s speed has changed, and the CarDetected class, which tells you that some other car has been detected in front of you. Your system is responsible for publishing a BrakeCommand to the service bus whenever an imminent collision is detected. These classes appear in Listing 10-1.

struct SpeedUpdate {
  double velocity_mps;
};

struct CarDetected {
  double distance_m;
  double velocity_mps;
};

struct BrakeCommand {
  double time_to_collision_s;
};

Listing 10-1: The POD classes that your service interacts with

You’ll publish the BrakeCommand using a ServiceBus object that has a publish method:

struct ServiceBus {
  void publish(const BrakeCommand&);
  --snip--
};

The lead architect wants you to expose an observe method so you can subscribe to SpeedUpdate and CarDetected events on the service bus. You decide to build a class called AutoBrake that you’ll initialize in the program’s entry point. The AutoBrake class will keep a reference to the publish method of the service bus, and it will subscribe to SpeedUpdate and CarDetected events through its observe method, as in Listing 10-2.

template <typename T>
struct AutoBrake {
  AutoBrake(const T& publish);
  void observe(const SpeedUpdate&);
  void observe(const CarDetected&);
private:
  const T& publish;
  --snip--
};

Listing 10-2: The AutoBrake class, which provides the automatic braking service

Figure 10-1 summarizes the relationship between the service bus ServiceBus, the automatic braking system AutoBrake, and other services.

image

Figure 10-1: A high-level depiction of the interaction between services and the service bus

The service integrates into the car’s software, yielding something like the code in Listing 10-3.

--snip--
int main() {
  ServiceBus bus;
  AutoBrake auto_brake{ [&bus] (const auto& cmd) {
                          bus.publish(cmd); 
                      }
  };
  while (true) {  // Service bus's event loop
    auto_brake.observe(SpeedUpdate{ 10L }); 
    auto_brake.observe(CarDetected{ 250L, 25L }); 
  }
}

Listing 10-3: A sample entry point using your AutoBrake service

You construct an AutoBrake with a lambda that captures a reference to a ServiceBus . All the details of how AutoBrake decides when to brake are completely hidden from the other teams. The service bus mediates all interservice communication. You’ve simply passed any commands from the AutoBrake directly to the ServiceBus . Within the event loop, a ServiceBus can pass SpeedUpdate and CarDetected objects to the observe method on your auto_brake.

Implementing AutoBrake

The conceptually simple way to implement AutoBrake is to iterate among writing some code, compiling the production binary, uploading it to a car, and testing functionality manually. This approach is likely to cause program (and car) crashes and to waste a whole lot of time. A better approach is to write code, compile a unit-test binary, and run it in your desktop development environment. You can iterate among these steps more quickly; once you’re reasonably confident that the code you’ve written works as intended, you can do a manual test with a live car.

The unit-test binary will be a simple console application targeting the desktop operating system. In the unit-test binary, you’ll run a suite of unit tests that pass specific inputs into an AutoBrake and assert that it produces the expected results.

After consulting with your management team, you’ve collected the following requirements:

  • AutoBrake will consider the car’s initial speed zero.
  • AutoBrake should have a configurable sensitivity threshold based on how many seconds are forecast until a collision. The sensitivity must not be less than 1 second. The default sensitivity is 5 seconds.
  • AutoBrake must save the car’s speed in between SpeedUpdate observations.
  • Each time AutoBrake observes a CarDetected event, it must publish a BrakeCommand if a collision is forecasted in less time than the configured sensitivity threshold.

Because you have such a pristine requirements list, the next step is to try implementing the automatic braking service using test-driven development (TDD).

NOTE

Because this book is about C++ and not about physics, your AutoBrake only works when a car is directly in front of you.

Test-Driven Development

At some point in the history of unit-testing adoption, some intrepid software engineers thought, “If I know I’m going to write a bunch of unit tests for this class, why not write the tests first?” This manner of writing software, known as TDD, underpins one of the great religious wars in the software engineering community. Vim or Emacs? Tabs or spaces? To use TDD or not to use TDD? This book humbly abstains from weighing in on these questions. But we’ll use TDD because it fits so naturally into a unit-testing discussion.

Advantages of TDD

The process of writing a test that encodes a requirement before implementing the solution is the fundamental idea behind TDD. Proponents say that code written this way tends to be more modular, robust, clean, and well designed. Writing good tests is the best way to document your code for other developers. A good test suite is a fully working set of examples that never gets out of sync. It protects against regressions in functionality whenever you add new features.

Unit tests also serve as a fantastic way to submit bug reports by writing a unit test that fails. Once the bug is fixed, it will stay fixed because the unit test and the code that fixes the bug become part of the test suite.

Red-Green-Refactor

TDD practitioners have a mantra: red, green, refactor. Red is the first step, and it means to implement a failing test. This is done for several reasons, principal of which is to make sure you’re actually testing something. You might be surprised how common it is to accidentally design a test that doesn’t make any assertions. Next, you implement code that makes the test pass. No more, no less. This turns the test from red to green. Now that you have working code and a passing test, you can refactor your production code. To refactor means to restructure existing code without changing its functionality. For example, you might find a more elegant way to write the same code, replace your code with a third-party library, or rewrite your code to have better performance characteristics.

If you accidentally break something, you’ll know immediately because your test suite will tell you. Then you continue to implement the remainder of the class using TDD. You can work on the collision threshold next.

Writing a Skeleton AutoBrake Class

Before you can write tests, you need to write a skeleton class, which implements an interface but provides no functionality. It’s useful in TDD because you can’t compile a test without a shell of the class you’re testing.

Consider the skeleton AutoBrake class in Listing 10-4.

struct SpeedUpdate {
  double velocity_mps;
};

struct CarDetected {
  double distance_m;
  double velocity_mps;
};

struct BrakeCommand {
  double time_to_collision_s;
};

template <typename T>
struct AutoBrake {
  AutoBrake(const T& publish) : publish{ publish } { }
  void observe(const SpeedUpdate& cd) { } 
  void observe(const CarDetected& cd) { } 
  void set_collision_threshold_s(double x) { 
    collision_threshold_s = x;
  }
  double get_collision_threshold_s() const { 
    return collision_threshold_s;
  }
  double get_speed_mps() const { 
    return speed_mps;
  }
private:
  double collision_threshold_s;
  double speed_mps;
  const T& publish;
};

Listing 10-4: A skeleton AutoBrake class

The AutoBrake class has a single constructor that takes the template parameter publish , which you save off into a const member. One of the requirements states that you’ll invoke publish with a BrakeCommand. Using the template parameter T allows you to program generically against any type that supports invocation with a BrakeCommand. You provide two different observe functions: one for each kind of event you want to subscribe to ➋➌. Because this is just a skeleton class, no instructions are in the body. You just need a class that exposes the appropriate methods and compiles without error. Because the methods return void, you don’t even need a return statement.

You implement a setter and getter . These methods mediate interaction with the private member variable collision_threshold_s. One of the requirements implies a class invariant about valid values for collision_threshold_s. Because this value can change after construction, you can’t just use the constructor to establish a class invariant. You need a way to enforce this class invariant throughout the object’s lifetime. You can use the setter to perform validation before the class sets a member’s value. The getter allows you to read the value of collision_threshold_s without permitting modification. It enforces a kind of external constness.

Finally, you have a getter for speed_mps with no corresponding setter. This is similar to making speed_mps a public member, with the important difference that it would be possible to modify speed_mps from an external class if it were public.

Assertions: The Building Blocks of Unit Tests

A unit test’s most essential component is the assertion, which checks that some condition is met. If the condition isn’t met, the enclosing test fails.

Listing 10-5 implements an assert_that function that throws an exception with an error message whenever some Boolean statement is false.

#include <stdexcept>
constexpr void assert_that(bool statement, const char* message) {
  if (!statement) throw std::runtime_error{ message }; 
}

int main() {
  assert_that(1 + 2 > 2, "Something is profoundly wrong with the universe."); 
  assert_that(24 == 42, "This assertion will generate an exception."); 
}
--------------------------------------------------------------------------
terminate called after throwing an instance of 'std::runtime_error'
  what():  This assertion will generate an exception. 

Listing 10-5: A program illustrating assert_that (Output is from a binary compiled by GCC v7.1.1.)

The assert_that function checks whether the statement parameter is false, in which case it throws an exception with the message parameter . The first assertion checks that 1 + 2 > 2, which passes . The second assertion checks that 24 == 42, which fails and throws an uncaught exception .

Requirement: Initial Speed Is Zero

Consider the first requirement that the car’s initial speed is zero. Before implementing this functionality in AutoBrake, you need to write a unit test that encodes this requirement. You’ll implement the unit test as a function that creates an AutoBrake, exercises the class, and makes assertions about the results. Listing 10-6 contains a unit test that encodes the requirement that the initial speed is zero.

void initial_speed_is_zero() {
  AutoBrake auto_brake{ [](const BrakeCommand&) {} }; 
  assert_that(auto_brake.get_speed_mps() == 0L, "speed not equal 0"); 
}

Listing 10-6: A unit test encoding the requirement that the initial speed be zero

You first construct an AutoBrake with an empty BrakeCommand publish function . This unit test is only concerned with the initial value of AutoBrake for car speed. Because this unit test is not concerned with how or when AutoBrake publishes a BrakeCommand, you give it the simplest argument that will still compile.

NOTE

A subtle but important feature of unit tests is that if you don’t care about some dependency of the unit under test, you can just provide an empty implementation that performs some innocuous, default behavior. This empty implementation is sometimes called a stub.

In initial_speed_is_zero, you only want to assert that the initial speed of the car is zero and nothing else . You use the getter get_speed_mps and compare the return value to 0. That’s all you have to do; assert will throw an exception if the initial speed is zero.

Now you need a way to run the unit tests.

Test Harnesses

A test harness is code that executes unit tests. You can make a test harness that will invoke your unit test functions, like initial_speed_is_zero, and handle failed assertions gracefully. Consider the test harness run_test in Listing 10-7.

#include <exception>
--snip--
void run_test(void(*unit_test)(), const char* name) {
  try {
    unit_test(); 
    printf("[+] Test %s successful.
", name); 
  } catch (const std::exception& e) {
    printf("[-] Test failure in %s. %s.
", name, e.what()); 
  }
}

Listing 10-7: A test harness

The run_test harness accepts a unit test as a function pointer named unit_test and invokes it within a try-catch statement . As long as unit_test doesn’t throw an exception, run_test will print a friendly message stating that the unit test passed before returning . If any exception is thrown, the test fails and prints a disapproving message .

To make a unit-test program that will run all of your unit tests, you place the run_test test harness inside the main function of a new program. All together, the unit-test program looks like Listing 10-8.

#include <stdexcept>

struct SpeedUpdate {
  double velocity_mps;
};

struct CarDetected {
  double distance_m;
  double velocity_mps;
};

struct BrakeCommand {
  double time_to_collision_s;
};

template <typename T>
struct AutoBrake {
  --snip--
};

constexpr void assert_that(bool statement, const char* message) {
  if (!statement) throw std::runtime_error{ message };
}

void initial_speed_is_zero() {
  AutoBrake auto_brake{ [](const BrakeCommand&) {} };
  assert_that(auto_brake.get_speed_mps() == 0L, "speed not equal 0");
}

void run_test(void(*unit_test)(), const char* name) {
  try {
    unit_test();
    printf("[+] Test %s successful.
", name);
  } catch (const std::exception& e) {
    printf("[-] Test failure in %s. %s.
", name, e.what());
  }
}

int main() {
  run_test(initial_speed_is_zero, "initial speed is 0"); 
}
--------------------------------------------------------------------------
[-] Test failure in initial speed is 0. speed not equal 0. 

Listing 10-8: The unit-test program

When you compile and run this unit-test binary, you can see that the unit test initial_speed_is_zero fails with an informative message .

NOTE

Because the AutoBrake member speed_mps is uninitialized in Listing 10-8, this program has undefined behavior. It’s not actually certain that the test will fail. The solution, of course, is that you shouldn’t write programs with undefined behavior.

Getting the Test to Pass

To get initial_speed_is_zero to pass, all that’s required is to initialize speed_mps to zero in the constructor of AutoBrake:

template <typename T>
struct AutoBrake {
  AutoBrake(const T& publish) : speed_mps{}, publish{ publish } { }
  --snip--
};

Simply add the initialization to zero . Now, if you update, compile, and run the unit-test program in Listing 10-8, you’re greeted with more pleasant output:

[+] Test initial speed is 0 successful.
Requirement: Default Collision Threshold Is Five

The default collision threshold needs to be 5. Consider the unit test in Listing 10-9.

void initial_sensitivity_is_five() {
  AutoBrake auto_brake{ [](const BrakeCommand&) {} };
  assert_that(auto_brake.get_collision_threshold_s() == 5L,
              "sensitivity is not 5");
}

Listing 10-9: A unit test encoding the requirement that the initial speed be zero

You can insert this test into the test program, as shown in Listing 10-10.

--snip--
int main() {
  run_test(initial_speed_is_zero, "initial speed is 0");
  run_test(initial_sensitivity_is_five, "initial sensitivity is 5");
}
--------------------------------------------------------------------------
[+] Test initial speed is 0 successful.
[-] Test failure in initial sensitivity is 5. sensitivity is not 5.

Listing 10-10: Adding the initial-sensitivity-is-5 test to the test harness

As expected, Listing 10-10 reveals that initial_speed_is_zero still passes and the new test initial_sensitivity_is_five fails.

Now, make it pass. Add the appropriate member initializer to AutoBrake, as demonstrated in Listing 10-11.

template <typename T>
struct AutoBrake {
  AutoBrake(const T& publish)
    : collision_threshold_s{ 5 }, 
      speed_mps{},
      publish{ publish } { }
  --snip--
};

Listing 10-11: Updating AutoBrake to satisfy the collision threshold requirement

The new member initializer sets collision_threshold_s to 5. Recompiling the test program, you can see initial_sensitivity_is_five is now passing:

[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.

Next, handle the class invariant that the sensitivity must be greater than 1.

Requirement: Sensitivity Must Always Be Greater Than One

To encode the sensitivity validation errors using exceptions, you can build a test that expects an exception to be thrown when collision_threshold_s is set to a value less than 1, as Listing 10-12 shows.

void sensitivity_greater_than_1() {
  AutoBrake auto_brake{ [](const BrakeCommand&) {} };
  try {
    auto_brake.set_collision_threshold_s(0.5L); 
  } catch (const std::exception&) {
    return; 
  }
  assert_that(false, "no exception thrown"); 
}

Listing 10-12: A test encoding the requirement that sensitivity is always greater than 1

You expect the set_collision_threshold_s method of auto_brake to throw an exception when called with a value of 0.5 . If it does, you catch the exception and return immediately from the test . If set_collision_threshold_s doesn’t throw an exception, you fail an assertion with the message no exception thrown .

Next, add sensitivity_greater_than_1 to the test harness, as demonstrated in Listing 10-13.

--snip--
int main() {
  run_test(initial_speed_is_zero, "initial speed is 0");
  run_test(initial_sensitivity_is_five, "initial sensitivity is 5");
  run_test(sensitivity_greater_than_1, "sensitivity greater than 1"); 
}
--------------------------------------------------------------------------
[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.
[-] Test failure in sensitivity greater than 1. no exception thrown. 

Listing 10-13: Adding set_collision_threshold_s to the test harness

As expected, the new unit test fails .

You can implement validation that will make the test pass, as Listing 10-14 shows.

#include <exception>
--snip--
template <typename T>
struct AutoBrake {
  --snip--
  void set_collision_threshold_s(double x) {
    if (x < 1) throw std::exception{ "Collision less than 1." };
    collision_threshold_s = x;
  }
}

Listing 10-14: Updating the set_collision_threshold method of AutoBrake to validate its input

Recompiling and executing the unit-test suite turns the test green:

[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.
[+] Test sensitivity greater than 1 successful.

Next, you want to make sure that an AutoBrake saves the car’s speed in between each SpeedUpdate.

Requirement: Save the Car’s Speed Between Updates

The unit test in Listing 10-15 encodes the requirement that an AutoBrake saves the car’s speed.

void speed_is_saved() {
  AutoBrake auto_brake{ [](const BrakeCommand&) {} }; 
  auto_brake.observe(SpeedUpdate{ 100L }); 
  assert_that(100L == auto_brake.get_speed_mps(), "speed not saved to 100"); 
  auto_brake.observe(SpeedUpdate{ 50L });
  assert_that(50L == auto_brake.get_speed_mps(), "speed not saved to 50");
  auto_brake.observe(SpeedUpdate{ 0L });
  assert_that(0L == auto_brake.get_speed_mps(), "speed not saved to 0");
}

Listing 10-15: Encoding the requirement that an AutoBrake saves the car’s speed

After constructing an AutoBrake , you pass a SpeedUpdate with velocity_mps equal to 100 into its observe method . Next, you get the speed back from auto_brake using the get_speed_mps method and expect it is equal to 100 .

NOTE

As a general rule, you should have a single assertion per test. This test violates the strictest interpretation of this rule, but it’s not violating its spirit. All of the assertions are examining the same, cohesive requirement, which is that the speed is saved whenever a SpeedUpdate is observed.

You add the test in Listing 10-15 to the test harness in the usual way, as demonstrated in Listing 10-16.

--snip--
int main() {
  run_test(initial_speed_is_zero, "initial speed is 0");
  run_test(initial_sensitivity_is_five, "initial sensitivity is 5");
  run_test(sensitivity_greater_than_1, "sensitivity greater than 1");
  run_test(speed_is_saved, "speed is saved"); 
}
--------------------------------------------------------------------------
[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.
[+] Test sensitivity greater than 1 successful.
[-] Test failure in speed is saved. speed not saved to 100. 

Listing 10-16: Adding the speed-saving unit test into the test harness

Unsurprisingly, the new test fails . To make this test pass, you implement the appropriate observe function:

template <typename T>
struct AutoBrake {
  --snip--
  void observe(const SpeedUpdate& x) {
    speed_mps = x.velocity_mps; 
  }
};

You extract the velocity_mps from the SpeedUpdate and store it into the speed_mps member variable . Recompiling the test binary shows that the unit test now passes:

[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.
[+] Test sensitivity greater than 1 successful.
[+] Test speed is saved successful.

Finally, you require that AutoBrake can compute the correct time to collision and, if appropriate, publish a BrakeCommand using the publish function.

Requirement: AutoBrake Publishes a BrakeCommand When Collision Detected

The relevant equations for computing times to collision come directly from high school physics. First, you calculate your car’s relative velocity to the detected car:

image

If your relative velocity is constant and positive, the cars will eventually collide. You can compute the time to such a collision as follows:

image

If TimeCollision is greater than zero and less than or equal to collision_threshold_s, you invoke publish with a BrakeCommand. The unit test in Listing 10-17 sets the collision threshold to 10 seconds and then observes events that indicate a crash.

void alert_when_imminent() {
  int brake_commands_published{}; 
  AutoBrake auto_brake{
    [&brake_commands_published](const BrakeCommand&) {
      brake_commands_published++; 
  } };
  auto_brake.set_collision_threshold_s(10L); 
  auto_brake.observe(SpeedUpdate{ 100L }); 
  auto_brake.observe(CarDetected{ 100L, 0L }); 
  assert_that(brake_commands_published == 1, "brake commands published not
one"); 
}

Listing 10-17: Unit testing for brake events

Here, you initialize the local variable brake_commands_published to zero . This will keep track of the number of times that the publish callback is invoked. You pass this local variable by reference into the lambda used to construct your auto_brake . Notice that you increment brake_commands_published . Because the lambda captures by reference, you can inspect the value of brake_commands_published later in the unit test. Next, you set set_collision_threshold to 10 . You update the car’s speed to 100 meters per second , and then you detect a car 100 meters away traveling at 0 meters per second (it is stopped) . The AutoBrake class should determine that a collision will occur in 1 second. This should trigger a callback, which will increment brake_commands_published. The assertion ensures that the callback happens exactly once.

After adding to main, compile and run to yield a new red test:

[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.
[+] Test sensitivity greater than 1 successful.
[+] Test speed is saved successful.
[-] Test failure in alert when imminent. brake commands published not one.

You can implement the code to make this test pass. Listing 10-18 provides all the code needed to issue brake commands.

template <typename T>
struct AutoBrake {
  --snip--
  void observe(const CarDetected& cd) {
    const auto relative_velocity_mps = speed_mps - cd.velocity_mps; 
    const auto time_to_collision_s = cd.distance_m / relative_velocity_mps; 
    if (time_to_collision_s > 0 &&  
        time_to_collision_s <= collision_threshold_s ) {
      publish(BrakeCommand{ time_to_collision_s }); 
    }
  }
};

Listing 10-18: Code implementing the braking functionality

First, you calculate the relative velocity . Next, you use this value to compute the time to collision . If this value is positive and less than or equal to the collision threshold , you publish a BrakeCommand .

Recompiling and running the unit-test suite yields success:

[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.
[+] Test sensitivity greater than 1 successful.
[+] Test speed is saved successful.
[+] Test alert when imminent successful.

Finally, you need to check that the AutoBrake will not invoke publish with a BrakeCommand if a collision will occur later than collision_threshold_s. You can repurpose the alert_when_imminent unit test, as in Listing 10-19.

void no_alert_when_not_imminent() {
  int brake_commands_published{};
  AutoBrake auto_brake{
    [&brake_commands_published](const BrakeCommand&) {
      brake_commands_published++;
  } };
  auto_brake.set_collision_threshold_s(2L);
  auto_brake.observe(SpeedUpdate{ 100L });
  auto_brake.observe(CarDetected{ 1000L, 50L });
  assert_that(brake_commands_published == 0 , "brake command published");
}

Listing 10-19: Testing that the car doesn’t issue a BrakeCommand if a collision isn’t anticipated within the collision threshold

This changes the setup. Your car’s threshold is set to 2 seconds with a speed of 100 meters per second. A car is detected 1,000 meters away traveling 50 meters per second. The AutoBrake class should forecast a collision in 20 seconds, which is more than the 2-second threshold. You also change the assertion .

After adding this test to main and running the unit-test suite, you have the following:

[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.
[+] Test sensitivity greater than 1 successful.
[+] Test speed is saved successful.
[+] Test alert when imminent successful.
[+] Test no alert when not imminent successful. 

For this test case, you already have all the code needed for this test to pass . Not having a failing test at the outset bends the red, green, refactor mantra, but that’s okay. This test case is closely related to alert_when_imminent. The point of TDD is not dogmatic adherence to strict rules. TDD is a set of reasonably loose guidelines that helps you write better software.

Adding a Service-Bus Interface

The AutoBrake class has a few dependencies: CarDetected, SpeedUpdated, and a generic dependency on some publish object callable with a single BrakeCommand parameter. The CarDetected and SpeedUpdated classes are plain-old-data types that are easy to use directly in your unit tests. The publish object is a little more complicated to initialize, but thanks to lambdas, it’s really not bad.

Suppose you want to refactor the service bus. You want to accept a std::function to subscribe to each service, as in the new IServiceBus interface in Listing 10-20.

#include <functional>

using SpeedUpdateCallback = std::function<void(const SpeedUpdate&)>;
using CarDetectedCallback = std::function<void(const CarDetected&)>;

struct IServiceBus {
  virtual ~IServiceBus() = default;
  virtual void publish(const BrakeCommand&) = 0;
  virtual void subscribe(SpeedUpdateCallback) = 0;
  virtual void subscribe(CarDetectedCallback) = 0;
};

Listing 10-20: The IServiceBus interface

Because IServiceBus is an interface, you don’t need to know the implementation details. It’s a nice solution because it allows you to do your own wiring into the service bus. But there’s a problem. How do you test AutoBrake in isolation? If you try to use the production bus, you’re firmly in integration-test territory, and you want easy-to-configure, isolated unit tests.

Mocking Dependencies

Fortunately, you don’t depend on the implementation: you depend on the interface. You can create a mock class that implements the IServiceBus interface and use this within AutoBrake. A mock is a special implementation that you generate for the express purpose of testing a class that depends on the mock.

Now when you exercise AutoBrake in your unit tests, AutoBrake interacts with the mock rather than the production service bus. Because you have complete control over the mock’s implementation and the mock is a unit-test-specific class, you have major flexibility in how you can test classes that depend on the interface:

  • You can capture arbitrarily detailed information about how the mock gets called. This can include information about the parameters and the number of times the mock was called, for example.
  • You can perform arbitrary computation in the mock.

In other words, you have complete control over the inputs and the outputs of the dependency of AutoBrake. How does AutoBrake handle the case where the service bus throws an out-of-memory exception inside of a publish invocation? You can unit test that. How many times did AutoBrake register a callback for SpeedUpdates? Again, you can unit test that.

Listing 10-21 presents a simple mock class you can use for your unit tests.

struct MockServiceBus : IServiceBus {
  void publish(const BrakeCommand& cmd) override {
    commands_published++; 
    last_command = cmd; 
  }
  void subscribe(SpeedUpdateCallback callback) override {
    speed_update_callback = callback; 
  }
  void subscribe(CarDetectedCallback callback) override {
    car_detected_callback = callback; 
  }
  BrakeCommand last_command{};
  int commands_published{};
  SpeedUpdateCallback speed_update_callback{};
  CarDetectedCallback car_detected_callback{};
};

Listing 10-21: A definition of MockServiceBus

The publish method records the number of times a BrakeCommand is published and the last_command that was published . Each time AutoBrake publishes a command to the service bus, you’ll see updates to the members of MockServiceBus. You’ll see in a moment that this allows for some very powerful assertions about how AutoBrake behaved during a test. You save the callback functions used to subscribe to the service bus . This allows you to simulate events by manually invoking these callbacks on the mock object.

Now, you can turn your attention to refactoring AutoBrake.

Refactoring AutoBrake

Listing 10-22 updates AutoBrake with the minimum changes necessary to get the unit-test binary compiling again (but not necessarily passing!).

#include <exception>
--snip--
struct AutoBrake { 
  AutoBrake(IServiceBus& bus) 
    : collision_threshold_s{ 5 },
      speed_mps{} {
  }
  void set_collision_threshold_s(double x) {
    if (x < 1) throw std::exception{ "Collision less than 1." };
    collision_threshold_s = x;
  }
  double get_collision_threshold_s() const {
    return collision_threshold_s;
  }
  double get_speed_mps() const {
    return speed_mps;
  }
private:
  double collision_threshold_s;
  double speed_mps;
};

Listing 10-22: A refactored AutoBrake skeleton taking an IServiceBus reference

Notice that all the observe functions have been removed. Additionally, AutoBrake is no longer a template . Rather, it accepts an IServiceBus reference in its constructor .

You’ll also need to update your unit tests to get the test suite compiling again. One TDD-inspired approach is to comment out all the tests that are not compiling and update AutoBrake so all the failing unit tests pass. Then, one by one, uncomment each unit test. You reimplement each unit test using the new IServiceBus mock, then update AutoBrake so the tests pass.

Let’s give it a try.

Refactoring the Unit Tests

Because you’ve changed the way to construct an AutoBrake object, you’ll need to reimplement every test. The first three are easy: Listing 10-23 just plops the mock into the AutoBrake constructor.

void initial_speed_is_zero() {
  MockServiceBus bus{}; 
  AutoBrake auto_brake{ bus }; 
  assert_that(auto_brake.get_speed_mps() == 0L, "speed not equal 0");
}

void initial_sensitivity_is_five() {
  MockServiceBus bus{}; 
  AutoBrake auto_brake{ bus }; 
  assert_that(auto_brake.get_collision_threshold_s() == 5,
              "sensitivity is not 5");
}

void sensitivity_greater_than_1() {
  MockServiceBus bus{}; 
  AutoBrake auto_brake{ bus }; 
  try {
    auto_brake.set_collision_threshold_s(0.5L);
  } catch (const std::exception&) {
    return;
  }
  assert_that(false, "no exception thrown");
}

Listing 10-23: Reimplemented unit-test functions using the MockServiceBus

Because these three tests deal with functionality not related to the service bus, it’s unsurprising that you didn’t need to make any major changes to AutoBrake. All you need to do is create a MockServiceBus and pass it into the AutoBrake constructor . Running the unit-test suite, you have the following:

[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.
[+] Test sensitivity greater than 1 successful.

Next, look at the speed_is_saved test. The AutoBrake class no longer exposes an observe function, but because you’ve saved the SpeedUpdateCallback on the mock service bus, you can invoke the callback directly. If AutoBrake subscribed properly, this callback will update the car’s speed, and you’ll see the effects when you call the get_speed_mps method. Listing 10-24 contains the refactor.

void speed_is_saved() {
  MockServiceBus bus{};
  AutoBrake auto_brake{ bus };

  bus.speed_update_callback(SpeedUpdate{ 100L }); 
  assert_that(100L == auto_brake.get_speed_mps(), "speed not saved to 100"); 
  bus.speed_update_callback(SpeedUpdate{ 50L });
  assert_that(50L == auto_brake.get_speed_mps(), "speed not saved to 50");
  bus.speed_update_callback(SpeedUpdate{ 0L });
  assert_that(0L == auto_brake.get_speed_mps(), "speed not saved to 0");
}

Listing 10-24: Reimplemented speed_is_saved unit-test function using the MockServiceBus

The test didn’t change too much from the previous implementation. You invoke the speed_update_callback function stored on the mock bus . You make sure that the AutoBrake object updated the car’s speed correctly . Compiling and running the resulting unit-test suite results in the following output:

[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.
[+] Test sensitivity greater than 1 successful.
[-] Test failure in speed is saved. bad function call.

Recall that the bad function call message comes from the std::bad_function_call exception. This is expected: you still need to subscribe from AutoBrake, so std::function throws an exception when you invoke it.

Consider the approach in Listing 10-25.

struct AutoBrake {
  AutoBrake(IServiceBus& bus)
    : collision_threshold_s{ 5 },
    speed_mps{} {
    bus.subscribe([this](const SpeedUpdate& update) {
      speed_mps = update.velocity_mps;
    });
  }
  --snip--
}

Listing 10-25: Subscribing the AutoBrake to speed updates from the IServiceBus

Thanks to std::function, you can pass your callback into the subscribe method of bus as a lambda that captures speed_mps. (Notice that you don’t need to save a copy of bus.) Recompiling and running the unit-test suite yields the following:

[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.
[+] Test sensitivity greater than 1 successful.
[+] Test speed is saved successful.

Next, you have the first of the alert-related unit tests, no_alert_when_not_imminent. Listing 10-26 highlights one way to update this test with the new architecture.

void no_alert_when_not_imminent() {
  MockServiceBus bus{};
  AutoBrake auto_brake{ bus };
  auto_brake.set_collision_threshold_s(2L);
  bus.speed_update_callback(SpeedUpdate{ 100L }); 
  bus.car_detected_callback(CarDetected{ 1000L, 50L }); 
  assert_that(bus.commands_published == 0, "brake commands were published");
}

Listing 10-26: Updating the no_alert_when_not_imminent test with the IServiceBus

As in the speed_is_saved test, you invoke the callbacks on the bus mock to simulate events on the service bus ➊➋. Recompiling and running the unit-test suite results in an expected failure.

[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.
[+] Test sensitivity greater than 1 successful.
[+] Test speed is saved successful.
[-] Test failure in no alert when not imminent. bad function call.

You need to subscribe with CarDetectedCallback. You can add this into the AutoBus constructor, as demonstrated in Listing 10-27.

struct AutoBrake {
  AutoBrake(IServiceBus& bus)
    : collision_threshold_s{ 5 },
    speed_mps{} {
    bus.subscribe([this](const SpeedUpdate& update) {
      speed_mps = update.velocity_mps;
    });
    bus.subscribe([this, &bus](const CarDetected& cd) {
      const auto relative_velocity_mps = speed_mps - cd.velocity_mps;
      const auto time_to_collision_s = cd.distance_m / relative_velocity_mps;
      if (time_to_collision_s > 0 &&
          time_to_collision_s <= collision_threshold_s) {
        bus.publish(BrakeCommand{ time_to_collision_s }); 
      }
    });
  }
  --snip--
}

Listing 10-27: An updated AutoBrake constructor that wires itself into the service bus

All you’ve done is port over the original observe method corresponding to CarDetected events. The lambda captures this and bus by reference in the callback. Capturing this allows you to compute collision times, whereas capturing bus allows you to publish a BrakeCommand if the conditions are satisfied. Now the unit-test binary outputs the following:

[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.
[+] Test sensitivity greater than 1 successful.
[+] Test speed is saved successful.
[+] Test no alert when not imminent successful.

Finally, turn on the last test, alert_when_imminent, as displayed in Listing 10-28.

void alert_when_imminent() {
  MockServiceBus bus{};
  AutoBrake auto_brake{ bus };
  auto_brake.set_collision_threshold_s(10L);
  bus.speed_update_callback(SpeedUpdate{ 100L });
  bus.car_detected_callback(CarDetected{ 100L, 0L });
  assert_that(bus.commands_published == 1, "1 brake command was not published");
  assert_that(bus.last_command.time_to_collision_s == 1L,
              "time to collision not computed correctly."); 
}

Listing 10-28: Refactoring the alert_when_imminent unit test

In MockServiceBus, you actually saved the last BrakeCommand published to the bus into a member. In the test, you can use this member to verify that the time to collision was computed correctly. If a car is going 100 meters per second, it will take 1 second to hit a stationary car parked 100 meters away. You check that the BrakeCommand has the correct time to collision recorded by referring to the time_to_collision_s field on our mock bus .

Recompiling and rerunning, you finally have the test suite fully green again:

[+] Test initial speed is 0 successful.
[+] Test initial sensitivity is 5 successful.
[+] Test sensitivity greater than 1 successful.
[+] Test speed is saved successful.
[+] Test no alert when not imminent successful.
[+] Test alert when imminent successful.

Refactoring is now complete.

Reevaluating the Unit-Testing Solution

Looking back at the unit-testing solution, you can identify several components that have nothing to do with AutoBrake. These are general purpose unit-testing components that you could reuse in future unit tests. Recall the two helper functions created in Listing 10-29.

#include <stdexcept>
#include <cstdio>

void assert_that(bool statement, const char* message) {
  if (!statement) throw std::runtime_error{ message };
}

void run_test(void(*unit_test)(), const char* name) {
  try {
    unit_test();
    printf("[+] Test %s successful.
", name);
    return;
  } catch (const std::exception& e) {
    printf("[-] Test failure in %s. %s.
", name, e.what());
  }
}

Listing 10-29: An austere unit-testing framework

These two functions reflect two fundamental aspects of unit testing: making assertions and running tests. Rolling your own simple assert_that function and run_test harness works, but this approach doesn’t scale very well. You can do a lot better by leaning on a unit-testing framework.

Unit-Testing and Mocking Frameworks

Unit-testing frameworks provide commonly used functions and the scaffolding you need to tie your tests together into a user-friendly program. These frameworks provide a wealth of functionality that helps you create concise, expressive tests. This section offers a tour of several popular unit-testing and mocking frameworks.

The Catch Unit-Testing Framework

One of the most straightforward unit-testing frameworks, Catch by Phil Nash, is available at https://github.com/catchorg/Catch2/. Because it's a header-only library, you can set up Catch by downloading the single-header version and including it in each translation unit that contains unit-testing code.

NOTE

At press time, Catch’s latest version is 2.9.1.

Defining an Entry Point

Tell Catch to provide your test binary’s entry point with #define CATCH_CONFIG_MAIN. Together, the Catch unit-test suite starts as follows:

#define CATCH_CONFIG_MAIN
#include "catch.hpp"

That’s it. Within the catch.hpp header, it looks for the CATCH_CONFIG_MAIN preprocessor definition. When present, Catch will add in a main function so you don’t have to. It will automatically grab all the unit tests you’ve defined and wrap them with a nice harness.

Defining Test Cases

Earlier, in “Unit Tests” on page 282, you defined a separate function for each unit test. Then you would pass a pointer to this function as the first parameter to run_test. You passed the name of the test as the second parameter, which is a bit redundant because you’ve already provided a descriptive name for the function pointed to by the first argument. Finally, you had to implement your own assert function. Catch handles all of this ceremony implicitly. For each unit test, you use the TEST_CASE macro, and Catch handles all the integration for you.

Listing 10-30 illustrates how to build a trivial Catch unit test program.

#define CATCH_CONFIG_MAIN
#include "catch.hpp"

TEST_CASE("AutoBrake") { 
  // Unit test here
}
--------------------------------------------------------------------------
==========================================================================
test cases: 1 | 1 passed 
assertions: - none - 

Listing 10-30: A simple Catch unit-test program

The Catch entry point detects that you declared one test called AutoBrake . It also provides a warning that you haven’t made any assertions .

Making Assertions

Catch comes with a built-in assertion that features two distinct families of assertion macros: REQUIRE and CHECK. The difference between them is that REQUIRE will fail a test immediately, whereas CHECK will allow the test to run to completion (but still cause a failure). CHECK can be useful sometimes when groups of related assertions that fail lead the programmer down the right path of debugging problems. Also included are REQUIRE_FALSE and CHECK_FALSE, which check that the contained statement evaluates to false rather than true. In some situations, you might find this a more natural way to represent a requirement.

All you need to do is wrap a Boolean expression with the REQUIRE macro. If the expression evaluates to false, the assertion fails. You provide an assertion expression that evaluates to true if the assertion passes and false if it fails:

REQUIRE(assertion-expression);

Let’s look at how to combine REQUIRE with a TEST_CASE to build a unit test.

NOTE

Because it’s by far the most common Catch assertion, we’ll use REQUIRE here. Refer to the Catch documentation for more information.

Refactoring the initial_speed_is_zero Test to Catch

Listing 10-31 shows the initial_speed_is_zero test refactored to use Catch.

#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include <functional>

struct IServiceBus {
  --snip--
};

struct MockServiceBus : IServiceBus {
  --snip--
};

struct AutoBrake {
  --snip--
};
TEST_CASE("initial car speed is zero") {
  MockServiceBus bus{};
  AutoBrake auto_brake{ bus };
  REQUIRE(auto_brake.get_speed_mps() == 0); 
}

Listing 10-31: An initial_speed_is_zero unit test refactored to use Catch

You use the TEST_CASE macro to define a new unit test . The test is described by its sole parameter . Inside the body of the TEST_CASE macro, you proceed with the unit test. You also see the REQUIRE macro in action . To see how Catch handles failed tests, comment out the speed_mps member initializer to cause a failing test and observe the program’s output, as shown in Listing 10-32.

struct AutoBrake {
  AutoBrake(IServiceBus& bus)
    : collision_threshold_s{ 5 }/*,
    speed_mps{} */{ 
  --snip--
};

Listing 10-32: Intentionally commenting out the speed_mps member initializer to cause test failures (using Catch)

The appropriate member initializer is commented out, resulting in a test failure. Rerunning the Catch test suite in Listing 10-31 yields the output in Listing 10-33.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
catch_example.exe is a Catch v2.0.1 host application.
Run with -? for options

------------------------------------------------------------------------------
initial car speed is zero
------------------------------------------------------------------------------
c:usersjalospinosocatch-testmain.cpp(82)
..............................................................................

c:usersjalospinosocatch-testmain.cpp(85): FAILED:
  REQUIRE( auto_brake.get_speed_mps()L == 0 ) 
with expansion:
  -92559631349317830736831783200707727132248687965119994463780864.0 
  ==
  0

==============================================================================
test cases: 1 | 1 failed
assertions: 1 | 1 failed

Listing 10-33: The output from running the test suite after implementing Listing 10-31

This is vastly superior output to what you had produced in the home-grown unit-test suite. Catch tells you the exact line where the unit test failed and then prints this line for you . Next, it expands this line into the actual values encountered at runtime. You can see that the grotesque (uninitialized) value returned by get_speed_mps() is clearly not 0 . Compare this output to the output of the home-grown unit test; I think you’ll agree that there’s immediate value to using Catch.

Assertions and Exceptions

Catch also provides a special assertion called REQUIRE_THROWS. This macro requires that the contained expression throw an exception. To achieve similar functionality in the home-grown unit-test framework, consider this multiline monstrosity:

  try {
    auto_brake.set_collision_threshold_s(0.5L);
  } catch (const std::exception&) {
    return;
  }
  assert_that(false, "no exception thrown");

Other exception-aware macros are available as well. You can require that some expression evaluation not throw an exception using the REQUIRE_NOTHROW and CHECK_NOTHROW macros. You can also be specific about the type of the exception you expect to be thrown by using the REQUIRE_THROWS_AS and CHECK_THROWS_AS macros. These expect a second parameter describing the expected type. Their usages are similar to REQUIRE; you simply provide some expression that must throw an exception for the assertion to pass:

REQUIRE_THROWS(expression-to-evaluate);

If the expression-to-evaluate doesn’t throw an exception, the assertion fails.

Floating-Point Assertions

The AutoBrake class involves floating-point arithmetic, and we’ve been glossing over a potentially very serious problem with the assertions. Because floating-point numbers entail rounding errors, it’s not a good idea to check for equality using operator==. The more robust approach is to test whether the difference between floating-point numbers is arbitrarily small. With Catch, you can handle these situations effortlessly using the Approx class, as shown in Listing 10-34.

TEST_CASE("AutoBrake") {
  MockServiceBus bus{};
  AutoBrake auto_brake{ bus };
  REQUIRE(auto_brake.get_collision_threshold_s() == Approx(5L));
}

Listing 10-34: A refactor of the “initializes sensitivity to five” test using the Approx class

The Approx class helps Catch perform tolerant comparisons of floating-point values. It can exist on either side of a comparison expression. It has sensible defaults for how tolerant it is, but you have fine-grained control over the specifics (see the Catch documentation on epsilon, margin, and scale).

Fail

You can cause a Catch test to fail using the FAIL() macro. This can sometimes be useful when combined with conditional statements, as in the following:

if (something-bad) FAIL("Something bad happened.")

Use a REQUIRE statement if a suitable one is available.

Test Cases and Sections

Catch supports the idea of test cases and sections, which make common setup and teardown in your unit tests far easier. Notice that each of the tests has some repeated ceremony each time you construct an AutoBrake:

  MockServiceBus bus{};
  AutoBrake auto_brake{ bus };

There’s no need to repeat this code over and over again. Catch’s solution to this common setup is to use nested SECTION macros. You can nest SECTION macros within a TEST_CASE in the basic usage pattern, as demonstrated in Listing 10-35.

TEST_CASE("MyTestGroup") {
  // Setup code goes here 
  SECTION("MyTestA") { 
    // Code for Test A
  }
  SECTION("MyTestB") { 
    // Code for Test B
  }
}

Listing 10-35: An example Catch setup with nested macros

You can perform all of the setup once at the beginning of a TEST_CASE . When Catch sees SECTION macros nested within a TEST_CASE, it (conceptually) copies and pastes all the setup into each SECTION ➋➌. Each SECTION runs independently of the others, so generally any side effects on objects created in the TEST_CASE aren’t observed across SECTION macros. Further, you can embed a SECTION macro within another SECTION macro. This might be useful if you have a lot of setup code for a suite of closely related tests (although it may just make sense to split this suite into its own TEST_CASE).

Let’s look at how this approach simplifies the AutoBrake unit-test suite.

Refactoring the AutoBrake Unit Tests to Catch

Listing 10-36 refactors all the unit tests into a Catch style.

#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include <functional>
#include <stdexcept>

struct IServiceBus {
  --snip--
};

struct MockServiceBus : IServiceBus {
  --snip--
};

struct AutoBrake {
  --snip--
};

TEST_CASE("AutoBrake") {
  MockServiceBus bus{}; 
  AutoBrake auto_brake{ bus }; 

  SECTION("initializes speed to zero") {
    REQUIRE(auto_brake.get_speed_mps() == Approx(0));
  }

  SECTION("initializes sensitivity to five") {
    REQUIRE(auto_brake.get_collision_threshold_s() == Approx(5));
  }

  SECTION("throws when sensitivity less than one") {
    REQUIRE_THROWS(auto_brake.set_collision_threshold_s(0.5L));
  }

  SECTION("saves speed after update") {
    bus.speed_update_callback(SpeedUpdate{ 100L });
    REQUIRE(100L == auto_brake.get_speed_mps());
    bus.speed_update_callback(SpeedUpdate{ 50L });
    REQUIRE(50L == auto_brake.get_speed_mps());
    bus.speed_update_callback(SpeedUpdate{ 0L });
    REQUIRE(0L == auto_brake.get_speed_mps());
  }

  SECTION("no alert when not imminent") {
    auto_brake.set_collision_threshold_s(2L);
    bus.speed_update_callback(SpeedUpdate{ 100L });
    bus.car_detected_callback(CarDetected{ 1000L, 50L });
    REQUIRE(bus.commands_published == 0);
  }

  SECTION("alert when imminent") {
    auto_brake.set_collision_threshold_s(10L);
    bus.speed_update_callback(SpeedUpdate{ 100L });
    bus.car_detected_callback(CarDetected{ 100L, 0L });
    REQUIRE(bus.commands_published == 1);
    REQUIRE(bus.last_command.time_to_collision_s == Approx(1));
  }
}
------------------------------------------------------------------------------
==============================================================================
All tests passed (9 assertions in 1 test case)

Listing 10-36: Using the Catch framework to implement the unit tests

Here, TEST_CASE is renamed to AutoBrake to reflect its more generic purpose . Next, the body of the TEST_CASE begins with the common setup code that all the AutoBrake unit tests share ➋➌. Each of the unit tests has been converted into a SECTION macro . You name each of the sections and then place the test-specific code within the SECTION body. Catch will do all the work of stitching together the setup code with each of the SECTION bodies. In other words, you get a fresh AutoBrake each time: the order of the SECTIONS doesn’t matter here, and they’re totally independent.

Google Test

Google Test is another extremely popular unit-testing framework. Google Test follows the xUnit unit-testing framework tradition, so if you’re familiar with, for example, junit for Java or nunit for .NET, you’ll feel right at home using Google Test. One nice feature when you’re using Google Test is that the mocking framework Google Mocks was merged in some time ago.

Configuring Google Test

Google Test takes some time to get up and running. Unlike Catch, Google Test is not a header-only library. You must download it from https://github.com/google/googletest/, compile it into a set of libraries, and link those libraries into your test project as appropriate. If you use a popular desktop build system, such as GNU Make, Mac Xcode, or Visual Studio, some templates are available that you can use to start building the relevant libraries.

For more information about getting Google Test up and running, refer to the Primer available in the repository’s docs directory.

NOTE

At press time, Google Test’s latest version is 1.8.1. See this book’s companion source, available at https://ccc.codes, for one method of integrating Google Test into a Cmake build.

Within your unit-test project, you must perform two operations to set up Google Test. First, you must ensure that the included directory of your Google Test installation is in the header search path of your unit-test project. This allows you to use #include "gtest/gtest.h" within your tests. Second, you must instruct your linker to include gtest and gtest_main static libraries from your Google Test installation. Make sure that you link in the correct architecture and configuration settings for your computer.

NOTE

A common gotcha getting Google Test set up in Visual Studio is that the C/C++ > Code Generation > Runtime Library option for Google Test must match your project’s option. By default, Google Test compiles the runtime statically (that is, with the /MT or MTd options). This choice is different from the default, which is to compile the runtime dynamically (for example, with the /MD or /MDd options in Visual Studio).

Defining an Entry Point

Google Test will supply a main() function for you when you link gtest_main into your unit-test project. Think of this as Google Test’s analogy for Catch’s #define CATCH_CONFIG_MAIN; it will locate all the unit tests you’ve defined and roll them together into a nice test harness.

Defining Test Cases

To define test cases, all you need to do is provide unit tests using the TEST macro, which is quite similar to Catch’s TEST_CASE. Listing 10-37 illustrates the basic setup of a Google Test unit test.

#include "gtest/gtest.h" 

TEST(AutoBrake, UnitTestName) {
  // Unit test here 
}
--------------------------------------------------------------------------
Running main() from gtest_main.cc 
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from AutoBrake
[ RUN      ] AutoBrake.UnitTestName
[       OK ] AutoBrake.UnitTestName (0 ms)
[----------] 1 test from AutoBrake (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (1 ms total)
[  PASSED  ] 1 test. 

Listing 10-37: An example Google Test unit test

First, you include the gtest/gtest.h header . This pulls in all the definitions you need to define your unit tests. Each unit test starts with the TEST macro . You define each unit test with two labels: a test case name, which is AutoBrake and a test name, which is UnitTestName . These are roughly analogous to the TEST_CASE and SECTION names (respectively) in Catch. A test case contains one or many tests. Usually, you place tests together that share some a common theme. The framework will group the tests together, which can be useful for some of the more advanced uses. Different test cases can have tests with the same name.

You would put the code for your unit test within the braces . When you run the resulting unit-test binary, you can see that Google Test provides an entry point for you . Because you provided no assertions (or code that could throw an exception), your unit tests pass with flying colors .

Making Assertions

Assertions in Google Test are less magical than in Catch’s REQUIRE. Although they’re also macros, the Google Test assertions require a lot more work on the programmer’s part. Where REQUIRE will parse the Boolean expression and determine whether you’re testing for equality, a greater-than relationship, and so on, Google Test’s assertions don’t. You must pass in each component of the assertion separately.

There are many other options for formulating assertions in Google Test. Table 10-1 summarizes them.

Table 10-1: Google Test Assertions

Assertion

Verifies that

ASSERT_TRUE(condition)

condition is true.

ASSERT_FALSE(condition)

condition is false.

ASSERT_EQ(val1, val2)

val1 == val2 is true.

ASSERT_FLOAT_EQ(val1, val2)

val1 - val2 is a rounding error (float).

ASSERT_DOUBLE_EQ(val1, val2)

val1 - val2 is a rounding error (double).

ASSERT_NE(val1, val2)

val1 != val2 is true.

ASSERT_LT(val1, val2)

val1 < val2 is true.

ASSERT_LE(val1, val2)

val1 <= val2 is true.

ASSERT_GT(val1, val2)

val1 > val2 is true.

ASSERT_GE(val1, val2)

val1 >= val2 is true.

ASSERT_STREQ(str1, str2)

The two C-style strings str1 and str2 have the same content.

ASSERT_STRNE(str1, str2)

The two C-style strings str1 and str2 have different content.

ASSERT_STRCASEEQ(str1, str2)

The two C-style strings str1 and str2 have the same content, ignoring case.

ASSERT_STRCASENE(str1, str2)

The two C-style strings str1 and str2 have different content, ignoring case.

ASSERT_THROW(statement, ex_type)

The evaluating statement causes an exception of type ex_type to be thrown.

ASSERT_ANY_THROW(statement)

The evaluating statement causes an exception of any type to be thrown.

ASSERT_NO_THROW(statement)

The evaluating statement causes no exception to be thrown.

ASSERT_HRESULT_SUCCEEDED(statement)

The HRESULT returned by statement corresponds with a success (Win32 API only).

ASSERT_HRESULT_FAILED(statement)

The HRESULT returned by statement corresponds with a failure (Win32 API only).

Let’s combine a unit-test definition with an assertion to see Google Test in action.

Refactoring the initial_car_speed_is_zero Test to Google Test

With the intentionally broken AutoBrake in Listing 10-32, you can run the following unit test to see what the test harness’s failure messages look like. (Recall that you commented out the member initializer for speed_mps.) Listing 10-38 uses ASSERT_FLOAT_EQ to assert that the car’s initial speed is zero.

#include "gtest/gtest.h"
#include <functional>

struct IServiceBus {
  --snip--
};

struct MockServiceBus : IServiceBus {
  --snip--
};

struct AutoBrake {
  AutoBrake(IServiceBus& bus)
    : collision_threshold_s{ 5 }/*,
    speed_mps{} */ {
  --snip--
};
TEST(AutoBrakeTest, InitialCarSpeedIsZero) {
  MockServiceBus bus{};
  AutoBrake auto_brake{ bus };
  ASSERT_FLOAT_EQ(0, auto_brake.get_speed_mps());
}
--------------------------------------------------------------------------
Running main() from gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from AutoBrakeTest
[ RUN      ] AutoBrakeTest.InitialCarSpeedIsZero
C:UsersjoshAutoBrakegtest.cpp(80): error: Expected equality of these values:
  0 
  auto_brake.get_speed_mps()
    Which is: -inf
[  FAILED  ] AutoBrakeTest.InitialCarSpeedIsZero (5 ms)
[----------] 1 test from AutoBrakeTest (5 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (7 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] AutoBrakeTest.InitialCarSpeedIsZero

 1 FAILED TEST

Listing 10-38: Intentionally commenting out the collision_threshold_s member initializer to cause test failures (using Google Test)

You declare a unit test with the test case name AutoBrakeTest and test name InitialCarSpeedIsZero . Within the test, you set up the auto_brake and assert that the car’s initial speed is zero . Notice that the constant value is the first parameter and the quantity you’re testing is the second parameter .

Like the Catch output in Listing 10-33, the Google Test output in Listing 10-38 is very clear. It tells you that a test failed, identifies the failed assertion, and gives a good indication of how you might fix the issue.

Test Fixtures

Unlike Catch’s TEST_CASE and SECTION approach, Google Test’s approach is to formulate test fixture classes when a common setup is involved. These fixtures are classes that inherit from the ::testing::Test class that the framework provides.

Any members you plan to use inside tests you should mark as public or protected. If you want some setup or teardown computation, you can put it inside the (default) constructor or destructor (respectively).

NOTE

You can also place such setup and teardown logic in overridden SetUp() and TearDown() functions, although it’s rare that you would need to. One case is if the teardown computation might throw an exception. Because you generally shouldn’t allow an uncaught exception to throw from a destructor, you would have to put such code in a TearDown() function. (Recall from “Throwing in Destructors” on page 106 that throwing an uncaught exception in a destructor when another exception is already in flight calls std::terminate.)

If a test fixture is like a Catch TEST_CASE, then TEST_F is like a Catch SECTION. Like TEST, TEST_F takes two parameters. The first must be the exact name of the test fixture class. The second is the name of the unit test. Listing 10-39 illustrates the basic usage of Google Test’s test fixtures.

#include "gtest/gtest.h"

struct MyTestFixture : ::testing::Test { };

TEST_F(MyTestFixture, MyTestA) {
  // Test A here
}

TEST_F(MyTestFixture, MyTestB) {
  // Test B here
}
--------------------------------------------------------------------------
Running main() from gtest_main.cc
[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from MyTestFixture
[ RUN      ] MyTestFixture.MyTestA
[       OK ] MyTestFixture.MyTestA (0 ms)
[ RUN      ] MyTestFixture.MyTestB
[       OK ] MyTestFixture.MyTestB (0 ms)
[----------] 2 tests from MyTestFixture (1 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran. (3 ms total)
[  PASSED  ] 2 tests.

Listing 10-39: The basic setup of Google Test’s test fixtures

You declare a class MyTestFixture that inherits from the ::testing::Test class that Google Test provides . You use the class’s name as the first parameter to the TEST_F macro . The unit test then has access to any public or protected methods inside MyTestFixture, and you can use the constructor and destructor of MyTestFixture to perform any common test setup/teardown. The second argument is the name of the unit test ➍➎.

Next, let’s look at how to use Google Test Fixtures to reimplement the AutoBrake unit tests.

Refactoring AutoBrake Unit Tests with Google Test

Listing 10-40 reimplements all the AutoBrake unit tests into Google Test’s test-fixture framework.

#include "gtest/gtest.h"
#include <functional>

struct IServiceBus {
  --snip--
};

struct MockServiceBus : IServiceBus {
  --snip--
};

struct AutoBrake {
  --snip--
};

struct AutoBrakeTest : ::testing::Test { 
  MockServiceBus bus{};
  AutoBrake auto_brake { bus };
};

TEST_F(AutoBrakeTest, InitialCarSpeedIsZero) {
  ASSERT_DOUBLE_EQ(0, auto_brake.get_speed_mps()); 
}

TEST_F(AutoBrakeTest, InitialSensitivityIsFive) {
  ASSERT_DOUBLE_EQ(5, auto_brake.get_collision_threshold_s());
}

TEST_F(AutoBrakeTest, SensitivityGreaterThanOne) {
  ASSERT_ANY_THROW(auto_brake.set_collision_threshold_s(0.5L)); 
}

TEST_F(AutoBrakeTest, SpeedIsSaved) {
  bus.speed_update_callback(SpeedUpdate{ 100L });
  ASSERT_EQ(100, auto_brake.get_speed_mps());
  bus.speed_update_callback(SpeedUpdate{ 50L });
  ASSERT_EQ(50, auto_brake.get_speed_mps());
  bus.speed_update_callback(SpeedUpdate{ 0L });
  ASSERT_DOUBLE_EQ(0, auto_brake.get_speed_mps());
}

TEST_F(AutoBrakeTest, NoAlertWhenNotImminent) {
  auto_brake.set_collision_threshold_s(2L);
  bus.speed_update_callback(SpeedUpdate{ 100L });
  bus.car_detected_callback(CarDetected{ 1000L, 50L });
  ASSERT_EQ(0, bus.commands_published);
}

TEST_F(AutoBrakeTest, AlertWhenImminent) {
  auto_brake.set_collision_threshold_s(10L);
  bus.speed_update_callback(SpeedUpdate{ 100L });
  bus.car_detected_callback(CarDetected{ 100L, 0L });
  ASSERT_EQ(1, bus.commands_published);
  ASSERT_DOUBLE_EQ(1L, bus.last_command.time_to_collision_s);
}
--------------------------------------------------------------------------
Running main() from gtest_main.cc
[==========] Running 6 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 6 tests from AutoBrakeTest
[ RUN      ] AutoBrakeTest.InitialCarSpeedIsZero
[       OK ] AutoBrakeTest.InitialCarSpeedIsZero (0 ms)
[ RUN      ] AutoBrakeTest.InitialSensitivityIsFive
[       OK ] AutoBrakeTest.InitialSensitivityIsFive (0 ms)
[ RUN      ] AutoBrakeTest.SensitivityGreaterThanOne
[       OK ] AutoBrakeTest.SensitivityGreaterThanOne (1 ms)
[ RUN      ] AutoBrakeTest.SpeedIsSaved
[       OK ] AutoBrakeTest.SpeedIsSaved (0 ms)
[ RUN      ] AutoBrakeTest.NoAlertWhenNotImminent
[       OK ] AutoBrakeTest.NoAlertWhenNotImminent (1 ms)
[ RUN      ] AutoBrakeTest.AlertWhenImminent
[       OK ] AutoBrakeTest.AlertWhenImminent (0 ms)
[----------] 6 tests from AutoBrakeTest (3 ms total)

[----------] Global test environment tear-down
[==========] 6 tests from 1 test case ran. (4 ms total)
[  PASSED  ] 6 tests.

Listing 10-40: Using Google Test to implement the AutoBrake unit tests

First, you implement the test fixture AutoBrakeTest . This class encapsulates the common setup code across all the unit tests: to construct a MockServiceBus and use it to construct an AutoBrake. Each of the unit tests is represented by a TEST_F macro . These macros take two parameters: the test fixture, such as AutoBrakeTest , and the name of the test, such as InitialCarSpeedIsZero . Within the body of the unit tests, you have the correct invocations for each of the assertions, such as ASSERT_DOUBLE_EQ and ASSERT_ANY_THROW .

Comparing Google Test and Catch

As you’ve seen, several major differences exist between Google Test and Catch. The most striking initial impression should be your investment in installing Google Test and making it work correctly in your solution. Catch is on the opposite end of this spectrum: as a header-only library, it’s trivial to make it work in your project.

Another major difference is the assertions. To a newcomer, REQUIRE is a lot simpler to use than the Google Test assertion style. To a seasoned user of another xUnit framework, Google Test might seem more natural. The failure messages are also a bit different. It’s really up to you to determine which of these styles is more sensible.

Finally, there’s performance. Theoretically, Google Test will compile more quickly than Catch because all of Catch must be compiled for each translation unit in your unit-test suite. This is the trade-off for header-only libraries; the setup investment you make when setting up Google Test pays you back later with faster compilation. This might or might not be perceptible depending on the size of your unit-test suite.

Boost Test

Boost Test is a unit-testing framework that ships as part of the Boost C++ libraries (or simply Boost). Boost is an excellent collection of open source C++ libraries. It has a history of incubating many ideas that are eventually incorporated into the C++ standard, although not all Boost libraries aim for eventual inclusion. You’ll see mention of a number of Boost libraries throughout the remainder of this book, and Boost Test is the first. For help installing boost into your environment, see Boost’s home page https://www.boost.org or have a look at this book’s companion code.

NOTE

At press time, the latest version of the Boost libraries is 1.70.0.

You can use Boost Test in three modes: as a header-only library (like Catch), as a static library (like Google Test), or as a shared library, which will link the Boost Test module at runtime. The dynamic library usage can save quite a bit of disk space in the event you have multiple unit-test binaries. Rather than baking the unit-test framework into each of the unit-test binaries, you can build a single shared library (like a .so or .dll) and load it at runtime.

As you’ve discovered while exploring Catch and Google Test, trade-offs are involved with each of these approaches. A major advantage of Boost Test is that it allows you to choose the best mode as you see fit. It’s not terribly difficult to switch modes should a project evolve, so one possible approach is to begin using Boost Test as a header-only library and transition into another mode as requirements change.

Setting Up Boost Test

To set up Boost Test in the header-only mode (what Boost documentation calls the “single-header variant”), you simply include the <boost/test/included/unit_test.hpp> header. For this header to compile, you need to define BOOST_TEST_MODULE with a user-defined name. For example:

#define BOOST_TEST_MODULE test_module_name
#include <boost/test/included/unit_test.hpp>

Unfortunately, you cannot take this approach if you have more than one translation unit. For such situations, Boost Test contains prebuilt static libraries that you can use. By linking these in, you avoid having to compile the same code for every translation unit. When taking this approach, you include the boost/test/unit_test.hpp header for each translation unit in the unit-test suite:

#include <boost/test/unit_test.hpp>

In exactly one translation unit, you also include the BOOST_TEST_MODULE definition:

#define BOOST_TEST_MODULE AutoBrake
#include <boost/test/unit_test.hpp>

You must also configure the linker to include the appropriate Boost Test static library that comes with the Boost Test installation. The compiler and architecture corresponding to the selected static library must match the rest of your unit-test project.

Setting Up Shared Library Mode

To set up Boost Test in shared library mode, you must add the following lines to each translation unit of the unit-test suite:

#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>

In exactly one translation unit, you must also define BOOST_TEST_MODULE:

#define BOOST_TEST_MODULE AutoBrake
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>

As with the static library usage, you must instruct the linker to include Boost Test. At runtime, the unit-test shared library must be available as well.

Defining Test Cases

You can define a unit test in Boost Test with the BOOST_AUTO_TEST_CASE macro, which takes a single parameter corresponding to the name of the test. Listing 10-41 shows the basic usage.

#define BOOST_TEST_MODULE TestModuleName 
#include <boost/test/unit_test.hpp> 

BOOST_AUTO_TEST_CASE(TestA) {
  // Unit Test A here 
}
--------------------------------------------------------------------------
Running 1 test case...

*** No errors detected

Listing 10-41: Using Google Test to implement the AutoBrake unit tests

The test module’s name is TestModuleName , which you define as the BOOST_TEST_MODULE. You include the boost/test/unit_test.hpp header , which provides you with access to all the components you need from Boost Test. The BOOST_AUTO_TEST_CASE declaration denotes a unit test called TestA . The body of the unit test goes between the braces .

Making Assertions

Assertions in Boost are very similar to the assertions in Catch. The BOOST_TEST macro is like the REQUIRE macro in Catch. You simply provide an expression that evaluates to true if the assertion passes and false if it fails:

BOOST_TEST(assertion-expression)

To require an expression to throw an exception upon evaluation, use the BOOST_REQUIRE_THROW macro, which is similar to Catch’s REQUIRE_THROWS macro, except you must also provide the type of the exception you want thrown. Its usage is as follows:

BOOST_REQUIRE_THROW(expression, desired-exception-type);

If the expression doesn’t throw an exception of type desired-exception-type, the assertion will fail.

Let’s examine what the AutoBrake unit-test suite looks like using Boost Test.

Refactoring the initial_car_speed_is_zero Test to Boost Test

You’ll use the intentionally broken AutoBrake in Listing 10-32 with the missing member initializer for speed_mps. Listing 10-42 causes Boost Test to deal with a failed unit test.

#define BOOST_TEST_MODULE AutoBrakeTest 
#include <boost/test/unit_test.hpp>
#include <functional>

struct IServiceBus {
  --snip--
};

struct MockServiceBus : IServiceBus {
  --snip--
};

struct AutoBrake {
  AutoBrake(IServiceBus& bus)
    : collision_threshold_s{ 5 }/*,
      speed_mps{} */ {
  --snip--
};

BOOST_AUTO_TEST_CASE(InitialCarSpeedIsZero) {
  MockServiceBus bus{};
  AutoBrake auto_brake{ bus };
  BOOST_TEST(0 == auto_brake.get_speed_mps()); 
}
--------------------------------------------------------------------------
Running 1 test case...
C:/Users/josh/projects/cpp-book/manuscript/part_2/10-testing/samples/boost/
minimal.cpp(80): error: in "InitialCarSpeedIsZero": check 0 == auto_brake.
get_speed_mps() has failed [0 != -9.2559631349317831e+61] 
*** 1 failure is detected in the test module "AutoBrakeTest"

Listing 10-42: Intentionally commenting out the speed_mps member initializer to cause test failures (using Boost Test)

The test module name is AutoBrakeTest . After commenting out the speed_mps member initializer , you have the InitialCarSpeedIsZero test . The BOOST_TEST assertion tests whether speed_mps is zero . As with Catch and Google Test, you have an informative error message that tells you what went wrong .

Test Fixtures

Like Google Test, Boost Test deals with common setup code using the notion of test fixtures. Using them is as simple as declaring an RAII object where the setup logic for the test is contained in that class’s constructor and the teardown logic is contained in the destructor. Unlike Google Test, you don’t have to derive from a parent class in your test fixture. The test fixtures work with any user-defined structure.

To use the test fixture in a unit test, you employ the BOOST_FIXTURE_TEST_CASE macro, which takes two parameters. The first parameter is the name of the unit test, and the second parameter is the test fixture class. Within the body of the macro, you implement a unit test as if it were a method of the test fixture class, as demonstrated in Listing 10-43.

#define BOOST_TEST_MODULE TestModuleName
#include <boost/test/unit_test.hpp>

struct MyTestFixture { }; 

BOOST_FIXTURE_TEST_CASE(MyTestA, MyTestFixture) {
  // Test A here
}

BOOST_FIXTURE_TEST_CASE(MyTestB, MyTestFixture) {
  // Test B here
}
--------------------------------------------------------------------------
Running 2 test cases...

*** No errors detected

Listing 10-43: Illustrating Boost test fixture usage

Here, you define a class called MyTestFixture and use it as the second parameter for each instance of BOOST_FIXTURE_TEST_CASE . You declare two unit tests: MyTestA and MyTestB . Any setup you perform within MyTestFixture affects each BOOST_FIXTURE_TEST_CASE.

Next, you’ll use Boost Test fixtures to reimplement the AutoBrake test suite.

Refactoring AutoBrake Unit Tests with Boost Test

Listing 10-44 implements the AutoBrake unit-test suite using Boost Test’s test fixture.

#define BOOST_TEST_MODULE AutoBrakeTest
#include <boost/test/unit_test.hpp>
#include <functional>

struct IServiceBus {
  --snip--
};

struct MockServiceBus : IServiceBus {
  --snip--
};

struct AutoBrakeTest { 
  MockServiceBus bus{};
  AutoBrake auto_brake{ bus };
};

BOOST_FIXTURE_TEST_CASE(InitialCarSpeedIsZero, AutoBrakeTest) {
  BOOST_TEST(0 == auto_brake.get_speed_mps());
}
BOOST_FIXTURE_TEST_CASE(InitialSensitivityIsFive, AutoBrakeTest) {
  BOOST_TEST(5 == auto_brake.get_collision_threshold_s());
}

BOOST_FIXTURE_TEST_CASE(SensitivityGreaterThanOne, AutoBrakeTest) {
  BOOST_REQUIRE_THROW(auto_brake.set_collision_threshold_s(0.5L),
                      std::exception);
}

BOOST_FIXTURE_TEST_CASE(SpeedIsSaved, AutoBrakeTest) {
  bus.speed_update_callback(SpeedUpdate{ 100L });
  BOOST_TEST(100 == auto_brake.get_speed_mps());
  bus.speed_update_callback(SpeedUpdate{ 50L });
  BOOST_TEST(50 == auto_brake.get_speed_mps());
  bus.speed_update_callback(SpeedUpdate{ 0L });
  BOOST_TEST(0 == auto_brake.get_speed_mps());
}

BOOST_FIXTURE_TEST_CASE(NoAlertWhenNotImminent, AutoBrakeTest) {
  auto_brake.set_collision_threshold_s(2L);
  bus.speed_update_callback(SpeedUpdate{ 100L });
  bus.car_detected_callback(CarDetected{ 1000L, 50L });
  BOOST_TEST(0 == bus.commands_published);
}

BOOST_FIXTURE_TEST_CASE(AlertWhenImminent, AutoBrakeTest) {
  auto_brake.set_collision_threshold_s(10L);
  bus.speed_update_callback(SpeedUpdate{ 100L });
  bus.car_detected_callback(CarDetected{ 100L, 0L });
  BOOST_TEST(1 == bus.commands_published);
  BOOST_TEST(1L == bus.last_command.time_to_collision_s);
}
--------------------------------------------------------------------------
Running 6 test cases...

*** No errors detected

Listing 10-44: Using Boost Test to implement your unit tests

You define the test fixture class AutoBrakeTest to perform the setup of the AutoBrake and MockServiceBus . It’s identical to the Google Test test fixture except you didn’t need to inherit from any framework-issued parent classes. You represent each unit test with a BOOST_FIXTURE_TEST_CASE macro . The rest of the tests use the BOOST_TEST and BOOST_REQUIRE_THROW assertion macros; otherwise, the tests look very similar to Catch tests. Instead of TEST_CASE and SECTION elements, you have a test fixture class and BOOST_FIXTURE_TEST_CASE.

Summary: Testing Frameworks

Although three different unit-testing frameworks were presented in this section, dozens of high-quality options are available. None of them is universally superior. Most frameworks support the same basic set of features, whereas some of the more advanced features will have heterogeneous support. Mainly, you should select a unit-testing framework based on the style that makes you comfortable and productive.

Mocking Frameworks

The unit-testing frameworks you just explored will work in a wide range of settings. It would be totally feasible to build integration tests, acceptance tests, unit tests, and even performance tests using Google Test, for example. The testing frameworks support a broad range of programming styles, and their creators have only modest opinions about how you must design your software to make them testable.

Mocking frameworks are a bit more opinionated than unit-testing frameworks. Depending on the mocking framework, you must follow certain design guidelines for how classes depend on each other. The AutoBrake class used a modern design pattern called dependency injection. The AutoBrake class depends on an IServiceBus, which you injected using the constructor of AutoBrake. You also made IServiceBus an interface. Other methods for achieving polymorphic behavior exist (like templates), and each involves trade-offs.

All the mocking frameworks discussed in this section work extremely well with dependency injection. To varying degrees, the mocking frameworks remove the need to define your own mocks. Recall that you implemented a MockServiceBus to allow you to unit test AutoBrake, as displayed in Listing 10-45.

struct MockServiceBus : IServiceBus {
  void publish(const BrakeCommand& cmd) override {
    commands_published++;
    last_command = cmd;
  };
  void subscribe(SpeedUpdateCallback callback) override {
    speed_update_callback = callback;
  };
  void subscribe(CarDetectedCallback callback) override {
    car_detected_callback = callback;
  };
  BrakeCommand last_command{};
  int commands_published{};
  SpeedUpdateCallback speed_update_callback{};
  CarDetectedCallback car_detected_callback{};
};

Listing 10-45: Your hand-rolled MockServiceBus

Each time you want to add a unit test involving some new kind of interaction with IServiceBus, you’ll likely need to update your MockServiceBus class. This is tedious and error prone. Additionally, it’s not clear that you can share this mock class with other teams: you’ve implemented a lot of your own logic in it that won’t be very useful to, say, the tire-pressure-sensor team. Also, each test might have different requirements. Mocking frameworks enables you to define mock classes, often using macro or template voodoo. Within each unit test, you can customize the mock specifically for that test. This would be extremely difficult to do with a single mock definition.

This decoupling of the mock’s declaration from the mock’s test-specific definition is extremely powerful for two reasons. First, you can define different kinds of behavior for each unit test. This allows you to, for example, simulate exceptional conditions for some unit tests but not for others. Second, it makes the unit tests far more specific. By placing the custom mock’s behavior within a unit test rather than in a separate source file, it’s much clearer to the developer what the test is trying to achieve.

The net effect of using a mocking framework is that it makes mocking much less problematic. When mocking is easy, it makes good unit testing (and TDD) possible. Without mocking, unit testing can be very difficult; tests can be slow, unreliable, and brittle due to slow or error-prone dependencies. It’s generally preferable, for example, to use a mock database connection instead of a full-blown production instance while you’re trying to use TDD to implement new features into a class.

This section provides a tour of two mocking frameworks, Google Mock and HippoMocks, and includes a brief mention of two others, FakeIt and Trompeloeil. For technical reasons having to do with a lack of compile time code generation, creating a mocking framework is much harder in C++ than in most other languages, especially those with type reflection, a language feature that allows code to programmatically reason about type information. Consequently, there are a lot of high-quality mocking frameworks, each with their own trade-offs resulting from the fundamental difficulties associated with mocking C++.

Google Mock

One of the most popular mocking frameworks is the Google C++ Mocking Framework (or Google Mock), which is included as part of Google Test. It’s one of the oldest and most feature-rich mocking frameworks. If you’ve already installed Google Test, incorporating Google Mock is easy. First, make sure you include the gmock static library in your linker, as you did for gtest and gtest_main. Next, add #include "gmock/gmock.h".

If you’re using Google Test as your unit-testing framework, that’s all the setup you’ll need to do. Google Mock will work seamlessly with its sister library. If you’re using another unit-testing framework, you’ll need to provide the initialization code in the entry point of the binary, as shown in Listing 10-46.

#include "gmock/gmock.h"

int main(int argc, char** argv) {
  ::testing::GTEST_FLAG(throw_on_failure) = true; 
  ::testing::InitGoogleMock(&argc, argv); 
  // Unit test as usual, Google Mock is initialized
}

Listing 10-46: Adding Google Mock to a third-party unit-testing framework

The GTEST_FLAG throw_on_failure causes Google Mock to throw an exception when some mock-related assertion fails. The call to InitGoogleMock consumes the command line arguments to make any necessary customization (refer to the Google Mock documentation for more details).

Mocking an Interface

For each interface you need to mock, there is some unfortunate ceremony. You need to take each virtual function of the interface and transmute it into a macro. For non-const methods, you use MOCK_METHOD*, and for const methods, you use MOCK_CONST_METHOD*, replacing * with the number of parameters that the function takes. The first parameter of MOCK_METHOD is the name of the virtual function. The second parameter is the function prototype. For example, to make a mock IServiceBus, you would build the definition shown in Listing 10-47.

struct MockServiceBus : IServiceBus { 
  MOCK_METHOD1(publish, void(const BrakeCommand& cmd));
  MOCK_METHOD1(subscribe, void(SpeedUpdateCallback callback));
  MOCK_METHOD1(subscribe, void(CarDetectedCallback callback));
};

Listing 10-47: A Google Mock MockServiceBus

The beginning of the definition of MockServiceBus is identical to the definition of any other IServiceBus implementation . You then employ MOCK_METHOD three times . The first parameter is the name of the virtual function, and the second parameter is the prototype of the function.

It’s a bit tedious to have to generate these definitions on your own. There’s no additional information in the MockServiceBus definition that isn’t already available in the IServiceBus. For better or worse, this is one of the costs of using Google Mock. You can take the sting out of generating this boilerplate by using the gmock_gen.py tool included in the scripts/generator folder of the Google Mock distribution. You’ll need Python 2 installed, and it’s not guaranteed to work in all situations. See the Google Mock documentation for more information.

Now that you’ve defined a MockServiceBus, you can use it in your unit tests. Unlike the mock you defined on your own, you can configure a Google Mock specifically for each unit test. You have an incredible amount of flexibility in this configuration. The key to successful mock configuration is the use of appropriate expectations.

Expectations

An expectation is like an assertion for a mock object; it expresses the circumstances in which the mock expects to be called and what it should do in response. The “circumstances” are specified using objects called matchers. The “what it should do in response” part is called an action. The sections that follow will introduce each of these concepts.

Expectations are declared with the EXPECT_CALL macro. The first parameter to this macro is the mock object, and the second is the expected method call. This method call can optionally contain matchers for each parameter. These matchers help Google Mock decide whether a particular method invocation qualifies as an expected call. The format is as follows:

EXPECT_CALL(mock_object, method(matchers))

There are several ways to formulate assertions about expectations, and which you choose depends on how strict your requirements are for how the unit being tested interacts with the mock. Do you care whether your code calls mocked functions that you didn’t expect? It really depends on the application. That’s why there are three options: naggy, nice, and strict.

A naggy mock is the default. If a naggy mock’s function is called and no EXPECT_CALL matches the call, Google Mock will print a warning about an “uninteresting call,” but the test won’t fail just because of the uninteresting call. You can just add an EXPECT_CALL into the test as a quick fix to suppress the uninteresting call warning, because the call then ceases to be unexpected.

In some situations, there might be too many uninteresting calls. In such cases, you should use a nice mock. The nice mock won’t produce a warning about uninteresting calls.

If you’re very concerned about any interaction with the mock that you haven’t accounted for, you might use a strict mock. Strict mocks will fail the test if any call is made to the mock for which you don’t have a corresponding EXPECT_CALL.

Each of these types of mocks is a class template. The way to instantiate these classes is straightforward, as outlined in Listing 10-48.

MockServiceBus naggy_mock;
::testing::NiceMock<MockServiceBus> nice_mock;
::testing::StrictMock<MockServiceBus> strict_mock;

Listing 10-48: Three different styles of Google Mock

Naggy mocks are the default. Every ::testing::NiceMock and ::testing::StrictMock takes a single template parameter, the class of the underlying mock. All three of these options are perfectly valid first parameters to an EXPECT_CALL.

As a general rule, you should use nice mocks. Using naggy and strict mocks can lead to very brittle tests. When you’re using a strict mock, consider whether it’s really necessary to be so restrictive about the way the unit under test collaborates with the mock.

The second parameter to EXPECT_CALL is the name of the method you expect to be called followed by the parameters you expect the method to be called with. Sometimes, this is easy. Other times, there are more complicated conditions you want to express for what invocations match and don’t match. In such situations, you use matchers.

Matchers

When a mock’s method takes arguments, you have broad discretion over whether an invocation matches the expectation. In simple cases, you can use literal values. If the mock method is invoked with exactly the specified literal value, the invocation matches the expectation; otherwise, it doesn’t. On the other extreme, you can use Google Mock’s ::testing::_ object, which tells Google Mock that any value matches.

Suppose, for example, that you want to invoke publish, and you don’t care what the argument is. The EXPECT_CALL in Listing 10-49 would be appropriate.

--snip--
using ::testing::_; 

TEST(AutoBrakeTest, PublishIsCalled) {
  MockServiceBus bus;
  EXPECT_CALL(bus, publish(_));
  --snip--
}

Listing 10-49: Using the ::testing::_ matcher in an expectation

To make the unit test nice and tidy, you employ a using for ::testing::_. You use _ to tell Google Mock that any invocation of publish with a single argument will match .

A slightly more selective matcher is the class template ::testing::A, which will match only if a method is invoked with a particular type of parameter. This type is expressed as the template parameter to A, so A<MyType> will match only a parameter of type MyType. In Listing 10-50, the modification to Listing 10-49 illustrates a more restrictive expectation that requires a BrakeCommand as the parameter to publish.

--snip--
using ::testing::A; 

TEST(AutoBrakeTest, PublishIsCalled) {
  MockServiceBus bus;
  EXPECT_CALL(bus, publish(A<BrakeCommand>));
  --snip--
}

Listing 10-50: Using the ::testing::A matcher in an expectation

Again, you employ using and use A<BrakeCommand> to specify that only a BrakeCommand will match this expectation.

Another matcher, ::testing::Field, allows you to inspect fields on arguments passed to the mock. The Field matcher takes two parameters: a pointer to the field you want to expect and then another matcher to express whether the pointed-to field meets the criteria. Suppose you want to be even more specific about the call to publish : you want to specify that the time_to_collision_s is equal to 1 second. You can accomplish this task with the refactor of Listing 10-49 shown in Listing 10-51.

--snip--
using ::testing::Field; 
using ::testing::DoubleEq; 

TEST(AutoBrakeTest, PublishIsCalled) {
  MockServiceBus bus;
  EXPECT_CALL(bus, publish(Field(&BrakeCommand::time_to_collision_s,
                                 DoubleEq(1L))));
  --snip--
}

Listing 10-51: Using the Field matcher in an expectation

You employ using for Field and DoubleEq to clean up the expectation code a bit. The Field matcher takes a pointer to the field you’re interested in time_to_collision_s and the matcher that decides whether the field meets the criteria DoubleEq .

Many other matchers are available, and they’re summarized in Table 10-2. But refer to the Google Mock documentation for all the details about their usages.

Table 10-2: Google Mock Matchers

Matcher

Matches when argument is . . .

_

Any value of the correct type

A<type>)()

Of the given type

An<type>)()

Of the given type

Ge(value)

Greater than or equal to value

Gt(value)

Greater than value

Le(value)

Less than or equal to value

Lt(value)

Less than value

Ne(value)

Not equal to value

IsNull()

Null

NotNull()

Not null

Ref(variable)

A reference to variable

DoubleEq(variable)

A double value approximately equal to variable

FloatEq(variable)

A float value approximately equal to variable

EndsWith(str)

A string ending with str

HasSubstr(str)

A string containing the substring str

StartsWith(str)

A string starting with str

StrCaseEq(str)

A string equal to str (ignoring case)

StrCaseNe(str)

A string not equal to str (ignoring case)

StrEq(str)

A string equal to str

StrNeq(string)

A string not equal to str

NOTE

One beneficial feature of matchers is that you can use them as an alternate kind of assertion for your unit tests. The alternate macro is one of EXPECT_THAT(value, matcher) or ASSERT_THAT(value, matcher). For example, you could replace the assertion

ASSERT_GT(power_level, 9000);

with the more syntactically pleasing

ASSERT_THAT(power_level, Gt(9000));

You can use EXPECT_CALL with StrictMock to enforce how the unit under test interacts with the mock. But you might also want to specify how many times the mock should respond to calls. This is called the expectation’s cardinality.

Cardinality

Perhaps the most common method for specifying cardinality is Times, which specifies the number of times that a mock should expect to be called. The Times method takes a single parameter, which can be an integer literal or one of the functions listed in Table 10-3.

Table 10-3: A Listing of the Cardinality Specifiers in Google Mock

Cardinality

Specifies that a method will be called . . .

AnyNumber()

Any number of times

AtLeast(n)

At least n times

AtMost(n)

At most n times

Between(m, n)

Between m and n times

Exactly(n)

Exactly n times

Listing 10-52 elaborates Listing 10-51 to indicate that publish must be called only once.

--snip--
using ::testing::Field;
using ::testing::DoubleEq;

TEST(AutoBrakeTest, PublishIsCalled) {
  MockServiceBus bus;
  EXPECT_CALL(bus, publish(Field(&BrakeCommand::time_to_collision_s,
                                 DoubleEq(1L)))).Times(1);
  --snip--
}

Listing 10-52: Using the Times cardinality specifier in an expectation

The Times call ensures that publish gets called exactly once (regardless of whether you use a nice, strict, or naggy mock).

NOTE

Equivalently, you could have specified Times(Exactly(1)).

Now that you have some tools to specify the criteria and cardinality for an expected invocation, you can customize how the mock should respond to expectations. For this, you employ actions.

Actions

Like cardinalities, all actions are chained off EXPECT_CALL statements. These statements can help clarify how many times a mock expects to be called, what values to return each time it’s called, and any side effects (like throwing an exception) it should perform. The WillOnce and WillRepeatedly actions specify what a mock should do in response to a query. These actions can get quite complicated, but for brevity’s sake, this section covers two usages. First, you can use the Return construct to return values to the caller:

EXPECT_CALL(jenny_mock, get_your_number()) 
  .WillOnce(Return(8675309)) 
  .WillRepeatedly(Return(911));

You set up an EXPECT_CALL the usual way and then tag on some actions that specify what value the jenny_mock will return each time get_your_number is called . These are read sequentially from left to right, so the first action, WillOnce , specifies that the first time get_your_number is called, the value 8675309 is returned by jenny_mock. The next action, WillRepeatedly , specifies that for all subsequent calls, the value 911 will be returned.

Because IServiceBus doesn’t return any values, you’ll need the action to be a little more involved. For highly customizable behavior, you can use the Invoke construct, which enables you to pass an Invocable that will get called with the exact arguments passed into the mock’s method. Let’s say you want to save off a reference to the callback function that the AutoBrake registers via subscribe. You can do this easily with an Invoke, as illustrated in Listing 10-53.

CarDetectedCallback callback; 
EXPECT_CALL(bus, subscribe(A<CarDetectedCallback>()))
    .Times(1)
    .WillOnce(Invoke([&callback](const auto& callback_in) {
      callback = callback_in; 
    }));

Listing 10-53: Using Invoke to save off a reference to the subscribe callback registered by an AutoBrake

The first (and only) time that subscribe is called with a CarDetectedCallback, the WillOnce(Invoke(...)) action will call the lambda that’s been passed in as a parameter. This lambda captures the CarDetectedCallback declared by reference . By definition, the lambda has the same function prototype as the subscribe function, so you can use auto-type deduction to determine the correct type for callback_in (it’s CarDetectedCallback). Finally, you assign callback_in to callback . Now, you can pass events off to whoever subscribes simply by invoking your callback . The Invoke construct is the Swiss Army Knife of actions, because you get to execute arbitrary code with full information about the invocation parameters. Invocation parameters are the parameters that the mocked method received at runtime.

Putting It All Together

Reconsidering our AutoBrake testing suite, you can reimplement the Google Test unit-test binary to use Google Mock rather than the hand-rolled mock, as demonstrated in Listing 10-54.

#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include <functional>

using ::testing::_;
using ::testing::A;
using ::testing::Field;
using ::testing::DoubleEq;
using ::testing::NiceMock;
using ::testing::StrictMock;
using ::testing::Invoke;

struct NiceAutoBrakeTest : ::testing::Test { 
  NiceMock<MockServiceBus> bus;
  AutoBrake auto_brake{ bus };
};

struct StrictAutoBrakeTest : ::testing::Test { 
  StrictAutoBrakeTest() {
    EXPECT_CALL(bus, subscribe(A<CarDetectedCallback>())) 
      .Times(1)
      .WillOnce(Invoke([this](const auto& x) {
        car_detected_callback = x;
      }));
    EXPECT_CALL(bus, subscribe(A<SpeedUpdateCallback>())) 
      .Times(1)
      .WillOnce(Invoke([this](const auto& x) {
        speed_update_callback = x;
      }));;
  }
  CarDetectedCallback car_detected_callback;
  SpeedUpdateCallback speed_update_callback;
  StrictMock<MockServiceBus> bus;
};

TEST_F(NiceAutoBrakeTest, InitialCarSpeedIsZero) {
  ASSERT_DOUBLE_EQ(0, auto_brake.get_speed_mps());
}

TEST_F(NiceAutoBrakeTest, InitialSensitivityIsFive) {
  ASSERT_DOUBLE_EQ(5, auto_brake.get_collision_threshold_s());
}

TEST_F(NiceAutoBrakeTest, SensitivityGreaterThanOne) {
  ASSERT_ANY_THROW(auto_brake.set_collision_threshold_s(0.5L));
}

TEST_F(StrictAutoBrakeTest, NoAlertWhenNotImminent) {
  AutoBrake auto_brake{ bus };

  auto_brake.set_collision_threshold_s(2L);
  speed_update_callback(SpeedUpdate{ 100L });
  car_detected_callback(CarDetected{ 1000L, 50L });
}

TEST_F(StrictAutoBrakeTest, AlertWhenImminent) {
  EXPECT_CALL(bus, publish(
                       Field(&BrakeCommand::time_to_collision_s, DoubleEq{ 1L
}))
                   ).Times(1);
  AutoBrake auto_brake{ bus };
  auto_brake.set_collision_threshold_s(10L);
  speed_update_callback(SpeedUpdate{ 100L });
  car_detected_callback(CarDetected{ 100L, 0L });
}

Listing 10-54: Reimplementing your unit tests using a Google Mock rather than a roll-your-own mock

Here, you actually have two different test fixtures: NiceAutoBrakeTest and StrictAutoBrakeTest . The NiceAutoBrakeTest test instantiates a NiceMock. This is useful for InitialCarSpeedIsZero, InitialSensitivityIsFive, and SensitivityGreaterThanOne, because you don’t want to test any meaningful interactions with the mock; it’s not the focus of these tests. But you do want to focus on AlertWhenImminent and NoAlertWhenNotImminent. Each time an event is published or a type is subscribed to, it could have potentially major ramifications on your system. The paranoia of a StrictMock here is warranted.

In the StrictAutoBrakeTest definition, you can see the WillOnce/Invoke approach to saving off the callbacks for each subscription ➌➍. These are used in AlertWhenImminent and NoAlertWhenNotImminent to simulate events coming off the service bus. It gives the unit tests a nice, clean, succinct feel, even though there’s a lot of mocking logic going on behind the scenes. Remember, you don’t even require a working service bus to do all this testing!

HippoMocks

Google Mock is one of the original C++ mocking frameworks, and it’s still a mainstream choice today. HippoMocks is an alternative mocking framework created by Peter Bindels. As a header-only library, HippoMocks is trivial to install. Simply pull down the latest version from GitHub (https://github.com/dascandy/hippomocks/). You must include the "hippomocks.h" header in your tests. HippoMocks will work with any testing framework.

NOTE

At press time, the latest version of HippoMocks is v5.0.

To create a mock using HippoMocks, you start by instantiating a MockRespository object. By default, all the mocks derived from this MockRepository will require strict ordering of expectations. Strictly ordered expectations cause a test to fail if each of the expectations is not invoked in the exact order you’ve specified. Usually, this is not what you want. To modify this default behavior, set the autoExpect field on MockRepository to false:

MockRepository mocks;
mocks.autoExpect = false;

Now you can use MockRepository to generate a mock of IServiceBus. This is done through the (member) function template Mock. This function will return a pointer to your newly minted mock:

auto* bus = mocks.Mock<IServiceBus>();

A major selling point of HippoMocks is illustrated here: notice that you didn’t need to generate any macro-laden boilerplate for the mock IServiceBus like you did for Google Mock. The framework can handle vanilla interfaces without any further effort on your part.

Setting up expectations is very straightforward as well. For this, use the ExpectCall macro on MockRespository. The ExpectCall macro takes two parameters: a pointer to your mock and a pointer to the method you’re expecting:

mocks.ExpectCall(bus, IServiceBus::subscribe_to_speed)

This example adds an expectation that bus.subscribe_to_speed will be invoked. You have several matchers you can add to this expectation, as summarized in Table 10-4.

Table 10-4: HippoMocks Matchers

Matcher

Specifies that an expectation matches when . . .

With(args)

The invocation parameters match args

Match(predicate)

predicate invoked with the invocation parameters returns true

After(expectation)

expectation has already been satisfied (This is useful for referring to a previously registered call.)

You can define actions to perform in response to ExpectCall, as summarized in Table 10-5.

Table 10-5: HippoMocks Actions

Action

Does the following upon invocation:

Return(value)

Returns value to the caller

Throw(exception)

Throws exception

Do(callable)

Executes callable with the invocation parameters

By default, HippoMocks requires an expectation to be met exactly once (like Google Mock’s .Times(1) cardinality).

For example, you can express the expectation that publish is called with a BrakeCommand having a time_to_collision_s of 1.0 in the following way:

mocks.ExpectCall(bus, IServiceBus::publish)
  .Match([](const BrakeCommand& cmd) {
    return cmd.time_to_collision_s == Approx(1); 
  });

You use ExpectCall to specify that bus should be called with the publish method . You refine this expectation with the Match matcher , which takes a predicate accepting the same arguments as the publish method—a single const BrakeCommand reference. You return true if the time_to_collision_s field of the BrakeCommand is 1.0; otherwise, you return false , which is fully compatible.

NOTE

As of v5.0, HippoMocks doesn’t have built-in support for approximate matchers. Instead, Catch’s Approx was used.

HippoMocks supports function overloads for free functions. It also supports overloads for methods, but the syntax is not very pleasing to the eye. If you are using HippoMocks, it is best to avoid method overloads in your interface, so it would be better to refactor IServiceBus along the following lines:

struct IServiceBus {
  virtual ~IServiceBus() = default;
  virtual void publish(const BrakeCommand&) = 0;
  virtual void subscribe_to_speed(SpeedUpdateCallback) = 0;
  virtual void subscribe_to_car_detected(CarDetectedCallback) = 0;
};

NOTE

One design philosophy states that it’s undesirable to have an overloaded method in an interface, so if you subscribe to that philosophy, the lack of support in HippoMocks is a moot point.

Now subscribe is no longer overloaded, and it’s possible to use HippoMocks. Listing 10-55 refactors the test suite to use HippoMocks with Catch.

#include "hippomocks.h"
--snip--
TEST_CASE("AutoBrake") {
  MockRepository mocks; 
  mocks.autoExpect = false;
  CarDetectedCallback car_detected_callback;
  SpeedUpdateCallback speed_update_callback;
  auto* bus = mocks.Mock<IServiceBus>();
  mocks.ExpectCall(bus, IServiceBus::subscribe_to_speed) 
    .Do([&](const auto& x) {
      speed_update_callback = x;
    });
  mocks.ExpectCall(bus, IServiceBus::subscribe_to_car_detected) 
    .Do([&](const auto& x) {
    car_detected_callback = x;
  });
  AutoBrake auto_brake{ *bus };

  SECTION("initializes speed to zero") {
    REQUIRE(auto_brake.get_speed_mps() == Approx(0));
  }

  SECTION("initializes sensitivity to five") {
    REQUIRE(auto_brake.get_collision_threshold_s() == Approx(5));
  }

  SECTION("throws when sensitivity less than one") {
    REQUIRE_THROWS(auto_brake.set_collision_threshold_s(0.5L));
  }

  SECTION("saves speed after update") {
    speed_update_callback(SpeedUpdate{ 100L }); 
    REQUIRE(100L == auto_brake.get_speed_mps());
    speed_update_callback(SpeedUpdate{ 50L });
    REQUIRE(50L == auto_brake.get_speed_mps());
    speed_update_callback(SpeedUpdate{ 0L });
    REQUIRE(0L == auto_brake.get_speed_mps());
  }

  SECTION("no alert when not imminent") {
    auto_brake.set_collision_threshold_s(2L);
    speed_update_callback(SpeedUpdate{ 100L }); 
    car_detected_callback(CarDetected{ 1000L, 50L });
  }

  SECTION("alert when imminent") {
    mocks.ExpectCall(bus, IServiceBus::publish) 
      .Match([](const auto& cmd) {
        return cmd.time_to_collision_s == Approx(1);
      });

    auto_brake.set_collision_threshold_s(10L);
    speed_update_callback(SpeedUpdate{ 100L });
    car_detected_callback(CarDetected{ 100L, 0L });
  }
}

Listing 10-55: Reimplementing Listing 10-54 to use HippoMocks and Catch rather than Google Mock and Google Test.

NOTE

This section couples HippoMocks with Catch for demonstration purposes, but HippoMocks works with all the unit-testing frameworks discussed in this chapter.

You create the MockRepository and relax the strict ordering requirements by setting autoExpect to false. After declaring the two callbacks, you create an IServiceBusMock (without having to define a mock class!), and then set expectations ➋➌ that will hook up your callback functions with AutoBrake. Finally, you create auto_brake using a reference to the mock bus.

The initializes speed to zero, initializes sensitivity to five, and throws when sensitivity less than one tests require no further interaction with the mock. In fact, as a strict mock, bus won’t let any further interactions happen without complaining. Because HippoMocks doesn’t allow nice mocks like Google Mock, this is actually a fundamental difference between Listing 10-54 and Listing 10-55.

In the saves speed after update test , you issue a series of speed_update callbacks and assert that the speeds are saved off correctly as before. Because bus is a strict mock, you’re also implicitly asserting that no further interaction happens with the service bus here.

In the no alert when not imminent test, no changes are needed to speed_update_callback . Because the mock is strict (and you don’t expect a BrakeCommand to get published), no further expectations are needed.

NOTE

HippoMocks offers the NeverCall method on its mocks, which will improve the clarity of your tests and errors if it’s called.

However, in the alert when imminent test, you expect that your program will invoke publish on a BrakeCommand, so you set up this expectation . You use the Match matcher to provide a predicate that checks for time_to_collision_s to equal approximately 1. The rest of the test is as before: you send AutoBrake a SpeedUpdate event and a subsequent CarDetected event that should cause a collision to be detected.

HippoMocks is a more streamlined mocking framework than Google Mock is. It requires far less ceremony, but it’s a little less flexible.

NOTE

One area where HippoMocks is more flexible than Google Mock is in mocking free functions. HippoMocks can mock free functions and static class functions directly, whereas Google Mock requires you to rewrite the code to use an interface.

A Note on Other Mocking Options: FakeIt and Trompeloeil

A number of other excellent mocking frameworks are available. But for the sake of keeping an already long chapter from getting much longer, let’s briefly look at two more frameworks: FakeIt (by Eran Pe’er, available at https://github.com/eranpeer/FakeIt/) and Trompeloeil (by Björn Fahller, available at https://github.com/rollbear/trompeloeil/).

FakeIt is similar to HippoMocks in its succinct usage patterns, and it’s a header-only library. It differs in that it follows the record-by-default pattern in building expectations. Rather than specifying expectations up front, FakeIt verifies that a mock’s methods were invoked correctly at the end of the test. Actions, of course, are still specified at the beginning.

Although this is a totally valid approach, I prefer the Google Mock/HippoMocks approach of specifying expectations—and their associated actions—all up front in one concise location.

Trompeloeil (from the French trompe-l’œil for “deceive the eye”) can be considered a modern replacement for Google Mock. Like Google Mock, it requires some macro-laden boilerplate for each of the interfaces you want to mock. In exchange for this extra effort, you gain many powerful features, including actions, such as setting test variables, returning values based on invocation parameters, and forbidding particular invocations. Like Google Mock and HippoMocks, Trompeloeil requires you to specify your expectations and actions up front (see the documentation for more details).

Summary

This chapter used an extended example of building the automatic braking system for an autonomous vehicle to explore the basics of TDD. You rolled your own testing and mocking framework, then learned about the many benefits of using available testing and mocking frameworks. You toured Catch, Google Test, and Boost Test as possible testing frameworks. For mocking frameworks, you dove into Google Mock and HippoMocks (with a brief mention of FakeIt and Trompeloeil). Each of these frameworks has strengths and weaknesses. Which you choose should be driven principally by which frameworks make you most efficient and productive.

NOTE

For the remainder of the book, examples will be couched in terms of unit tests. Accordingly, I had to choose a framework for the examples. I’ve chosen Catch for a few reasons. First, Catch’s syntax is the most succinct, and it lends itself well to book form. In header-only mode, Catch compiles much quicker than Boost Test. This might be considered an endorsement of the framework (and it is), but it’s not my intention to discourage the use of Google Test, Boost Test, or any other testing framework. You should make such decisions after careful consideration (and hopefully some experimentation.)

EXERCISES

10-1. Your car company has completed work on a service that detects speed limits based on signage it observes on the side of the road. The speed-limit-detection team will publish objects of the following type to the event bus periodically:

struct SpeedLimitDetected {
  unsigned short speed_mps;
}

The service bus has been extended to incorporate this new type:

#include <functional>
--snip--
using SpeedUpdateCallback = std::function<void(const SpeedUpdate&)>;
using CarDetectedCallback = std::function<void(const CarDetected&)>;
using SpeedLimitCallback = std::function<void(const SpeedLimitDetected&)>;

struct IServiceBus {
  virtual ~IServiceBus() = default;
  virtual void publish(const BrakeCommand&) = 0;
  virtual void subscribe(SpeedUpdateCallback) = 0;
  virtual void subscribe(CarDetectedCallback) = 0;
  virtual void subscribe(SpeedLimitCallback) = 0;
};

Update the service with the new interface and make sure the tests still pass.

10-2. Add a private field for the last known speed limit. Implement a getter method for this field.

10-3. The product owner wants you to initialize the last known speed limit to 39 meters per second. Implement a unit test that checks a newly constructed AutoBrake that has a last known speed limit of 39.

10-4. Make unit tests pass.

10-5. Implement a unit test where you publish three different SpeedLimitDetected objects using the same callback technique you used for SpeedUpdate and CarDetected. After invoking each of the callbacks, check the last known speed limit on the AutoBrake object to ensure it matches.

10-6. Make all unit tests pass.

10-7. Implement a unit test where the last known speed limit is 35 meters per second, and you’re traveling at 34 meters per second. Ensure that no BrakeCommand is published by AutoBrake.

10-8. Make all unit tests pass.

10-9. Implement a unit test where the last known speed limit is 35 meters per second and then publish a SpeedUpdate at 40 meters per second. Ensure that exactly one BrakeCommand is issued. The time_to_collision_s field should equal 0.

10-10. Make all unit tests pass.

10-11. Implement a new unit test where the last known speed limit is 35 meters per second and then publish a SpeedUpdate at 30 meters per second. Then issue a SpeedLimitDetected with a speed_mps of 25 meters per second. Ensure that exactly one BrakeCommand is issued. The time_to_collision_s field should equal 0.

10-12. Make all unit tests pass.

FURTHER READING

  • Specification by Example by Gojko Adzic (Manning, 2011)
  • BDD in Action by John Ferguson Smart (Manning, 2014)
  • Optimized C++: Proven Techniques for Heightened Performance by Kurt Guntheroth (O’Reilly, 2016)
  • Agile Software Development and Agile Principles, Patterns, and Practices in C# by Robert C. Martin (Prentice Hall, 2006)
  • Test-Driven Development: By Example by Kent Beck (Pearson, 2002)
  • Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce (Addison-Wesley, 2009)
  • “Editor war.” https://en.wikipedia.org/wiki/Editor_war
  • “Tabs versus Spaces: An Eternal Holy War” by Jamie Zawinski. https://www.jwz.org/doc/tabs-vs-spaces.html
  • “Is TDD dead?” by Martin Fowler. https://martinfowler.com/articles/is-tdd-dead/
..................Content has been hidden....................

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