Let’s start with a full example of software testing. Imagine you work as a developer for a software company that creates programs for fire-control systems, as shown in figure 3.1.
The processing unit is connected to multiple fire sensors and polls them continuously for abnormal readings. When a fire is discovered, the alarm sounds. If the fire starts spreading and another detector is triggered, the fire brigade is automatically called. Here are the complete requirements of the system:
Your colleague has already implemented this system, and you’re tasked with unit testing. The skeleton of the Java implementation is shown in listing 3.1.
You can find almost all code listings for this book at https://github.com/kkapelon/java-testing-with-spock.
For brevity, the book sometimes points you to the source code (especially for long listings). I tend to use the Eclipse IDE in my day-to-day work. If you didn’t already install Spock and Eclipse in chapter 2, you can find installation instructions in appendix A.
This fire sensor is regularly injected with the data from the fire sensors, and at any given time, the sensor can be queried for the status of the alarm.
The application uses two classes:
This is only the heart of the system. Code for contacting the fire brigade or triggering the alarm is outside the scope of this example.
Your colleague has finished the implementation code, and has even written a JUnit test[2] as a starting point for the test suite you’re supposed to finish. You now have the full requirements of the system and the implementation code, and you’re ready to start unit testing.
Following the test-driven development (TDD) principles of writing a unit test for a feature before the feature implementation.
You decide to look first at the existing JUnit test your colleague already wrote. The code is shown in the following listing.
This unit test covers the case of a single sensor detecting fire. According to the requirements, the alarm should sound, but the fire department isn’t contacted yet. If you closely examine the code, you’ll discover a hidden structure between the lines. All good JUnit tests have three code segments:
Notice that this structure is implied with JUnit. It’s never enforced by the framework and might not be clearly visible in complex unit tests. Your colleague is a seasoned developer and has clearly marked the three phases by using the empty lines in listing 3.2:
This is good advice to follow, but not all developers follow this technique. (It’s also possible to demarcate the phases with comments.)
Because JUnit doesn’t clearly distinguish between the setup-stimulate-assert phases, it’s up to the developer to decide on the structure of the unit test. Understanding the structure of a JUnit test isn’t always easy when more-complex testing is performed. For comparison, the following listing shows a real-world result.[3]
This unit test is from the jedis library found on GitHub. I mean no disrespect to the authors of this code, and I congratulate them for offering their code to the public. The rest of the tests from jedis are well-written.
private static final String MASTER_NAME = "mymaster"; private static HostAndPort sentinel = new HostAndPort("localhost",26379); @Test public void sentinelSet() { Jedis j = new Jedis(sentinel.getHost(), sentinel.getPort()); try { Map<String, String> parameterMap = new HashMap<String, String>(); parameterMap.put("down-after-milliseconds", String.valueOf(1234)); parameterMap.put("parallel-syncs", String.valueOf(3)); parameterMap.put("quorum", String.valueOf(2)); j.sentinelSet(MASTER_NAME, parameterMap); List<Map<String, String>> masters = j.sentinelMasters(); for (Map<String, String> master : masters) { if (master.get("name").equals(MASTER_NAME)) { assertEquals(1234, Integer.parseInt(master .get("down-after-milliseconds"))); assertEquals(3, Integer.parseInt(master.get("parallel- syncs"))); assertEquals(2, Integer.parseInt(master.get("quorum"))); } } parameterMap.put("quorum", String.valueOf(1)); j.sentinelSet(MASTER_NAME, parameterMap); } finally { j.close(); } }
After looking at the code, how long did it take you to understand its structure? Can you easily understand which class is under test? Are the boundaries of the three phases really clear? Imagine that this unit test has failed, and you have to fix it immediately. Can you guess what has gone wrong simply by looking at the code?
Another problem with the lack of clear structure of a JUnit test is that a developer can easily mix the phases in the wrong[4] order, or even write multiple tests into one. Returning to the fire-control system in listing 3.2, the next listing shows a bad unit test that tests two things at once. The code is shown as an antipattern. Please don’t do this in your unit tests!
Because “everything that can go wrong, will go wrong,” you can imagine that I’ve seen too many antipatterns of JUnit tests that happen because of the lack of a clear structure.
This unit test asserts two different cases. If it breaks and the build server reports the result, you don’t know which of the two scenarios has the problem.
Another common antipattern I see all too often is JUnit tests with no assert statements at all! JUnit is powerful, but as you can see, it has its shortcomings. How would Spock handle this fire-control system?
Unlike JUnit, Spock has a clear test structure that’s denoted with labels (blocks in Spock terminology), as you’ll see in chapter 4, which covers the lifecycle of a Spock test. Looking back at the requirements of the fire-control system, you’ll see that they can have a one-to-one mapping with Spock tests. Here are the requirements again:
Spock can directly encode these sentences by using full English text inside the source test of the code, as shown in the following listing.
Spock follows a given-when-then structure that’s enforced via labels inside the code. Each unit test can be described using plain English sentences, and even the labels can be described with text descriptions.
This enforced structure pushes the developer to think before writing the test, and also acts as a guide on where each statement goes. The beauty of the English descriptions (unlike JUnit comments) is that they’re used directly by reporting tools. A screenshot of a Maven Surefire report is shown in figure 3.2 with absolutely no modifications (Spock uses the JUnit runner under the hood). This report can be created by running mvn surefire-report:report on the command line.
The first column shows the result of the test (a green tick means that the test passes), the second column contains the description of the test picked up from the source code, and the third column presents the execution time of each test (really small values are ignored). More-specialized tools can drill down in the labels of the blocks as well, as shown in figure 3.3. The example shown is from Spock reports (https://github.com/renatoathaydes/spock-reports).
Spock isn’t a full BDD tool,[5] but it certainly pushes you in that direction. With careful planning, your Spock tests can act as living business documentation.
See JBehave (http://jbehave.org/) or Cucumber JVM (http://cukes.info/) to see how business analysts, testers, and developers can define the test scenarios of an enterprise application.
You’ve now seen how Spock handles basic testing. Let’s see a more complex testing scenario, where the number of input and output variables is much larger.
3.143.205.27