Groovy features useful to Spock tests

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

Now you have all the essential knowledge needed in order to write your own Spock assertions. The rest of this chapter continues with the theme of reducing unit test code size with the expressiveness provided by Groovy compared to Java. All the follow- ing techniques are optional, and you can still use normal Java code in your Spock tests if your organization needs a more gradual change. Each application is different, so it’s hard to predict all the ways that Groovy can help you with unit tests. The following selection is my personal preference.

2.3.1 Using map-based constructors

If there’s one feature of Groovy that I adore, it’s object creation. Most unit tests create new classes either as test data or as services or helper classes used by the class under test. In a large Java application, a lot of statements are wasted creating such objects.

The next listing presents a Java example of testing a class that takes as its argument a list of persons.

Employee trainee = new Employee();

trainee.setAge(22);

trainee.setFirstName("Alice");

trainee.setLastName("Olson");

trainee.setInTraining(true);

Employee seasoned = new Employee();

seasoned.setAge(45);

seasoned.setFirstName("Alex");

seasoned.setMiddleName("Jones");

seasoned.setLastName("Corwin");

List<Employee> people = Arrays.asList(trainee,seasoned);

Department department = new Department();

department.assign(people);

[...rest of test]

Java needs more than 10 statements to create the two objects that will be used for test input. This boilerplate code is too noisy when compared with the code that tests the Department class.

Listing 2.11 JUnit test with multiple object creation statements

Java object that will be used as test input Filling of fields one by one

Second Java object for test input Filling of different

fields one by one

Class under test Test data is used.

47 Groovy features useful to Spock tests

EASY OBJECT CREATION WITH GROOVY CONSTRUCTORS

This is a well-known problem for Java developers. Sometimes special constructors are created for business domain objects to allow for easy testing. I consider this an antipat- tern. This technique not only shifts verbosity from unit tests to core code, but also has its own shortcomings in the case of multiple constructors. In the preceding example, the Employee class would be polluted with at least two constructors (one that sets the trainee flag and one that ignores it).

Groovy comes to the rescue! Map-based constructors are autogenerated for your Java objects, allowing your Spock tests to initialize any number of fields as well, as shown in the following listing.

when:

Employee trainee = new

Employee(age:22,firstName:"Alice",lastName:"Olson",inTraining:true) Employee seasoned = new Employee(middleName:"Jones",lastName:"Corwin",age:45,firstName:"Alex") List<Employee> people = Arrays.asList(trainee,seasoned)

Department department = new Department() department.assign(people)

[...rest of test]

Without changing a single line of Java code in the Employee class file, I’ve used the map-based constructors, whereby each field is identified by name and the respective value is appended after the semicolon character. Notice that the order of the fields and the set of the fields are completely arbitrary. With this technique,10 you can create a Java object with all possible combinations of its fields in any order that you like!

2.3.2 Using maps and lists in Groovy

The syntax shown in the previous section isn’t specific to constructors. This is the Groovy way of initializing a map. You can use it for creating a map in a single state- ment. The following listing presents an example.

Map<String,Integer> wordCounts = new HashMap<>();

wordCounts.put("Hello",1);

wordCounts.put("Java",1);

wordCounts.put("World",2);

Map<String,Integer> wordCounts2 = ["Hello":1,"Groovy":1,"World":2]

Listing 2.12 Spock test with map-based constructors

10 Groovy supports even-more-concise constructors. They sacrifice clarity, so I refrain from showing them here.

Listing 2.13 Groovy versus Java maps

Java object created with specific field values Another Java

object with different field values

Class under test Test data is used.

Manually filling a map (Java way) Groovy can

create and initialize a map.

You can create any kind of map like these, even those with keys and values that are classes on their own. For Groovy, it makes no difference (see the following listing).

Employee person1 = new

Employee(firstName:"Alice",lastName:"Olson",age:30) Employee person2 = new

Employee(firstName:"Jones",lastName:"Corwin",age:45)

Address address1 = new Address(street:"Marley",number:25) Address address2 = new Address(street:"Barnam",number:7)

Map<Employee,Address> staffAddresses = new HashMap<>();

staffAddresses.put(person1, address1);

staffAddresses.put(person2, address2);

Map<Employee,Address> staffAddresses2 = [(person1):address1,(person2):address2]

As shown in listing 2.13, when classes are used for the keys of the map, you need to use extra parentheses. If you don’t, the classes are assumed to be strings. Also, this concise Groovy syntax creates by default a LinkedHashMap.

In a similar way to maps, Groovy supports a concise syntax for lists. If you’ve ever wondered why it’s so easy to create an array in Java in a single statement but not a list, you’ll be happy to discover that Groovy has you covered. The following listing shows the comparison between Groovy and Java lists.

List<String> races = Arrays.asList("Drazi", "Minbari", "Humans") List<String> races2 = ["Drazi", "Minbari", "Humans"]

assert races == races2

String[] racesArray = ["Drazi", "Minbari", "Humans"]

String[] racesArrayJava = {"Drazi", "Minbari", "Humans"}

Because the syntax of arrays and lists is similar in Groovy, you might find yourself using arrays less and less as you gain experience with Groovy. Notice that the usual way of declaring arrays in Java is one of the few cases where valid Java is invalid Groovy. If you try to create an array in Groovy by using the Java notation, you’ll get an error, because Groovy uses the same syntax for closures, as you’ll see later in this chapter.

Listing 2.14 Groovy maps with nonscalar keys and values

