Using dedicated data generators

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

All the previous examples of data pipes use lists (Groovy ranges also act as lists) to hold the parameters for each test iteration. Grouping parameters in a list is the more readable option in my opinion, but Spock can also iterate on the following:

■ Strings (each iteration will fetch a character).

■ Maps (each iteration will pick a key).

■ Enumerations.

■ Arrays.

■ RegEx matchers.

■ Iterators.

■ Iterables.

This list isn’t exhaustive. Everything that Groovy can iterate on can be used as a data generator. Chapter 2 even includes a Groovy Expando as an example of an iterator.

Iterables and iterators are interfaces, which means that you can implement your own classes for the greatest control of how Spock uses parameters. Even though custom implementations can handle complex transformations of test data when required, I consider them a last-resort solution because they’re not always as readable as simpler data tables. The solutions offered by Spock are compared in figure 5.15.

Figure 5.14 Derived values are recalculated for each test run. Here the second parameter is always the negative representation of the first one.

Data tables Data tables with expressions

Data pipes with lists

Flexibility Readability

Custom iterators

Figure 5.15 All solutions shown for parameterized Spock tests. Data tables are limited, but readable by even nontechnical people. All other techniques sacrifice readability for more expressive power.

149 Using dedicated data generators

If you need to create a custom iterator for obtaining business data, you should always ask yourself whether the transformation of the data belongs in the business class that you’re trying to test, or whether it’s part of the iterator.

Before trying custom iterators, you should spend some time determining whether you can use existing classes in your application that already return data in the format that you expect. As an example, assume you have a text file that holds image names that your program can accept, as shown in figure 5.16.

The content of the file validImageNames.txt is as follows:

hello.jpg another.jpeg modern0034.JPEG city.Png city_004.PnG landscape.JPG

To read this file, you don’t need a custom iterator. The Groovy File class already con- tains a readLines() method that returns a list of all lines in a file. The respective Spock test is shown in the following listing.

@Unroll("Checking image name #pictureFile") def "Valid images are PNG and JPEG files"() { given: "an image extension checker"

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

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

pictureFile << new

File("src/test/resources/validImageNames.txt") .readLines() }

Listing 5.18 Using existing data generators

Groovy reads the lines from a text file and passes them on to Spock. Spock uses each line to create a scenario.

given:

.jpg

expect: where:

hello.jpg another.jpeg modern0034.JPEG city.png

.jpg

.png

Figure 5.16 Using a text file as a data source for a parameterized test

Gets a list of all lines of the file

Here Groovy opens the file, reads its lines in a list, and passes them to Spock. Spock fetches the lines one by one to create all the scenarios of the test. Running the test produces the output shown in figure 5.17.

Before resorting to custom iterators, always see whether you can obtain data with your existing application code or GDK/JDK facilities.9 Always keep in mind the excel- lent facilities of Groovy for XML and JSON reading (these were covered in chapter 2).

5.4.1 Writing a custom data generator

You show the unit test with the valid image names to your business analyst in order to explain what’s supported by the system. The analyst is impressed, and as a new task, you get the following file named invalidImageNames.txt:

#Found by QA starsystem.tiff galaxy.tif

#Reported by client bunny04.gif looper.GIF dolly_take.mov afar.xpm

The file can’t be used as is in a unit test. It contains comments that start with the # sign, it has empty lines, and it even has tabs in front of some image names.

You want to write a Spock test that checks this file and confirms the rejection of the image names (they’re all invalid). It’s obvious that the Groovy File class can’t help you in this case; the file has to be processed before it’s used in the Spock test.10

9 Or even classes from Guava, Apache commons, CSV reading libraries, and so on.

10In this simple example, you could clear the file contents manually. In a larger file, this isn’t practical or even possible.

Figure 5.17 Reading test values from a file by using Groovy code

151 Using dedicated data generators

To solve this new challenge, you should first create a custom data iterator, as shown in the next listing.

public class InvalidNamesGen implements Iterator<String>{

private List<String> invalidNames;

private int counter =0;

public InvalidNamesGen() {

invalidNames = new ArrayList<>();

parse();

}

private void parse() {

[...code that reads the file and discards

empty lines, tabs and comments not shown for brevity...]

}

@Override

public boolean hasNext() { return counter < invalidNames.size();

}

@Override

public String next() { String result = invalidNames.get(counter);

counter++;

return result;

}

@Override

public void remove() { }

}

There’s nothing Spock-specific about this class on its own. It’s a standard Java iterator that reads the file and can be used to obtain string values. You can use this iterator directly in Spock, as shown in the next listing.

@Unroll("Checking image name #pictureFile") def "Valid images are PNG and JPEG files"() { given: "an image extension checker"

ImageNameValidator validator = new ImageNameValidator() expect: "that all filenames are rejected"

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

pictureFile << new InvalidNamesGen() }

Listing 5.19 Java iterator that processes invalidImageNames.txt

Listing 5.20 Using Java iterators in Spock

Class will return strings (lines).

Generate values while lines are present in the file.

Get the next line from the file.

No need to implement this for this example

This time you expect invalid images.

Instruct Spock to read strings from the custom class.

