If you try using a testing framework called spock framework for unit testing in Java I would like to introduce it because it was more convenient than I expected. Click here for the official website → spock framework
Since there are places where the flow until introduction is a little difficult to understand, I tried to write an article from the first introduction and actually writing a test using spock. I'm glad if you can think of spock yeah.
By the way, my environment is Ubuntu 18.04 LTS + eclipse, Of course you can use spock in Windows environment. Is that so?
OS: Ubuntu 18.04 LTS IDE: eclipse
Since we will write the test code with groovy this time, we will introduce a plugin to handle groovy in eclipse.
Help > Install New Software >
For the URL, refer to this site (groovy-eclipse) and use the one that corresponds to the version of eclipse you are using. Check the Main Package and Finish.
This time, I will use gradle to proceed quickly. Go to any directory where you want to place the source code and run gradle.
$ gradle init --type java-application --test-framework spock
that's all. You now have a project that uses spock for unit testing. Easy!
If it is not new, add the description to the existing build.gradle (pom.xml for maven).
build.gradle
plugins {
id 'groovy'
}
repositories {
jcenter()
}
dependencies {
testImplementation 'org.codehaus.groovy:groovy-all:2.4.17'
testImplementation 'org.spockframework:spock-core:1.0-groovy-2.4'
testImplementation 'junit:junit:4.12'
}
Google Code Archive - Long-term storage for Google Code Project Hosting. It's crumbled, so keep the shaped one.
Spock version | Groovy version | JUnit version | Grails version | Spring version |
---|---|---|---|---|
0.5-groovy-1.6 | 1.6.1-1.6.x | 4.7-4.x | 1.2.0-1.2.x | 2.5.0-3.x |
0.5-groovy-1.7 | 1.7.0-1.7.x | 4.7-4.x | 1.3.0-1.3.x | 2.5.0-3.x |
0.6-groovy-1.7 | 1.7.0-1.7.x | 4.7-4.x | 1.3.0-1.3.x | 2.5.0-3.x |
0.6-groovy-1.8 | 1.8.1-1.8.x | 4.7-4.x | 2.0-2.x | 2.5.0-3.x |
0.7-groovy-1.8 | 1.8.1-1.8.x | 4.7-4.x | 2.0-2.x | 2.5.0-3.x |
0.7-groovy-2.0 | 2.0.0 -2.x.x | 4.7-4.x | 2.2-2.x | 2.5.0-3.x |
1.0-groovy-2.0 | 2.0.0 -2.2.x | 4.7-4.x | 2.2-2.x | 2.5.0-4.x |
1.0-groovy-2.3 | 2.3.0 -2.3.x | 4.7-4.x | 2.2-2.x | 2.5.0-4.x |
1.0-groovy-2.4 | 2.4.0 -2.x.x | 4.7-4.x | 2.2-2.x | 2.5.0-4.x |
Let's write a test as a trial. The location of the test is src / test / groovy / [package Name] /. The extension is .groovy. Make sure it is in the same package as the class under test. The test class name is [Class name to be tested] + Spec. This is because it represents the specification of the class under test even though it is called a unit test. It's my commitment, so I think anything is fine.
Make it like this.
Now, let's implement a method that calculates the tax-included amount (8%) of the consumption tax.
TaxCalculaterSpec.groovy
import spock.lang.Specification
class TaxCalculaterSpec extends Specification {
def sut = new TaxCalculater()
def "calculate:Calculate the tax-included amount"() {
expect:
sut.calculate(100) == 108
}
}
Suddenly I wrote a test. As I said earlier, this is a specification, so the test method name is something that represents the specification. At this point, the build will not pass before the test passes, so write the main code.
TaxCalculater.java
public class TaxCalculater {
public int calculate(int value) {
return 0;
}
}
Well, when I try to run it ...
Condition not satisfied:
sut.calculate(100) == 108
| | |
| 0 false
Yes, the test failed brilliantly. That's right, it fails because it's a method that returns 0.
The point of interest is how this message appears. As a result of executing the method, 0 was returned, so the comparison with 108 became false and the test failed. You can see at a glance. This ease of understanding is one of the attractions of spock.
Now that we've confirmed that the test fails, let's write some code that will pass the test.
TaxCalculater.java
public class TaxCalculater {
public int calculate(int value) {
return 108;
}
}
The test passed. I did it (I didn't do it)
With just one test, you don't know if that test is really valid. Maybe it's just returning a fixed value. Therefore, there are variations in values and situations. With jUnit, you can increase method calls in such cases It's quite annoying because duplicate code is generated, but spock makes it a little easier to read.
TaxCalculaterSpec.groovy
class TaxCalculaterSpec extends Specification {
def sut = new TaxCalculater()
def "Calculate the tax-included amount"() {
expect:
sut.calculate(value) == $result
where:
value |$result
100 |108
200 |216
}
}
where! What a guy! Suddenly, an unknown writing style came out. This where is a notation that allows you to write a combination of values that is written like markdown. The above example shows that the "calculate tax-included amount" method is repeated for the pattern. Since there are two patterns, this test method will be executed twice.
If you run the test in this state, it will fail like this.
Condition not satisfied:
sut.calculate(value) == $result
| | | | |
| 108 200 | 216
| false
216 is the expected value, but 108 is returned, so that's right. That's the implementation. It can not be helped.
So, I will modify the implementation.
TaxCalculater.java
public class TaxCalculater {
public int calculate(int value) {
return (int)(value * 1.08);
}
}
The test is now passed.
Let's add a specification to the method for calculating the tax-included amount. Add a specification that "values after the decimal point included in the tax-included amount are rounded to the first decimal place".
TaxCalculaterSpec.groovy
class TaxCalculaterSpec extends Specification {
def sut = new TaxCalculater()
def "Calculate the tax-included amount"() {
expect:
sut.calculate(value) == $result
where:
value |$result
100 |108
200 |216
and: "Values after the decimal point included in the tax-included amount are rounded to the first decimal place"
111 |120
}
}
Added to the where pattern. 111 is 119.88 when multiplied by 1.08, so the expected value should be 120.
Condition not satisfied:
sut.calculate(value) == $result
| | | | |
| 119 111 | 120
| false
It's been truncated! !! (I knew) So let's put in a rounding process.
TaxCalculater.java
public class TaxCalculater {
public int calculate(int value) {
return (int)(Math.round(value * 1.08));
}
}
In this way, once the specifications are finalized, we will add patterns to the test. It is very easy to see if you write the specifications such as boundary values in the test as described above. And writing a test will give you the courage to refactor.
TaxCalculater.java
public class TaxCalculater {
private final static double taxRate = 1.08;
public int calculate(int value) {
return (int)(Math.round(value * taxRate));
}
}
I put out the tax rate, but the test passes successfully, so there is no problem. Don't you like the fixed variable name being camel case? If so, let's fix it and run the test. It is safe if the test passes.
One of the specifications is that an exception occurs under certain conditions. Let's write a test that raises an exception.
TaxCalculaterSpec.groovy
def "Exception occurs when calculating tax-included amount from negative value"() {
when:
sut.calculate(-1000)
then:
thrown(ApplicationException)
}
It's easy to understand! Let's run a test.
Expected exception of type 'org.omg.CORBA.portable.ApplicationException', but no exception was thrown
at org.spockframework.lang.SpecInternals.checkExceptionThrown(SpecInternals.java:79)
at org.spockframework.lang.SpecInternals.thrownImpl(SpecInternals.java:66)
at spockSampleProject.TaxCalculaterSpec.Exception occurs when calculating tax-included amount from negative value(TaxCalculaterSpec.groovy:27)
The test failed that the ApplicationException was supposed to be thrown but wasn't.
TaxCalculater.java
public int calculate(int value) throws ApplicationException {
if (value < 0) {
throw new ApplicationException(null, null);
}
return (int) (Math.round(value * taxRate));
}
If you implement it to throw an exception if it is negative and rerun the test, everything will succeed. I did it (I did it)
If you decide to write the specification for the method name of the test class in Japanese, the report will be much easier to read. If you look at this, you can see the specifications. Personally, it's much easier to read than javadoc, so I don't have to write javadoc. I'm thinking of writing a unit test properly.
I tried test-driven programming using the spock framework, What do you think.
I would be happy if you could convey the goodness of being able to write the specifications clearly and easily. In addition to the contents introduced this time, it is quite powerful that you can write mocking tests without using a mock library such as mockit. Of course, you can also test your data in combination with DbUnit. I would like to write these articles soon.