Step 6: Automate the test cases

It is now time to transform the test cases into automated JUnit tests. Writing those tests is mostly a mechanical task. The creative part is coming up with inputs to exercise the specific partition and understanding the correct program output for that partition.

The automated test suite is shown in listings 2.3 through 2.7. They are long but easy to understand. Each call to the substringsBetween method is one of our test cases. The 21 calls to it are spread over the test methods, each matching the test cases we devised earlier.

First are the tests related to the string being null or empty.

Listing 2.3 Tests for substringsBetween, part 1

import org.junit.jupiter.api.Test;
import static ch2.StringUtils.substringsBetween;
import static org.assertj.core.api.Assertions.assertThat;
 
public class StringUtilsTest {
 
  @Test
  void strIsNullOrEmpty() {
    assertThat(substringsBetween(null, "a", "b"))   ❶
      .isEqualTo(null);
 
    assertThat(substringsBetween("", "a", "b"))     ❷
      .isEqualTo(new String[]{});
  }
 
}

❶ This first call to substringsBetween is our test T1.

❷ Test T2

Next are all the tests related to open or close being null or empty.

Listing 2.4 Tests for substringsBetween, part 2

  @Test
  void openIsNullOrEmpty() {
    assertThat(substringsBetween("abc", null, "b")).isEqualTo(null);
    assertThat(substringsBetween("abc", "", "b")).isEqualTo(null);
  }
 
  @Test
  void closeIsNullOrEmpty() {
    assertThat(substringsBetween("abc", "a", null)).isEqualTo(null);
    assertThat(substringsBetween("abc", "a", "")).isEqualTo(null);
  }

Now come all the tests related to string and open and close tags with length 1.

Listing 2.5 Tests for substringsBetween, part 3

  @Test
  void strOfLength1() {
    assertThat(substringsBetween("a", "a", "b")).isEqualTo(null);
    assertThat(substringsBetween("a", "b", "a")).isEqualTo(null);
    assertThat(substringsBetween("a", "b", "b")).isEqualTo(null);
    assertThat(substringsBetween("a", "a", "a")).isEqualTo(null);
  }
 
  @Test
  void openAndCloseOfLength1() {
    assertThat(substringsBetween("abc", "x", "y")).isEqualTo(null);
    assertThat(substringsBetween("abc", "a", "y")).isEqualTo(null);
    assertThat(substringsBetween("abc", "x", "c")).isEqualTo(null);
    assertThat(substringsBetween("abc", "a", "c"))
      .isEqualTo(new String[] {"b"});
    assertThat(substringsBetween("abcabc", "a", "c"))
      .isEqualTo(new String[] {"b", "b"});
  }

Then we have the tests for the open and close tags of varying sizes.

Listing 2.6 Tests for substringsBetween, part 4

  @Test
  void openAndCloseTagsOfDifferentSizes() {
    assertThat(substringsBetween("aabcc", "xx", "yy")).isEqualTo(null);  
    assertThat(substringsBetween("aabcc", "aa", "yy")).isEqualTo(null);
    assertThat(substringsBetween("aabcc", "xx", "cc")).isEqualTo(null);
    assertThat(substringsBetween("aabbcc", "aa", "cc"))
      .isEqualTo(new String[] {"bb"});
    assertThat(substringsBetween("aabbccaaeecc", "aa", "cc"))
      .isEqualTo(new String[] {"bb", "ee"});
  }

Finally, here is the test for when there is no substring between the open and close tags.

Listing 2.7 Tests for substringsBetween, part 5

@Test
void noSubstringBetweenOpenAndCloseTags() {
  assertThat(substringsBetween("aabb", "aa", "bb"))
    .isEqualTo(new String[] {""});
  }
}

I decided to group the assertions in five different methods. They almost match my groups when engineering the test cases in step 5. The only difference is that I broke the exceptional cases into three test methods: strIsNullOrEmptyopenIsNullOrEmpty, and closeIsNullOrEmpty.

Some developers would vouch for a single method per test case, which would mean 21 test methods, each containing one method call and one assertion. The advantage would be that the test method’s name would clearly describe the test case. JUnit also offers the ParameterizedTest feature which could be used in this case.

I prefer simple test methods that focus on one test case, especially when implementing complex business rules in enterprise systems. But in this case, there are lots of inputs to test, and many of them are variants of a larger partition, so it made more sense to me to code the way I did.

Deciding whether to put all tests in a single method or in multiple methods is highly subjective. We discuss test code quality and how to write tests that are easy to understand and debug.

Also note that sometimes there are values we do not care about. For example, consider test case 1: str is null. We do not care about the values we pass to the open and close tags here. My usual approach is to select reasonable values for the inputs I do not care about—that is, values that will not interfere with the test.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *