![](/user_photo/_userpic.png)
- •Table of Contents
- •About the Author
- •About the Technical Reviewer
- •Acknowledgments
- •Software Entropy
- •Clean Code
- •C++11: The Beginning of a New Era
- •Who This Book Is For
- •Conventions Used in This Book
- •Sidebars
- •Notes, Tips, and Warnings
- •Code Samples
- •Coding Style
- •C++ Core Guidelines
- •Companion Website and Source Code Repository
- •UML Diagrams
- •The Need for Testing
- •Unit Tests
- •What About QA?
- •Rules for Good Unit Tests
- •Test Code Quality
- •Unit Test Naming
- •Unit Test Independence
- •One Assertion per Test
- •Independent Initialization of Unit Test Environments
- •Exclude Getters and Setters
- •Exclude Third-Party Code
- •Exclude External Systems
- •What Do We Do with the Database?
- •Don’t Mix Test Code with Production Code
- •Tests Must Run Fast
- •How Do You Find a Test’s Input Data?
- •Equivalence Partitioning
- •Boundary Value Analysis
- •Test Doubles (Fake Objects)
- •What Is a Principle?
- •KISS
- •YAGNI
- •It’s About Knowledge!
- •Building Abstractions Is Sometimes Hard
- •Information Hiding
- •Strong Cohesion
- •Loose Coupling
- •Be Careful with Optimizations
- •Principle of Least Astonishment (PLA)
- •The Boy Scout Rule
- •Collective Code Ownership
- •Good Names
- •Names Should Be Self-Explanatory
- •Use Names from the Domain
- •Choose Names at an Appropriate Level of Abstraction
- •Avoid Redundancy When Choosing a Name
- •Avoid Cryptic Abbreviations
- •Avoid Hungarian Notation and Prefixes
- •Avoid Using the Same Name for Different Purposes
- •Comments
- •Let the Code Tell the Story
- •Do Not Comment Obvious Things
- •Don’t Disable Code with Comments
- •Don’t Write Block Comments
- •Don’t Use Comments to Substitute Version Control
- •The Rare Cases Where Comments Are Useful
- •Documentation Generation from Source Code
- •Functions
- •One Thing, No More!
- •Let Them Be Small
- •“But the Call Time Overhead!”
- •Function Naming
- •Use Intention-Revealing Names
- •Parameters and Return Values
- •Avoid Flag Parameters
- •Avoid Output Parameters
- •Don’t Pass or Return 0 (NULL, nullptr)
- •Strategies for Avoiding Regular Pointers
- •Choose simple object construction on the stack instead of on the heap
- •In a function’s argument list, use (const) references instead of pointers
- •If it is inevitable to deal with a pointer to a resource, use a smart one
- •If an API returns a raw pointer...
- •The Power of const Correctness
- •About Old C-Style in C++ Projects
- •Choose C++ Strings and Streams over Old C-Style char*
- •Use C++ Casts Instead of Old C-Style Casts
- •Avoid Macros
- •Managing Resources
- •Resource Acquisition Is Initialization (RAII)
- •Smart Pointers
- •Unique Ownership with std::unique_ptr<T>
- •Shared Ownership with std::shared_ptr<T>
- •No Ownership, but Secure Access with std::weak_ptr<T>
- •Atomic Smart Pointers
- •Avoid Explicit New and Delete
- •Managing Proprietary Resources
- •We Like to Move It
- •What Are Move Semantics?
- •The Matter with Those lvalues and rvalues
- •rvalue References
- •Don’t Enforce Move Everywhere
- •The Rule of Zero
- •The Compiler Is Your Colleague
- •Automatic Type Deduction
- •Computations During Compile Time
- •Variable Templates
- •Don’t Allow Undefined Behavior
- •Type-Rich Programming
- •Know Your Libraries
- •Take Advantage of <algorithm>
- •Easier Parallelization of Algorithms Since C++17
- •Sorting and Output of a Container
- •More Convenience with Ranges
- •Non-Owning Ranges with Views
- •Comparing Two Sequences
- •Take Advantage of Boost
- •More Libraries That You Should Know About
- •Proper Exception and Error Handling
- •Prevention Is Better Than Aftercare
- •No Exception Safety
- •Basic Exception Safety
- •Strong Exception Safety
- •The No-Throw Guarantee
- •An Exception Is an Exception, Literally!
- •If You Can’t Recover, Get Out Quickly
- •Define User-Specific Exception Types
- •Throw by Value, Catch by const Reference
- •Pay Attention to the Correct Order of Catch Clauses
- •Interface Design
- •Attributes
- •noreturn (since C++11)
- •deprecated (since C++14)
- •nodiscard (since C++17)
- •maybe_unused (since C++17)
- •Concepts: Requirements for Template Arguments
- •The Basics of Modularization
- •Criteria for Finding Modules
- •Focus on the Domain of Your Software
- •Abstraction
- •Choose a Hierarchical Decomposition
- •Single Responsibility Principle (SRP)
- •Single Level of Abstraction (SLA)
- •The Whole Enchilada
- •Object-Orientation
- •Object-Oriented Thinking
- •Principles for Good Class Design
- •Keep Classes Small
- •Open-Closed Principle (OCP)
- •A Short Comparison of Type Erasure Techniques
- •Liskov Substitution Principle (LSP)
- •The Square-Rectangle Dilemma
- •Favor Composition over Inheritance
- •Interface Segregation Principle (ISP)
- •Acyclic Dependency Principle
- •Dependency Inversion Principle (DIP)
- •Don’t Talk to Strangers (The Law of Demeter)
- •Avoid Anemic Classes
- •Tell, Don’t Ask!
- •Avoid Static Class Members
- •Modules
- •The Drawbacks of #include
- •Three Options for Using Modules
- •Include Translation
- •Header Importation
- •Module Importation
- •Separating Interface and Implementation
- •The Impact of Modules
- •What Is Functional Programming?
- •What Is a Function?
- •Pure vs Impure Functions
- •Functional Programming in Modern C++
- •Functional Programming with C++ Templates
- •Function-Like Objects (Functors)
- •Generator
- •Unary Function
- •Predicate
- •Binary Functors
- •Binders and Function Wrappers
- •Lambda Expressions
- •Generic Lambda Expressions (C++14)
- •Lambda Templates (C++20)
- •Higher-Order Functions
- •Map, Filter, and Reduce
- •Filter
- •Reduce (Fold)
- •Fold Expressions in C++17
- •Pipelining with Range Adaptors (C++20)
- •Clean Code in Functional Programming
- •The Drawbacks of Plain Old Unit Testing (POUT)
- •Test-Driven Development as a Game Changer
- •The Workflow of TDD
- •TDD by Example: The Roman Numerals Code Kata
- •Preparations
- •The First Test
- •The Second Test
- •The Third Test and the Tidying Afterward
- •More Sophisticated Tests with a Custom Assertion
- •It’s Time to Clean Up Again
- •Approaching the Finish Line
- •Done!
- •The Advantages of TDD
- •When We Should Not Use TDD
- •TDD Is Not a Replacement for Code Reviews
- •Design Principles vs Design Patterns
- •Some Patterns and When to Use Them
- •Dependency Injection (DI)
- •The Singleton Anti-Pattern
- •Dependency Injection to the Rescue
- •Adapter
- •Strategy
- •Command
- •Command Processor
- •Composite
- •Observer
- •Factories
- •Simple Factory
- •Facade
- •The Money Class
- •Special Case Object (Null Object)
- •What Is an Idiom?
- •Some Useful C++ Idioms
- •The Power of Immutability
- •Substitution Failure Is Not an Error (SFINAE)
- •The Copy-and-Swap Idiom
- •Pointer to Implementation (PIMPL)
- •Structural Modeling
- •Component
- •Interface
- •Association
- •Generalization
- •Dependency
- •Template and Template Binding
- •Behavioral Modeling
- •Activity Diagram
- •Action
- •Control Flow Edge
- •Other Activity Nodes
- •Sequence Diagram
- •Lifeline
- •Message
- •State Diagram
- •State
- •Transitions
- •External Transitions
- •Internal Transitions
- •Trigger
- •Stereotypes
- •Bibliography
- •Index
Chapter 2 Build a Safety Net
We can learn from our mistakes and get better. Professional developers remedy those quality deficits immediately by fixing the bugs found by QA, and by writing automated unit tests to catch them in the future. Then they should carefully think about this: “How could it happen that we’ve overlooked this issue?” The result of this retrospective should serve as feedback to improve the development process.
Rules for Good Unit Tests
I’ve seen many unit tests that are pretty unhelpful. Unit tests should add value to your project. To achieve this goal, you need to follow some essential rules, which I describe in this section.
Test Code Quality
The same high-quality requirements for the production code have to be valid for the unit test code. I’ll go even further. Ideally, there should be no distinction between production and test code—they are equal. If we say that there is production code on the one hand and test code on the other, we separate things that belong together. Don’t do that! Thinking about production and test code in two categories lays the foundation to neglect tests later in the project.
Unit Test Naming
If a unit test fails, the developer wants to know immediately:
•\ |
What is the name of the unit; whose test failed? |
•\ |
What was tested, and what was the environment of the test (the test |
|
scenario)? |
•\ |
What was the expected test result, and what was the actual test result |
|
of the failed test? |
Hence an expressive and descriptive naming standard for your unit tests is very important. My advice is to establish naming standards for all tests.
First of all, it’s good practice to name the unit test module (depending on the unit test framework, they are called test harnesses or test fixtures) in such a way so that the tested unit can be easily derived from it. They should have a name like <Unit_under_Test>Test,
22
![](/html/75672/2303/html_cw6SR1shwt.1WUZ/htmlconvd-KksEvx37x1.jpg)
Chapter 2 Build a Safety Net
whereby the placeholder <Unit_under_Test> is substituted with the name of the test subject. For instance, if your system under test (SUT) is the unit Money, the corresponding test fixture that attaches to that unit and contains all unit test cases should be named MoneyTest (see Figure 2-3).
Figure 2-3. The system under test, (SUT) Money and its test fixture, MoneyTest
Beyond that, unit tests must have expressive and descriptive names. It is not helpful when unit tests have meaningless names like testConstructor(), test4391(), or sumTest(). Here are two suggestions for finding good names for them.
For general, multipurpose classes that can be used in different contexts, an expressive name could contain the following parts:
•\ |
The precondition of the test scenario, that is, the state of the SUT |
|
before the test was executed. |
•\ |
The tested part of the unit under test, typically the name of the tested |
|
procedure, function, or method (API). |
•\ |
The expected test result. |
That leads to a name template for unit test procedures/methods like this one:
<PreconditionAndStateOfUnitUnderTest>_<TestedPartOfAPI>_<ExpectedBehavior>
Listing 2-1 shows a few examples.
Listing 2-1. Examples of Good and Expressive Unit Test Names
void CustomerCacheTest::cacheIsEmpty_addElement_sizeIsOne();
void CustomerCacheTest::cacheContainsOneElement_removeElement_sizeIsZero(); void ComplexNumberCalculatorTest::givenTwoComplexNumbers_add_Works();
23
Chapter 2 Build a Safety Net
void MoneyTest:: givenTwoMoneyObjectsWithDifferentBalance_Inequality Comparison_Works();
void MoneyTest::createMoneyObjectWithParameter_getBalanceAsString_ returnsCorrectString();
void InvoiceTest::invoiceIsReadyForAccounting_getInvoiceDate_returnsToday();
Another possible approach to building expressive unit test names is to manifest a specific requirement in the name. These names typically reflect requirements
of the application’s domain. For instance, they could be derived from stakeholder requirements. See Listing 2-2.
Listing 2-2. More Examples of Unit Test Names that Verify Domain-Specific Requirements
void UserAccountTest::creatingNewAccountWithExisting EmailAddressThrowsException();
void ChessEngineTest::aPawnCanNotMoveBackwards();
void ChessEngineTest::aCastlingIsNotAllowedIfInvolvedKingHasBeenMovedBefore(); void ChessEngineTest::aCastlingIsNotAllowedIfInvolvedRookHasBeenMovedBefore(); void HeaterControlTest::ifWaterTemperatureIsGreaterThan92DegTurnHeaterOff(); void BookInventoryTest::aBookThatIsInTheInventoryCanBeBorrowedByAuthorized People();
void BookInventoryTest::aBookThatIsAlreadyBorrowedCanNotBeBorrowedTwice();
As you read these test method names, it should become clear that even if the implementation of the tests and the test methods are not shown here, a lot of useful information can be easily derived. This is also a great advantage if such a test fails. All known unit test frameworks either output the name of a failed test via stdout on the command-line interface or list it in a special output window of the IDE. Thus, error location is greatly facilitated.
Unit Test Independence
Each unit test must be independent of all the others. It would be fatal if tests had to be executed in a specific order because one test was based on the result of the previous one. Never write a unit test whose result is the prerequisite for a subsequent test. Never leave the unit under test in an altered state, which is a precondition for the following tests.
24
![](/html/75672/2303/html_cw6SR1shwt.1WUZ/htmlconvd-KksEvx39x1.jpg)
Chapter 2 Build a Safety Net
Major problems can be caused by global states, for example, the usage of Singletons or static members in your unit under test. Not only do Singletons increase the coupling between software units, they also often hold a global state that circumvents unit test independence. For instance, if a certain global state is the precondition for a successful test, but the previous test has mutated that global state, this can cause serious trouble.
Especially in legacy systems, which are often littered with Singletons, this begs the question: how can you get rid of all those nasty dependencies to those Singletons and make your code more easily testable? Well, that’s an important question I discuss in the section entitled “Dependency Injection” in Chapter 6.
DEALING WITH LEGACY SYSTEMS
If you are confronted with so-called legacy systems and you are facing many difficulties while trying to add unit tests, I recommend the book Working Effectively with Legacy Code [Feathers07] by Michael C. Feathers. Feathers’s book contains many strategies for working with large, untested legacy code bases. It also includes a catalogue of 24 dependencybreaking techniques. These strategies and techniques are beyond the scope of this book.
One Assertion per Test
My advice is to limit a unit test to one assertion only, as shown in Listing 2-3. I know that this is a controversial topic, but I will try to explain why I think this is important.
Listing 2-3. A Unit Test that Checks the not-equal-Operator of a Money Class
void MoneyTest::givenTwoMoneyObjectsWithDifferentBalance_ InequalityComparison_Works() {
const Money m1(-4000.0); const Money m2(2000.0); ASSERT_TRUE(m1 != m2);
}
One could now argue that you could also check whether other comparison operators (e.g., Money::operator==()) are working correctly in this unit test. It would be easy to do that, by simply adding more assertions, as shown in Listing 2-4.
25