3.1. Introducing the behavior-testing paradigm

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.

Figure 3.1. A fire-monitoring system controlling multiple detectors

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:

  • If all sensors report nothing strange, the system is OK and no action is needed.
  • If one sensor is triggered, the alarm sounds (but this might be a false positive because of a careless smoker who couldn’t resist a cigarette).
  • If more than one sensor is triggered, the fire brigade is called (because the fire has spread to more than one room).

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.

How to use the code listings

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.

Listing 3.1. A fire-control system in Java

The application uses two classes:

  • The polling class has all the intelligence and contains a getter that returns a status class with the present condition of the system.
  • The status class is a simple object that holds the details.[1]

    1

    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.

2

Following the test-driven development (TDD) principles of writing a unit test for a feature before the feature implementation.

3.1.1. The setup-stimulate-assert structure of JUnit

You decide to look first at the existing JUnit test your colleague already wrote. The code is shown in the following listing.

Listing 3.2. A JUnit test for the fire-control system

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:

  1. In the setup phase, the class under test and all collaborators are created. All initialization stuff goes here.
  2. In the stimulus phase, the class under test is tampered with, triggered, or otherwise passed a message/action. This phase should be as brief as possible.
  3. The assert phase contains only read-only code (code with no side effects), in which the expected behavior of the system is compared with the actual one.

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:

  • The setup phase creates the FireEarlyWarning class and sets the number of triggered sensors that will be evaluated (the first two statements in listing 3.2).
  • The stimulus phase passes the triggered sensors to the fire monitor and also asks it for the current status (the middle two statements in listing 3.2).
  • The assert phase verifies the results of the test (the last two statements).

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]

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.

Listing 3.3. JUnit test with complex structure (real example)
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!

4

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.

Listing 3.4. A JUnit test that tests two things—don’t do this

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?

3.1.2. The given-when-then flow of Spock

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:

  • If all sensors report nothing strange, the system is OK and no action is needed.
  • If one sensor is triggered, the alarm sounds (but this might be a false positive because of a careless smoker who couldn’t resist a cigarette).
  • If more than one sensor is triggered, the fire brigade is called (because the fire has spread to more than one room).

Spock can directly encode these sentences by using full English text inside the source test of the code, as shown in the following listing.

Listing 3.5. The full Spock test for the fire-control system

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.

Figure 3.2. Surefire report with Spock test description

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).

Figure 3.3. Spock report with all English sentences of the test

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.

5

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.205.27