Detecting the need for parameterized tests

Một phần của tài liệu Manning java testing with spock (Trang 153 - 156)

Experienced developers usually can understand the need for a parameterized test right away. But even if you’re just starting with unit tests, an easy rule of thumb can show you the need for a parameterized test. Every time you start a new unit test by copying-pasting an existing one, ask yourself, “Is this test that much different from the previous one?” If you find yourself duplicating unit tests and then changing only one or two variables to create a similar scenario, take a step back and think about whether a parameterized test would be more useful. Parameterized tests will help you keep the test code DRY.1

Assume, for example, that you have a single class that takes an image filename and returns true if the picture has an extension that’s considered valid for the application:

public class ImageNameValidator {

public boolean isValidImageExtension(String fileName) {

[...redacted for brevity...]

} }

Duplicating unit test code isn’t a healthy habit

Anytime you copy-paste a unit test, you’re creating code duplication, because you haven’t thought about reusable code segments. Like production code, test code should be treated with the same “respect.” Refactoring unit tests to allow them to share code via composition instead of performing a blind copy-paste should be one of your first priorities when adding new unit tests into an existing suite. More details are presented in chapter 8.

1 This acronym stands for don’t repeat yourself. See https://en.wikipedia.org/wiki/Don’t_repeat_yourself for more information.

129 Detecting the need for parameterized tests

A first naive approach would be to write a single Spock test for every image extension that needs to be examined. This approach is shown in the following listing (and it clearly suffers from code duplication).

def "Valid images are JPG"() {

given: "an image extension checker and a jpg file"

ImageNameValidator validator = new ImageNameValidator() String pictureFile = "scenery.jpg"

expect: "that the filename is valid"

validator.isValidImageExtension(pictureFile) }

def "Valid images are JPEG"() {

given: "an image extension checker and a jpeg file"

ImageNameValidator validator = new ImageNameValidator() String pictureFile = "house.jpeg"

expect: "that the filename is valid"

validator.isValidImageExtension(pictureFile) }

def "Tiff are invalid"() {

given: "an image extension checker and a tiff file"

ImageNameValidator validator = new ImageNameValidator() String pictureFile = "sky.tiff"

expect: "that the filename is invalid"

!validator.isValidImageExtension(pictureFile) }

The original requirement is to accept JPG files only, and a single unit test is written.

The customer reports that the application doesn’t work on Linux because .jpeg files are used. Application code is updated, and another test method is added (by copying the existing one).

Then a business analyst requests an explicit test for not supporting TIFF files. You can see where this is going. In large enterprise applications, multiple developers might work on the same feature as time progresses. If each developer is blindly adding a new unit test by copy-paste (either because of a lack of time or lack of experience), the result is a Spock test, as shown in listing 5.1, that smells of code duplication from afar.

Notice that each test method by itself in listing 5.1 is well structured. Each is docu- mented, it tests one thing, the trigger action is small, and so on. The problem stems from the collection of those test methods that need further refactoring, as they all have the exact same business logic.

Listing 5.1 Duplicate tests—don’t do this

Each test differs in input data.

Each test differs in output data.

5.1.1 What are parameterized tests?

An example of a parameterized test for this class in Spock is shown in the following listing. With a single unit test, this listing not only replaces all three tests of listing 5.1 but also adds two more cases.

def "Valid images are PNG and JPEG files"() { given: "an image extension checker"

ImageNameValidator validator = new ImageNameValidator() expect: "that only valid filenames are accepted"

validator.isValidImageExtension(pictureFile) == validPicture where: "sample image names are"

pictureFile || validPicture "scenery.jpg" || true

"house.jpeg" || true "car.png" || true "sky.tiff" || false "dance_bunny.gif" || false }

The test method examines multiple scenarios in which the test logic is always the same (validate a filename) and only the input (jpg) and output (valid/not valid) variables change each time. The test code is fixed, whereas the test input and output data come in the form of parameters (and thus you have a parameterized test).

The idea is better illustrated in figure 5.1.

Listing 5.2 Simple Spock parameterized test

Class under test Common test

logic for all scenarios that use pictureFile and validPicture

where: block contains parameters for multiple scenarios.

First line of block is always the names of parameters

Input and expected output for each scenario in each line

Input Output

Different sets of input/output…

…use the same test logic:

Is the image extension valid?

.jpg

.jpeg

.png

.tiff

Figure 5.1 Parameterized tests share the same test logic for different input/

output datasets.

131 Using the where: block

The test code is shared among all parameters. Instead of duplicating this common code for each scenario, you centralize it on a single test method. Then each scenario comes with its own scenario parameters that define the expected result for each input.

Output 1 is expected when the test scenario is triggered with input 1, output 2 is expected if input 2 is used, and so on.

Một phần của tài liệu Manning java testing with spock (Trang 153 - 156)

Tải bản đầy đủ (PDF)

(306 trang)