Part 1. Foundations
This first part of the book aims to give you, the reader, and me, the author, a shared context to build upon throughout the chapters. With the ultimate purpose of this book being to help you improve your ability to write good tests, chapter 1 begins with an overview of what kind of value we can extract from writing tests in the first place. Once weâve discussed the dynamics of programmer productivity and the kind of impact that our testsâand the quality of our testsâcan have on it, weâll conclude the chapter with a brief introduction to two methods that are closely related to automated tests: test-driven development (TDD) and behavior-driven development (BDD).
Chapter 2 takes on the challenge of defining what makes for a good test. In short, weâd like to write tests that are readable, maintainable, and reliable. Part 2 will go deeper into this rabbit hole by turning the question around and reviewing a collection of examples of what we donât want to get.
Part 1 concludes with chapter 3, which tackles one of the most essential tools in the modern programmerâs tool beltâtest doubles. Weâll establish the legitimate uses of test doubles, such as isolating code so that it can be tested properly, and weâll make a distinction between the types of test doubles we might reach out to. Finally, weâll throw in guidelines for making good use of test doubles to help you get the benefits without stumbling on common pitfalls.
After reading these first three chapters you should have a good idea of what kind of tests you want to write and why. You should also have a clear understanding of test doubles as a useful vehicle for getting there. The rest of this book will then build on this foundation and give you more ammunition for your journey in the real world.
Chapter 1. The promise of good tests
In this chapter - The value of having unit tests
- How tests contribute to a programmerâs productivity
- Using tests as a design tool
When I started getting paid for programming, the world looked a lot different. This was more than 10 years ago and people used simple text editors like Vim and Emacs instead of todayâs integrated development environments like Eclipse, Net-Beans, and IDEA. I vividly remember a senior colleague wielding his Emacs macros to generate tons of calls to System.out.println as he was debugging our software. Even more vivid are my memories of deciphering the logs those printouts ended up in after a major customer had reported that their orders werenât going through like they should.
That was a time when âtestingâ for most programmers meant one of two thingsâthe stuff that somebody else does after Iâm done coding, or the way you run and poke your code before you say youâre done coding. And when a bug did slip through, youâd find yourself poking and prodding your code againâthis time adding a few more logging statements to see if you could figure out where things went wrong.
Automation was a state-of-the-art concept for us. We had makefiles to compile and package our code in a repeatable manner, but running automated tests as part of the build wasnât quite in place. We did have various shell scripts that launched one or two âtest classesââsmall applications that operated our production code and printed what was happening and what our code was returning for what kind of input. We were far from the kind of standard testing frameworks and self-verifying tests that report all failures in our assertions.
Weâve come a long way since those days.
1.1. State of the union: writing better tests
Today, itâs widely recommended that developers write automated tests that fail the build when there are regressions. Furthermore, an increasing number of professionals is leaning on a test-first style of programming, using automated tests not for protecting against regression but for aiding them in design, specifying the behavior they expect from code before writing that code, thereby validating a design before verifying its implementation.
Being a consultant, I get to see a lot of teams, organizations, products, and code bases. Looking at where we are today, itâs clear that automated tests have made their way into the mainstream. This is good because without such automated tests, most software projects would be in a far worse situation than they are today. Automated tests improve your productivity and enable you to gain and sustain development speed.
Help! Iâm new to unit testing If you arenât that familiar with writing automated tests, this would be a good time to get acquainted with that practice. Manning has released several books on JUnit, the de facto standard library for writing unit tests for Java, and the second edition of JUnit in Action (written by Petar Tahchiev, et al. and published in July 2010) is a good primer for writing tests for all kinds of Java code, from plain old Java objects to Enterprise JavaBeans.
In case youâre at home with writing unit tests but youâre new to Java or JUnit, perhaps all you need to get the most out of this book is to first check out appendix A, so that you wonât have trouble reading the examples.
Automated tests being mainstream doesnât mean that our test coverage is as high as it should be or that our productivity couldnât improve. In fact, a significant part of my work in the last five years or so has revolved around helping people write tests, write tests before code, and especially write better tests.
Why is it so important to write better tests? Whatâll happen if we donât pay attention to the quality of our tests? Letâs talk about what kind of value tests give us and why the quality of those tests matters.
1.2. The value of having tests
Meet Marcus. Marcus is a prominent programming wiz who graduated two years ago and joined the IT group of a local investment bank, where heâs been developing the bankâs online self-service web application. Being the youngest member of the team, he kept a low profile at first and focused his energy on learning about the banking domain and getting to know âthe way things are done here.â
A few months on, Marcus started noticing that a lot of the teamâs work was rework: fixing programmer mistakes.[1] He started paying attention to the types of mistakes the team was fixing and noticed that most of them wouldâve been caught fairly easily by unit tests. Marcus started writing unit tests here and there when he felt that the code was particularly prone to having mistakes in it.
1 For some reason, people referred to them as errors, defects, bugs, or issues.
Tests help us catch mistakes.
Time went on and the rest of the team was writing unit tests, too, here and there. Marcus had become test-infected and rarely left a piece of code that he touched without fairly good coverage from automated tests.[2] They werenât spending any more time fixing errors than they had before, but for the first time, their total number of open defects was on the decline. The tests started having a clearly visible impact on the quality of the teamâs work.
2 The term test-infected was coined by Erich Gamma and Kent Beck in their 1998 Java Report article, âTest-Infected: Programmers Love Writing Tests.â
Almost a year had passed since Marcus wrote the first test in their code base. On his way to the company Christmas party, Marcus realized how time had flown and started thinking back to the changes theyâd seen. The teamâs test coverage had grown quickly and had started stabilizing in the recent weeks and months, peaking at 98% branch coverage.
For a while Marcus had thought that they should push that number all the way to 100%. But in the last couple of weeks, heâd more or less made up his mindâthose missing tests wouldnât give them much more value, and putting any more effort into writing tests wouldnât yield additional benefit. A lot of the code that wasnât covered with tests was there only because the APIs they used mandated the implementation of certain interfaces that Marcusâs team wasnât using, so why test those empty method stubs?
100% Code Co...