If you run this test, you’ll see that the file is correctly cleaned up and processed, as shown in figure 5.18. Empty lines, comments, and tabs are completely ignored, and only the image names are used in each test scenario.

Happy with the result, you show the new test to your business analyst (thinking that you’re finished). Apparently, you must face one last challenge.

5.4.2 Using multivalued data iterators

Your business analyst examines the two Spock tests (the one for valid images, and the one for invalid images) and decides that two files aren’t needed. The analyst combines the two files into one, called imageNames.txt, with the following content:

#Found by QA

starsystem.tiff fail galaxy.tif fail desktop.png pass europe.jpg pass modern0034.JPEG pass city.Png pass

city_004.PnG pass

#Reported by client bunny04.gif fail

looper.GIF fail dolly_take.mov fail

afar.xpm fail

Writing custom data generators in Groovy

In this section and the next, I use Java to implement a custom data generator because I assume that you’re more familiar with Java. It’s possible to write data generators in Groovy. This would be the preferred method when you know your way around Groovy, because you can include the generator inside the same source file as the Spock test (instead of having two separate files, one in Java for the iterator and one in Groovy for the Spock test).

Figure 5.18 Using a Java iterator in a Spock unit test allows for more fine- grained file reading.

153 Using dedicated data generators

This file is similar to the other two, with one important difference. It contains the word pass/fail in the same line as the image name.11 At first glance, it seems that you need to write a test similar to listing 5.13, but using two custom iterators, as follows:

where: "sample image names are"

pictureFile << new CustomIterator1() validPicture << new CustomIterator2()

The first iterator is responsible for reading the image names as before, and the second iterator reads the pass/fail flag and converts it to a Boolean. This solution would cer- tainly work, but having two custom iterators isn’t practical. They would both share sim- ilar code (both need to ignore empty lines), and keeping them in sync if the file format changed would be a big challenge.

Hopefully, with Spock tests you don’t need extra custom iterators for each parame- ter. Spock supports multivalue iterators (powered by Groovy multivalued assign- ments12), so you can obtain all your input/output parameters from a single iterator.

For illustration purposes, our example uses a custom iterator to fetch two variables, but the same technique can work with any number of parameters. The iterator is shown in the next listing.

public class MultiVarReader implements Iterator<Object[]>{

private List<String> fileNames;

private List<Boolean> validFlag;

private int counter =0;

public MultiVarReader() {

fileNames = new ArrayList<>();

validFlag = new ArrayList<>();

parse();

}

private void parse() {

[...code that reads the file and discards

empty lines, tabs and comments not shown for brevity...]

}

@Override

public boolean hasNext() { return counter< fileNames.size();

}

@Override

public Object[] next() { Object[] result = new Object[2];

11In reality, this file would be a large XLS file with multiple columns that contained both important and unre- lated data.

12You can find more details about Groovy multivalue assignments at htttp://www.groovy-lang.org/semantics .html#_multiple_assignment.

Listing 5.21 Java multivalued iterator

Class will return multiple values.

Generate values while lines are present.

First parameter is the file, and second parameter is the pass/fail result.

result[0] = fileNames.get(counter);

result[1] = validFlag.get(counter);

counter++;

return result;

}

@Override

public void remove() { }

}

Here the defined iterator returns two objects. The first object is the image name, and the second object is a Boolean that’s true if the image should be considered valid, and false if the image name should be rejected. Notice again that there’s nothing Spock-specific about this class. It’s a normal Java class.

The Spock test that uses this multivalue iterator is shown in the following listing.

@Unroll("Checking image name #pictureFile with result=#result") def "Valid images are PNG and JPEG files only 2"() {

given: "an image extension checker"

ImageNameValidator validator = new ImageNameValidator() expect: "that all filenames are categorized correctly"

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

[pictureFile,result] << new MultiVarReader() }

In the Spock test, the left-shift operator is used as before, but this time the left side is a list of parameters instead of a single parameter. Spock reads the respective values from the data generator and places them in theparameters in the orderthey’re mentioned.

The first value that comes out of the data generator is placed in the first parameter (the image name, in this case), and the second value from the generator (the Boolean flag, in this case) is placed in the second parameter. Running the test produces the output in figure 5.19.

Listing 5.22 Using multivalued iterators in Spock No need to implement this

Result is now an output parameter.

The iterator reads both parameters at once.

Figure 5.19 Multivalued iterators.

For each test run, Spock reads both the input (image filename) and the output parameter (result of validity) from the data file.

155 Working with third-party data generators

This capability of the left-shift operator to handle multivalues isn’t restricted to data generators (although that’s where it’s most useful). You can perform multivalue parameter assignments by using plain data pipes, as shown in the following listing.

@Unroll("Checking harcoded image name #pictureFile with #result") def "Valid images are PNG and JPEG files only"() {

given: "an image extension checker"

ImageNameValidator validator = new ImageNameValidator() expect: "that all filenames are categorized correctly"

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

[pictureFile,result] << [["sample.jpg",true], ["bunny.gif",false]]

}

The right side of the assignment contains a list of all scenarios (which are lists). For each scenario, Spock again picks the first element and places it in the first variable of the left list. The second element from the scenario is placed in the second parameter, and so on.

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

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

(306 trang)