Welcome
Welcome to the Test training for Acme
Structure
-
09:00 - 16:30? (approximately)
-
Break 45 minutes?
-
Means: 3 + 3.5 hours = 6.5 hours = 13 halve hours - 2 for slip = 11 points
-
Means that we cover a lot regarding testing which is planned during previous setup-meetings
-
Basic knowledge of Java development
-
Basic knowledge of Maven
-
Basic knowledge of Spring Boot
(If not explain some)
Release Notes
-
Improve the Spring Boot section after they changed their starter page
-
Remove more ing names
-
Alter companyName to Acme
-
More imporovmements
-
Tip for Intellij (Reimport). Clearer sentence regarding field Calculator calculator. Change Holder to Client
-
Everything is new here! :-)
Introduction
-
Who are you
-
What is your target of this day
-
When is this day successful for you
-
Topics - see TOC
-
Video - will be recorded during sessions and distributed later
-
Sourcecode for referall on Github - will be available later
Topic: Concepts
What is a good test
-
What were you testing?
-
What should it do?
-
What was the output (actual behavior)?
-
What was the expected output (expected behavior)?
Why testing
Testing shows the presence of errors, not the absence
Software Testing is necessary because we all make mistakes. Some of those mistakes are unimportant, but some of them are expensive or dangerous. We need to check everything and anything we produce because things can always go wrong – humans make mistakes all the time
There is always a balance in testing, is a term coined by some managers. You cannot test everything since all testing will cost a lot. Personally I feel different on this. Testing should be done for all since everything we do not test will fail or the consumer will test it!
Tips from Pragmatic Programmer
Which means as much that you should stop the program when something bad happens instead of letting that error getting his own life. Hence, use exceptions well and Crash Early
Which means as much as that If it can’t happen, use assertions to ensure that it won’t
| Leave assertions turned on, even in production although it might lead to a lot of discussion. Let’s say there are two schools about that. One says if assertions are failing in development that should surely fail in production. Other says: we should disable assertions since there is a separate error handling in production. |
Which means as much as - as a programmer - do not overuse exceptions. Having a 0(zero) passed in to a functions which divides let’s say an int over that 0 should not throw an exception but should be handled in an other / previously way.
Which means as musch as to write code which is testable. Sometimes even removing some security to test it. Although that also might lead to discussion. By changing a private method in Java to default access it is testable using a Unittest in the same package (although in src/test/java folder). The win of being able to test that method / unit is greater than the loss of security.
Use some contract up front to test (kind a acceptance-criteria in Scrum)
When you design a module, or even a single routine, you should design both it’s contact and the code to test that contract.
Which means as much as: all software you write will be tested - if not by you and your team, then by the eventual users - so you might as wel plan on tesing it thoroughly. Test your software before those small little fish become big sharks
Which means as much as to use a build system like Maven, Gradle which executes the unittests per build
Test early, test often, test automatically and coding ain’t done tll all the tests run (successfully)
Subtotal points: 1
Test pyramid
Topic: Configuration / Validation
-
Java Oracle JDK (11+)
-
IntelliJ / Eclipse
-
Maven (might be in IntelliJ)
Topic: Introducing Spring Boot
The trainer will introduce Spring Boot since we are going to work with it today. To have a clue is enough for this topic
Demo and (later) Assignment: Create an empty Spring Boot project
-
Group: nl.acme
-
Artifact: testing
-
Name: Testing Training
-
Description: This is for a description
-
Package name: nl.acme.testing
-
Packaging: jar
-
Java version: 11
Click See all
-
Core
-
Devtools
-
-
Web
-
Web
-
Rest Repositories
-
-
Rest Docs
-
SQL
-
JPA
-
MySQL
-
H2
-
-
Ops
-
Actuator
-
| If you omit or forget one, no problem, we can fix that later! |
Below at the button click
to update the dependencies and go back to the main page
Below at the button click
to download the zip file
Move the download zip file to a convenient location
| Convenient means handy to reach: so /Users/<username>/project is OK but C:\Program Files\Maven\bin\myprojects\v3.1.2\testing would be unwise :-) |
Unzip the file at that location
Enter the created directory!
$ mvn clean validate
⇒ Should print BUILD SUCCESS
$ mvn clean package
⇒ Should print BUILD SUCCESS and should result in a .jar file in the target subdirectory
Subtotal points: 3
Topic: Unittesting
In computer programming, unit testing is a software testing method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures, are tested to determine whether they are fit for use
It is imperative to remind that you see that whole as a unit. Phrased otherwise, you cannot split the test in half because than there would be nothing to test anymore. Hence, a unittest is atomic. It is not perse one method but mostly you see in Java language a unittest for one method. (but a get and a set can also be tested by a unittest, since we see that get and set as a unit)
Below you find an example
package nl.ing.testing.tools;
public class BasicCalculator {
public int add(int n, int m) {
return n+m;
}
public int subtract(int n, int m) {
return n-m;
}
public int multiply(int n, int m) {
return n*m;
}
public double divide(int n, int m) {
return (double) n / (double) m;
}
}
package nl.ing.testing.tools;
import org.aspectj.lang.annotation.Before;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class BasicCalculatorTest {
private BasicCalculator basicCalculator;
@BeforeEach
public void setUp() {
this.basicCalculator = new BasicCalculator();
}
@Test
public void testAdd() {
Assertions.assertEquals(7, this.basicCalculator.add(3,4));
}
@Test
public void testSubtract() {
Assertions.assertEquals(3, this.basicCalculator.subtract(7,4));
}
@Test
public void testMultiply() {
Assertions.assertEquals(15, this.basicCalculator.multiply(3,5));
}
@Test
public void testDivide() {
Assertions.assertEquals(0.6, this.basicCalculator.divide(3,5), 0.1);
}
}
| Sometimes IntelliJ gets confused. Sometimes missing libraries as JUnit and so. |
-
View / Tool windows / Maven
-
In the upper left of that window click the Reimport button
Assignment: Create your first unittest
-
The code above
-
I have a basic calculator
-
And I invoke the add method
-
The result should be mathematically correct
Subtotal points: 4
Topic: Mocking
-
Doubles
-
Stub
-
Mock
-
Topic: Mocking with Mockito
Use the slides and ASSIGNMENT from this Mocking with Mockito page
| During and After this assignment you should validate for yourself that you know that we have Mocked out the Calculator interface which is NOT THE SAME as the BasicCalculator in the Assignment on Unittesting! |
Subtotal points: 6
Topic: Coverage
Introduction
During this section we will learn why, when, what and how to implement and use coverage
Why Coverage
Why Coverage? To see and show (to management mostly) that we have pretty good insight about the real quality of our code
What is Coverage
Coverage is the amount (expressed in percentages) of code which is covered during some test. We are talking about production-code lines (in src/main/java) here and not (src/test/java) So in fact … every test should run some productioncode. That amount is expressed as Code Coverage
How to calculate Coverage
Using an IDE
-
Can show coverage when you add the EclEmma plugin
-
Shows coverage if you install the coverage plugin
Using a Maven reporting tool
$ mvn clean cobertura:cobertura
| Cobertura is pretty unusable since it is onlu usable up to Java 7 (Lambda expressions fail) |
We will use Jacoco ⇒
Using Jacoco
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.2</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
$ mvn clean test
$ mvn jacoco:report
-
Based on the code above
-
Add jacoco to your Maven configuration
-
Run the test goal
-
Create a report
Subtotal points: 8
Bonus Assignment: Add the minimum coverage percentage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<!-- element can also be PACKAGE or LINES -->
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.50</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
$ mvn clean verify # the verify phase is the Maven phase in which the jacoco:check goal is run
Based on the code above, please implement this to your build
Topic: Integrationtesting
Explain what integrationtesting is
Topic: Spring Boot integrationtesting
Topic: Spring Boot short introduced
These topics will be shortly explained by the trainer
-
application.properties
-
@Component
-
@Autowired
-
@Bean
-
@ActiveProfiles("name of your current profile since we are running integrationtests")
-
e.g. @ActiveProfiles("integrationtest") ⇒ Spring boot will read the application-integrationtest.properties after reading the application.properties file
-
-
@SpringBootTest(…)
-
@SpringBootTest(classes=YourSpringBootApplication.class, webEnvironment=WebEnvironment…)
-
Tells Spring Boot that on location YourSpringBootApplication.class the wiring should begin
-
-
Unittest have filename with postfix Test.java
-
IntegrationTests have filename with postfix IT.java
-
We should use the Maven failsafe plugin to run the integrationtests
-
which are run during the verify phase of the Maven build
-
-
Rename the test in src/test/java which are created by default from …Test to …IT (which means, that test is now an integrationtest, which in fact it already was)
-
In fact you might call that a bug from the Spring Boot generator
-
-
Add the Maven failsafe plugin to the build section of pom.xml
-
Which results in the fact that the IT files are seen as integration tests by Maven
-
That plugin might already be in, but still you have to know that he is there
-
Testable using: $ mvn dependency:tree (enter)
-
-
-
Change the first version of your sofware from 0.0.1-SNAPSHOT to 0.1.0-SNAPSHOT
-
Why? You might read some regarding Semantic Versioning
-
Spring Boot integrationtest Demo
-
Add H2 depencency to pom.xml
-
We will create a class BankAccount - the model
-
We will create a class BankAccountService - the service with business logic
-
We will create a class BankAccountRepository - the interface which saves the data to DB
-
We will create an application-integrationtest.properties file to src/test/resources/
-
Which is used to persist to an in-memory db instead of real (MySQL) data
-
-
We will create a BankAccountServiceIT integration test
-
Which (integration) tests the full path from service to repo to DB
-
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package nl.ing.testing.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class BankAccount {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String bankAccountNumber;
private double saldo;
private boolean savingsAccount;
public boolean isSavingsAccount() {
return savingsAccount;
}
public void setSavingsAccount(boolean savingsAccount) {
this.savingsAccount = savingsAccount;
}
public long getId() {
return id;
}
public String getBankAccountNumber() {
return bankAccountNumber;
}
public double getSaldo() {
return saldo;
}
public void setBankAccountNumber(String bankAccountNumber) {
this.bankAccountNumber = bankAccountNumber;
}
public void setSaldo(double saldo) {
this.saldo = saldo;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package nl.ing.testing.service;
import nl.ing.testing.model.BankAccount;
import nl.ing.testing.persistence.BankAccountRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.Optional;
@Service
@Transactional
public class BankAccountService {
@Autowired
private BankAccountRepository repository;
public BankAccount insert(BankAccount newBankAccount) {
return this.repository.save(newBankAccount);
}
public Optional<BankAccount> findById(long id) {
return this.repository.findById(id);
}
public BankAccount update(long id, BankAccount data) {
BankAccount target = this.repository.findById(id).get();
target.setBankAccountNumber(data.getBankAccountNumber());
target.setSaldo(data.getSaldo());
return this.repository.save(target);
}
public boolean deleteById(long id){
if(this.repository.findById(id).isPresent()) {
this.repository.deleteById(id);
return true;
}
else {
return false;
}
}
public long count() {
return this.repository.count();
}
public void setAllBankAccountsToZeroes() {
this.repository.setAllBankAccountsToZeroes();
}
public Iterable<BankAccount> findAll() {
return this.repository.findAll();
}
public void setAllBankAccountsToSavingsAccount() {
this.repository.setAllBankAccountsToSavingsAccount();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package nl.ing.testing.persistence;
import nl.ing.testing.model.BankAccount;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface BankAccountRepository extends CrudRepository <BankAccount, Long> {
@Query("update BankAccount set saldo='0'")
@Modifying
public void setAllBankAccountsToZeroes();
@Query("update BankAccount set savingsAccount=true")
@Modifying
public void setAllBankAccountsToSavingsAccount();
}
1
2
3
4
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
#nog niet nodig spring.datasource.password=
spring.datasource.driverClassName=org.h2.Driver
| Now we are ready to add the BankAccountServiceI(ntegretation)T(est) |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package nl.ing.testing.service;
import nl.ing.testing.Application;
import nl.ing.testing.model.BankAccount;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.ActiveProfiles;
@ActiveProfiles("integrationtest")
@SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.RANDOM_PORT)
public class BankAccountServiceIT {
@Autowired
private BankAccountService bankAccountService;
@Test
public void testCrud() {
BankAccount nieuwe = new BankAccount();
nieuwe.setSavingsAccount(true);
nieuwe.setBankAccountNumber("Schaken");
BankAccount bankAccountCreated = this.bankAccountService.insert(nieuwe);
Assertions.assertTrue(bankAccountCreated.getId() != 0);
long id = bankAccountCreated.getId();
BankAccount opgehaald = this.bankAccountService.findById(id).get(); // rloman refactor
Assertions.assertEquals(nieuwe.getBankAccountNumber(), opgehaald.getBankAccountNumber());
opgehaald.setBankAccountNumber("10.21.33.44.555");
BankAccount opgehaaldNaSaven = this.bankAccountService.update(id, opgehaald);
Assertions.assertEquals("10.21.33.44.555", opgehaaldNaSaven.getBankAccountNumber());
this.bankAccountService.deleteById(id);
Assertions.assertFalse(this.bankAccountService.findById(id).isPresent());
}
@Test
public void testDeletingNonExisting() {
BankAccount nieuwe = new BankAccount();
nieuwe.setSavingsAccount(true);
nieuwe.setBankAccountNumber("Schaken");
BankAccount bankAccountCreated = this.bankAccountService.insert(nieuwe);
Assertions.assertTrue(bankAccountCreated.getId() != 0);
long id = bankAccountCreated.getId();
BankAccount opgehaald = this.bankAccountService.findById(id).get(); // rloman refactor
Assertions.assertEquals(nieuwe.getBankAccountNumber(), opgehaald.getBankAccountNumber());
opgehaald.setBankAccountNumber("Schaken en Dammen");
BankAccount opgehaaldNaSaven = this.bankAccountService.update(id, opgehaald);
Assertions.assertEquals("Schaken en Dammen", opgehaaldNaSaven.getBankAccountNumber());
Assertions.assertTrue(this.bankAccountService.deleteById(id));
Assertions.assertFalse(this.bankAccountService.deleteById(id));
}
@Test
public void testSetAllBankAccountsToZero() {
long init = this.bankAccountService.count();
for (int i = 0; i < 3; i++) {
BankAccount nieuwe = new BankAccount();
nieuwe.setSavingsAccount(false);
nieuwe.setBankAccountNumber(String.valueOf(Math.random()* 100_000_000));
this.bankAccountService.insert(nieuwe);
}
Assertions.assertEquals(init+3, this.bankAccountService.count());
this.bankAccountService.setAllBankAccountsToZeroes();
this.bankAccountService.findAll().forEach(s -> {
Assertions.assertEquals(0, s.getSaldo(), 0.01);
});
Assertions.assertEquals(3, this.bankAccountService.count());
}
@Test
public void testSetAllBankAccountsToSavingsAccount() {
long init = this.bankAccountService.count();
for (int i = 0; i < 3; i++) {
BankAccount nieuwe = new BankAccount();
nieuwe.setSavingsAccount(false);
this.bankAccountService.insert(nieuwe);
}
this.bankAccountService.setAllBankAccountsToSavingsAccount();
this.bankAccountService.findAll().forEach(s -> {
Assertions.assertTrue(s.isSavingsAccount());
});
Assertions.assertEquals(init+3, this.bankAccountService.count());
}
}
Spring Boot integrationtest Assignment I
Based on the code above implement the BankAccountServiceIT.java
| We perform this assignment to have a running integrationtest in your environment. In fact the first, mental creation. The next, the second physical creation will be the following assignment where you will learn how to make one yourself!!! |
Spring Boot integrationtest Assignment II
When the BankAccountServiceIT successfully runs, now please create an integrationtest for a NEW domain class Client (Dutch: Rekeninghouder / Client) using the same principles as used for the BankAccount. Be creative and assume some properties of the Client for yourself
Subtotal points: 11