Listing 2.15 Groovy versus Java lists

Creating a Java object by using map-based constructors

Filling a map manually (the Java way) Creating and initializing a map (the Groovy way)

Creating a list with data in Java Creating

a list with data in Groovy

The == operator tests equality in Groovy and not identity. This assert passes.

Creating an array with data in Groovy This is valid Java,

but invalid Groovy.

49 Groovy features useful to Spock tests

Using the knowledge you’ve gained from the preceding sections, you can com- pletely rewrite the JUnit test from listing 2.11, as the following listing shows.

List<Employee> people = [ new Employee(age:22,firstName:"Alice",lastName:"Olson", inTraining:true),

new Employee(middleName:"Jones",lastName:"Corwin",age:45, firstName:"Alex")

]

Department department = new Department() department.assign(people) [...rest of test]

By following Groovy conventions, I’ve replaced 11 Java statements with 1. The unit test is much more readable because it’s clearly split as far as test data creation and test data usage are concerned.

ACCESSING LISTS IN GROOVY BY USING ARRAY INDEX NOTATION

So far, I’ve demonstrated only how maps and lists are initialized. Let’s see how Groovy improves their usage as well, as shown in the next listing.

List<String> humanShips = ["Condor","Explorer"]

assert humanShips.get(0) == "Condor"

assert humanShips[0] == "Condor"

humanShips.add("Hyperion")

humanShips << "Nova" << "Olympus"

assert humanShips[3] == "Nova"

assert humanShips[4] == "Olympus"

humanShips[3] = "Omega"

assert humanShips[3] == "Omega"

Notice how writing and reading to a list uses the same syntax, and only the context defines the exact operation. Again, this syntax is optional, and you’re free to use the Java way of doing things even in Spock tests.

Groovy offers the same array-like syntax for maps as well, as shown in the following listing.

Map<String,String> personRoles = [:]

personRoles.put("Suzan Ivanova","Lt. Commander") personRoles["Stephen Franklin"]= "Doctor"

Listing 2.16 Creating Groovy lists and maps in test code

Listing 2.17 Using Groovy lists

Listing 2.18 Using Groovy maps

Groovy initialization of a list, using map-based constructor objects

Java class under test

Use of test data

Creating a list with two elements Java way

of getting

an element Groovy way of

accessing a list Java way of adding a new element Groovy way

of adding elements

Groovy way of replacing an element

Creating an empty map in Groovy Java way

of inserting

into map Groovy way of

inserting into map

assert personRoles.get("Suzan Ivanova") == "Lt. Commander"

assert personRoles["Stephen Franklin"] == "Doctor"

personRoles["Suzan Ivanova"]= "Commander"

assert personRoles["Suzan Ivanova"] == "Commander"

Lists and maps are one of the many areas where Groovy augments existing Java collec- tions. Groovy comes with its own GDK that sits on top of the existing JDK. You should spend some time exploring the GDK according to your own unit tests and discovering more ways to reduce your existing Java code.

So far, you’ve seen how Groovy enhances classes, fields, and collections. Let’s see how Groovy strings compare to Java strings.

2.3.3 Interpolating text with Groovy strings

I demonstrated Groovy strings (GStrings) at the beginning of this chapter by taking a single Java class and converting it to idiomatic Groovy in a gradual way. At the most basic level, Groovy strings allow for quick text templates of object properties, but they also can handle full expressions, as shown in the following listing.

SimpleDepartment sales = new SimpleDepartment(name:"Sales",location:"block C")

SimpleEmployee employee = new SimpleEmployee(fullName:"Andrew Collins",age:37,department:sales) System.out.println("Age is "+employee.getAge())

println "Age is $employee.age"

System.out.println("Department location

is at "+employee.getDepartment().getLocation())

println "Department location is at $employee.department.location"

println "Person is adult ${employee.age > 18}"

println "Amount in dollars is \$300"

println 'Person is adult ${employee.age > 18}'

When run, this code prints the following:

>groovy GroovyStrings.groovy Age is 37

Age is 37

Department location is at block C Department location is at block C Person is adult true

Amount in dollars is $300

Person is adult ${employee.age > 18}

Groovy string interpolation is certainly powerful, but for unit tests, their multiline capability is more interesting. Similar to other scripting languages, Groovy allows you to split a big string with newlines, as shown in the next listing.

Listing 2.19 Using Groovy strings

Java way of accessing map Groovy way

of accessing

map Groovy way of

replacing element

Creating Java objects with map-based constructors

Java way of accessing fields Groovy

way of accessing fields Using {}

for full expressions

Escaping the

$ character Disabling evaluation altogether with single quotes

51 Reading a test dataset from an external source

def "Another demo for Groovy multiline strings"() { when: "a paragraph is processed"

String input = '''I want you to know you were right. I didn't want \ to admit that. Just pride I guess. You get my age, you \ get kinda set in your ways. It had to be \ done. Don't blame yourself for what happened later.'''

WordDetector wordDetector = new WordDetector();

wordDetector.parseText(input);

then: "word count should be correct"

wordDetector.wordsFound() == 34 }

This is a great feature for unit tests that require text input of three to four lines that can be embedded directly on the source file. Multiline strings also support text inter- polation if you use double quotes instead of single, but inside unit tests, it’s clearer if they’re pure text (without text interpolation). For more lines of text, I also advise using a separate text file, as demonstrated in the next section.

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

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

(306 trang)