13 Testing and debugging
This chapter covers
- Avoiding runtime errors by moving them to compile-time
- Understanding the benefits of pure functions in unit testing
- Automatically generating test cases for pure functions
- Testing code by comparing against existing solutions
- Testing monad-based concurrent systems
Computers are becoming omnipresent. We have smart watches, TVs, toasters, and more. Consequences of bugs in software today range from minor annoyances to serious problems, including identity theft and even danger to our lives.
Therefore, it’s more important than ever that the software we write is correct: that it does exactly what it should and doesn’t contain bugs. Although this sounds like a no-brainer—because who in their right mind would want to write bug-ridden software?—it’s widely accepted that all nontrivial programs contain bugs. We are so accustomed to this fact that we tend to subconsciously develop workarounds to avoid the bugs we discover in programs we’re using.
Although this is the sad truth, it’s not an excuse for not trying to write correct programs. The issue is that this isn’t easy to do.
Most features of higher-level programming languages are introduced with this issue in mind. This is especially true for C++, where most recent evolution has focused on making safe programs easier to write—or, to be more precise, to make it easier for programmers to avoid common programming mistakes.