Introduction

Welcome to the third lesson of the Increasing Code Test Coverage course! In our previous lessons, we explored the importance of code test coverage and how characterization tests can help document existing behavior. Now, we will focus on increasing testability by using abstract classes and mocks. This lesson will guide you through the process of decoupling dependencies, which is crucial for writing effective and reliable tests. By the end of this lesson, you'll understand how to refactor code to make it more testable and how to use the Google Mock library to create mock objects for testing.

Understanding the Problem

Testing code with tightly coupled dependencies can be challenging. When a class directly depends on external services, such as email or database services, it becomes difficult to isolate the code under test. This can lead to unreliable and non-repeatable tests. For instance, if our OrderProcessor class directly calls an EmailService to send confirmation emails, testing the OrderProcessor without actually sending emails becomes problematic. The goal is to isolate the code under test to ensure that our tests are reliable and repeatable, while not triggering side effects and not depending on logic that isn't being tested directly.

Introducing Abstract Classes

Abstract classes play a crucial role in decoupling dependencies in C++. By defining an abstract class, you create a contract that different implementations can adhere to. This allows you to substitute real implementations with test doubles, such as mocks or stubs, during testing. For example, instead of directly using an EmailService in our OrderProcessor, we can define an EmailServiceInterface abstract class. This abstract class can then be implemented by the EmailService class. Here's the EmailService in question:

This class can be adjusted to adhere to an abstract class that defines the contract for the relevant class:

By using the EmailServiceInterface abstract class, we can easily swap out the real EmailService with a mock during testing, allowing us to test the OrderProcessor without sending actual emails.

Implementing Mocks with Google Mock Library

Mocks are a type of test double that allows you to simulate the behavior of real objects. The Google Mock library is a popular tool for creating mock objects in C++. It enables you to define how a mock should behave and verify interactions with it. For example, we can use Google Mock to create a mock of the EmailServiceInterface and verify that the SendOrderConfirmation method is called during the ProcessOrder method of the OrderProcessor. Here's how you can set up a mock using Google Mock:

In this example, we create a mock of the EmailServiceInterface. This allows us to use any code that requires the EmailServiceInterface with full control over what happens when calling members of said interface.

Refactoring Code for Testability

Refactoring code to use abstract classes and mocks can significantly enhance testability. Let's walk through the process of refactoring the OrderProcessor class to accept an EmailServiceInterface abstract class. The initial implementation could look something like this:

With our abstract class defined above, we can now add a constructor that accepts the EmailServiceInterface as a parameter, while still maintaining backward compatibility for other parts of the system that rely on the current implementation:

Notice that we also changed the type used in the member variable within the OrderProcessor class. By using the EmailServiceInterface abstract class, the OrderProcessor can now work with any implementation of the interface, making it more flexible and easier to test.

Practical Application and Review

Now that we've covered the theory, it's time to put it into practice. In the upcoming exercises, we'll have the opportunity to apply these techniques to the provided code examples. We'll have an initial set of tests that help us confirm that you didn't break any existing behavior as we refactor. We will also add a new test to test a behavior previously not covered. Remember, the key points to focus on are decoupling dependencies using abstract classes and leveraging the power of mocks to simulate real objects during testing. This approach not only increases testability but also enhances the overall quality and reliability of your code. Good luck, and enjoy the exercises!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal