Streamline Java testing with Spock

Introduction

The sample code in this text has been validated in the following environment:


What is Spock?

Spock is a testing and specification framework for Java and Groovy applications. Compared to other tools, it features a beautiful and expressive specification language. https://spock-framework-reference-documentation-ja.readthedocs.io/ja/latest/introduction.html


What is different from JUnit?

The amount of test coding is overwhelmingly small compared to JUnit! In other words, you can simply increase the efficiency of your unit tests by reducing the amount of coding.

So how can you reduce the amount of test coding?


Tests can be coded in groovy

Spock writes tests in groovy. Groovy's linguistic characteristics allow for lighter coding than Java. If the build is gradle, both will be groovy, so I think it will be quick to learn.

Groovy is a dynamic programming language that runs on the Java platform. https://ja.wikipedia.org/wiki/Groovy

For those who have never used groovy, learning costs may be a burden. However, groovy can write Java code almost as it is, so if you don't know the worst groovy code, you can write it in Java.

How different is the same test coded in JUnit and Spock (groovy)? There was sample code published on Spock's github, so here is an example of coding the same test in JUnit.

JUnit


import static org.junit.Assert.assertEquals;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.h2.Driver;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class DatabaseDrivenTest {

	private static Connection con;

	@BeforeClass
	public static void setup() throws Exception {
		Driver.load();
		con = DriverManager.getConnection("jdbc:h2:mem:");

		Statement stmt = con.createStatement();
		stmt.execute("create table maxdata (id int primary key, a int, b int, c int)");
		stmt.execute("insert into maxdata values (1, 3, 7, 7), (2, 5, 4, 5), (3, 9, 9, 9)");
		stmt.close();
	}

	@Test
	public void testMaximumOfTwoNumbers() throws Exception {
		// Math.max(a, b) == c;
		Statement stmt = con.createStatement();
		ResultSet rs = stmt.executeQuery("select a, b, c from maxdata");
		rs.next();
		rs.getInt("a");
		assertEquals(Math.max(rs.getInt("a"), rs.getInt("b")), rs.getInt("c"));

		stmt.close();
	}

	@AfterClass
	public static void after() throws SQLException {
		con.close();
	}
}

Here is the code for Spock. https://github.com/spockframework/spock-example/blob/master/src/test/groovy/DatabaseDrivenSpec.groovy

You'll find that groovy is overwhelmingly simple.


Streamline combination testing with data-driven testing

Another powerful mechanism is the data launch test. Iterative testing of parameter and result combinations can be easily achieved.

import spock.lang.*

@Unroll
class DataDrivenSpec extends Specification {
  def "minimum of #a and #b is #c"() {
    expect:
    Math.min(a, b) == c //Test execution and result verification

    where:
    a | b || c    //Parameter 1|Parameter 2||result
    3 | 7 || 3
    5 | 4 || 4
    9 | 9 || 9
  }
}

This code is a test with the following content: The result of executing Math.min (a, b) with a = 3 and b = 7 is 3 The result of executing Math.min (a, b) with a = 5 and b = 3 is 4 The result of executing Math.min (a, b) with a = 9 and b = 9 is 9

If you want to increase the variation of parameters and results, you can imagine adding a pattern to where. It is a form that is easy to convert from the pattern table of the combination test created in Excel.


Error report

If the test fails, an error report will be printed to the console. It's kinder than Java's error stack trace.

Condition not satisfied:

validator.validate() == true
|         |          |
|         false      false
<Validator@5aa9e4eb>

	at ValidatorSpec.validate test(ValidatorSpec.groovy:40)


Easy to mock

Spock also has a mocking function.

Mock HttpServletRequest and rewrite the return value of getRequestURI to "/some/path.html".

def request = Mock(HttpServletRequest)
request.getRequestURI() >> "/some/path.html"

However, there are various restrictions on mocking, so that point will be described later.


Introduction method

You can install it with gradle or maven. Please note that there is a version dependency with groovy.

gradle


testCompile group: 'org.spockframework', name: 'spock-core', version: '1.3-groovy-2.5'
testCompile group: 'org.objenesis', name: 'objenesis', version: '3.0.1'
testCompile group: 'cglib', name: 'cglib', version: '3.2.10'

Maven


<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-core</artifactId>
    <version>1.3-groovy-2.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.objenesis</groupId>
    <artifactId>objenesis</artifactId>
    <version>3.0.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.10</version>
</dependency>


gradle setup example

build.gradle


apply plugin: 'java'
apply plugin: 'groovy'

sourceSets {
	test {
		java {
			srcDir 'src/test/groovy'
		}
	}
}

repositories {
    mavenCentral()
}

dependencies {
    testCompile group: 'org.spockframework', name: 'spock-core', version: '1.3-groovy-2.5'
    testCompile group: 'org.objenesis', name: 'objenesis', version: '3.0.1'
    testCompile group: 'cglib', name: 'cglib', version: '3.2.10'
}

