There are many books about software testing, but Quality Code is among the best that I have read. It presents a philosophy about software testing that has been mine in my previous life as a software developer: the programmer is the main person responsible for the quality of its code.
Stephen Vance describes the goal of his book as “This book is about easily and routinely testing all of your software. It primarily focuses on unit tests, but many of the techniques apply to higher-level tests as well. This book will give you the tools – the implementation patterns – to test almost any code and to recognize when the code needs to change to be testable.” The book is aimed at professional software developers and it provides a lot of practical examples in code that is readable even if you don’t program with this specific language.
This practical part is preceded by an introduction that describes the principles and theory of software testing, discussing for instance the different nuances of the black and white approaches or explaining how some programming patterns make the code difficult to test. My favorite part is indeed this chapter devoted to design and testability. Everything is presented in a way that is easy to read, even the parts with meaningful code examples.
Quality Code is a book that I will strongly recommend to every software developer that is convinced that code quality is his main responsibility and not something that will be eventually managed by a QA department elsewhere before delivery. This is a great book on how to grow code and quality together.
Reference: “Quality Code: Software Testing Principles, Practices, and Patterns”, Stephen Vance, Addison-Wesley
However, as I have coached teams into testing regimens, I have repeatedly found that the obstacle to adoption is neither a lack of understanding of the process flow, nor a misunderstanding of the concepts, nor an insufficient lexicon, nor skepticism about the value of the practices. All of these obstacles exist in various people at different times, but the most common one that has not been well addressed is simply limited understanding of the mechanics of testing code and of writing testable code.
Clearly delineating intent and implementation helps your testing efforts scale with your software. The more you can test the intent without incorporating elements of the implementation, the less you couple your tests to your code. Less coupling means that changes in implementation do not force you to update or rewrite your tests. Fewer changes to tests mean that less of your effort is spent on tests, and it increases the likelihood that the tests remain correct. All of this results in lower costs to verify, maintain, and extend your software, especially over the long term.
Of all the practices you can leverage to assist your craftsmanship, you will get the most benefit from testing. You may say, “We have been testing software for years, and we still have lots of bugs.” Therein lies the key to going forward. In a Lean system, you try to prevent defects rather than catch them. The tradition of software testing has been one of catching defects. It should not surprise us, then, that we have an inefficient process. The earlier you test, the sooner you catch defects. Does that mean you are still just catching defects? If you write the tests after the code, then yes. If you close the gap between creating and catching the defects, less change occurs between the two events. You have a higher likelihood of the context of the created code being fresh in your mind. If you write the test immediately after writing the code, it can verify your conceptions and assumptions and keep you on course. You also have an opportunity to really catch the bugs before they occur, however. Test-first and test-driven approaches to development bring the testing before the coding. When you test first, you capture your intent in an automatable and executable form. You focus on what you are about to write in a way that works to prevent defects rather than create them. The tests you write serve as a persistent reinforcement of that intent going forward. In addition to helping you do the thing right, it helps you to do the right thing. Remember, it is still a bug if you implement the wrong thing really well.
As discussed in Chapter 3, all software has intent, and the first goal of testing is to verify the intent. Black-box testing is popular and effective because it verifies software purely by its intent rather than its implementation. However, most moderately complex algorithms or logic cannot be completely tested purely as a black box. Coverage-guided unit testing requires white-box insight into the implementation. Notice that this principle says “intent over implementation” rather than “intent instead of implementation” as you find in more prescriptive statements. It is advisable and necessary to guide some level of testing, particularly unit tests, against the implementation, but you should never lose sight of the intent that the code is trying to produce.