Please note that the source directory must be set to'src / test / groovy'.


Eclipse Plugins You need to install the following plug-ins to use it in Eclipse. Please install from Market Place.

Spock Plugin http://marketplace.eclipse.org/content/spock-plugin

Groovy Development Tools http://marketplace.eclipse.org/content/groovy-development-tools


Spock basics

The basic form of the test looks like this.

class ValidatorSpec extends Specification {
    @Unroll
    def "Sample test param1=#param1 param2=#param2" () {
       setup: // 1.Preprocessing
            def target =new Tested class()
            def mockTarget = Mock(Class you want to mock)
            mockTarget.method() >>Value you want to rewrite
            target.mockTarget = mockTarget //Assign a mock to a member variable of the class under test
        expect: // 2.Execution of the test target
            def returnObj = target.targetMethod(param1, param2)
        then: // 3.Verification of results
            returnObj == result
            1 * mockTarget.method()
        where: // 4
.Parameter and result combination
            | param1 | param2 || result
            | "a"    | 1      || "ok"
            | "b"    | 20     || "ng"
    }
}
  1. In the pre-processing, the test target class is generated and the mock is generated.
  2. Here, execute the test target method of the test target class.
  3. Verify the execution result of the test handling method. Validation Validation is evaluated by boolean.
  4. Define a combination of parameters and results with a pipe connection. The point is that param1, param2, and result do not have variable declarations.

Block type

There are the following types of blocks:

block Description
expect Test execution and result verification(when + then)
where Parameters and results
when Run the test target
then Verification of results
setup(given) Pre-processing
cleanup post process

Supports legacy code testing in combination with JMockit

There are tests that Spock alone cannot do, such as static, final, and private constructors used in legacy code. If you need these tests, we recommend using JMockit.

 setup:
    new MockUp<LegacyUtil>() {
        @Mock
        public boolean check() {
            return true
        }
    }

Cases where the effect of Spock cannot be expected

100% test coverage is required

Aiming for 100% test coverage is very difficult, and achieving 100% does not mean high quality. However, there are actually development projects that are required as one of the quality index values. It is difficult to achieve with spock alone, so please make full use of jMockit or use JUnit together.

Classes that are difficult to test even with JUnit

The principle is to design a class that is easy to test, but I think that there are many opportunities to encounter spaghetti code at the actual development site. Please refactor it to make the test easier.

Reference site

Spock http://spockframework.org

Spock Framework Reference Document https://spock-framework-reference-documentation-ja.readthedocs.io/ja/latest/

Spock sample code https://github.com/spockframework/spock-example

Recommended Posts

Streamline Java testing with Spock
Testing with com.google.testing.compile
Install java with Homebrew
Change seats with java
Install Java with Ansible
Testing model with RSpec
Comfortable download with JAVA
Switch java with direnv
Download Java with Ansible
Let's scrape with Java! !!
Build Java with Wercker
Endian conversion with JAVA
Easy BDD with (Java) Spectrum?
Use Lambda Layers with Java
Java multi-project creation with Gradle
Getting Started with Java Collection
Java Config with Spring MVC
Basic Authentication with Java 11 HttpClient
Let's experiment with Java inlining
Run batch with docker-compose with Java batch
[Template] MySQL connection with Java
Rewrite Java try-catch with Optional
Install Java 7 with Homebrew (cask)
[Java] JSON communication with jackson
Java to play with Function
Enable Java EE with NetBeans 9
[Java] JavaConfig with Static InnerClass
Let's operate Excel with Java! !!
Version control Java with SDKMAN
RSA encryption / decryption with java 8
Paging PDF with Java + PDFBox.jar
[Java] Content acquisition with HttpCliient
Java version control with jenv
Troubleshooting with Java Flight Recorder
Connect to DB with Java
Connect to MySQL 8 with Java
Error when playing with java
Using Mapper with Java (Spring)
Java study memo 2 with Progate
REST API testing with REST Assured
Getting Started with Java Basics
When you want to implement Java library testing in Spock with multi-module in Gradle in Android Studio 3
Seasonal display with Java switch
Use SpatiaLite with Java / JDBC
Study Java with Progate Note 1
Compare Java 8 Optional with Swift
HTML parsing with JAVA (scraping)
Run Java VM with WebAssembly
Screen transition with swing, java
Java unit tests with Mockito
A story about having a hard time aligning a testing framework with Java 6
[Java 8] Duplicate deletion (& duplicate check) with Stream
Java lambda expressions learned with Comparator
Build a Java project with Gradle
Install java with Ubuntu 16.04 based Docker
Java to learn with ramen [Part 1]
Morphological analysis in Java with Kuromoji
Use java with MSYS and Cygwin
Distributed tracing with OpenCensus and Java
100% Pure Java BDD with JGiven (Introduction)
Automatic API testing with Selenium + REST-Assured