first commit: pure unit tests and cucumber bdd tests with database integration
This commit is contained in:
commit
46eec5da55
16 changed files with 585 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.idea/
|
||||
Reports/
|
||||
target/
|
||||
*.iml
|
||||
.DS_Store
|
||||
|
||||
91
pom.xml
Normal file
91
pom.xml
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>nl.sander</groupId>
|
||||
<artifactId>testautomatisering</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>testautomatisering</name>
|
||||
<description>POC van dynamische (testgedreven) manier om met dependency injection van mocks om te gaan</description>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.200</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.inject</groupId>
|
||||
<artifactId>guice</artifactId>
|
||||
<version>4.2.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
<version>0.9.11</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>1.10.19</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cucumber</groupId>
|
||||
<artifactId>cucumber-core</artifactId>
|
||||
<version>5.0.0-RC4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cucumber</groupId>
|
||||
<artifactId>cucumber-java</artifactId>
|
||||
<version>5.0.0-RC4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cucumber</groupId>
|
||||
<artifactId>cucumber-java8</artifactId>
|
||||
<version>5.0.0-RC4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cucumber</groupId>
|
||||
<artifactId>cucumber-junit</artifactId>
|
||||
<version>5.0.0-RC4</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M4</version>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>**/*Tests.java</include>
|
||||
<include>**/*Test.java</include>
|
||||
</includes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
118
src/main/java/nl/sander/testautomation/cars/Car.java
Normal file
118
src/main/java/nl/sander/testautomation/cars/Car.java
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
package nl.sander.testautomation.cars;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Car {
|
||||
|
||||
private long id;
|
||||
|
||||
private String brand;
|
||||
private String model;
|
||||
private String color;
|
||||
private int year;
|
||||
|
||||
public static CarBuilder builder(){
|
||||
return new CarBuilder();
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getBrand() {
|
||||
return brand;
|
||||
}
|
||||
|
||||
public void setBrand(String brand) {
|
||||
this.brand = brand;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(String color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public int getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public void setYear(int year) {
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Car car = (Car) o;
|
||||
return id == car.id &&
|
||||
year == car.year &&
|
||||
brand.equals(car.brand) &&
|
||||
model.equals(car.model) &&
|
||||
color.equals(car.color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, brand, model, color, year);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Car{" +
|
||||
"id=" + id +
|
||||
", brand='" + brand + '\'' +
|
||||
", model='" + model + '\'' +
|
||||
", color='" + color + '\'' +
|
||||
", year=" + year +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static class CarBuilder{
|
||||
private final Car car = new Car();
|
||||
|
||||
public CarBuilder withId(long id){
|
||||
car.setId(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CarBuilder withBrand(String brand){
|
||||
car.setBrand(brand);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CarBuilder withModel(String model){
|
||||
car.setModel(model);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CarBuilder withColor(String color){
|
||||
car.setColor(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CarBuilder withYear(int year){
|
||||
car.year=year;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Car build(){
|
||||
return car;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/main/java/nl/sander/testautomation/cars/CarDao.java
Normal file
13
src/main/java/nl/sander/testautomation/cars/CarDao.java
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package nl.sander.testautomation.cars;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
public interface CarDao {
|
||||
|
||||
Car getCar(long carId) throws SQLException;
|
||||
|
||||
List<Car> getAllCars() throws SQLException;
|
||||
|
||||
void store(Car car) throws SQLException;
|
||||
}
|
||||
12
src/main/java/nl/sander/testautomation/cars/CarModule.java
Normal file
12
src/main/java/nl/sander/testautomation/cars/CarModule.java
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package nl.sander.testautomation.cars;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
public class CarModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure(){
|
||||
bind(CarDao.class).to(JdbcCarDao.class);
|
||||
bind(CarService.class).toInstance(new CarService());
|
||||
}
|
||||
}
|
||||
19
src/main/java/nl/sander/testautomation/cars/CarService.java
Normal file
19
src/main/java/nl/sander/testautomation/cars/CarService.java
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package nl.sander.testautomation.cars;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CarService {
|
||||
|
||||
@Inject
|
||||
private CarDao carDao;
|
||||
|
||||
public Optional<Car> getCar(long carId) {
|
||||
try {
|
||||
return Optional.of(carDao.getCar(carId));
|
||||
} catch (SQLException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
74
src/main/java/nl/sander/testautomation/cars/JdbcCarDao.java
Normal file
74
src/main/java/nl/sander/testautomation/cars/JdbcCarDao.java
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package nl.sander.testautomation.cars;
|
||||
|
||||
import nl.sander.testautomation.database.Database;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class JdbcCarDao implements CarDao {
|
||||
|
||||
@Inject
|
||||
private Database database;
|
||||
|
||||
@Override
|
||||
public Car getCar(long carId) throws SQLException {
|
||||
try (Connection connection = createConnection()) {
|
||||
PreparedStatement statement = connection.prepareStatement("select brand, model, color, year from cars where id = ?");
|
||||
statement.setLong(1, carId);
|
||||
ResultSet result = statement.executeQuery();
|
||||
if (result.next()) {
|
||||
Car car = new Car();
|
||||
car.setId(carId);
|
||||
car.setBrand(result.getString("brand"));
|
||||
car.setModel(result.getString("model"));
|
||||
car.setColor(result.getString("color"));
|
||||
car.setYear(result.getInt("year"));
|
||||
return car;
|
||||
} else {
|
||||
throw new IllegalArgumentException("no car found with id " + carId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void store(Car car) throws SQLException {
|
||||
try (Connection connection = createConnection()) {
|
||||
PreparedStatement statement = connection.prepareStatement("insert into cars (id, brand, model, color, year) " +
|
||||
"values(?,?,?,?,?)");
|
||||
statement.setLong(1, car.getId());
|
||||
statement.setString(2, car.getBrand());
|
||||
statement.setString(3, car.getModel());
|
||||
statement.setString(4, car.getColor());
|
||||
statement.setInt(5, car.getYear());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Car> getAllCars() throws SQLException {
|
||||
try (Connection connection = createConnection()) {
|
||||
PreparedStatement statement = connection.prepareStatement("select id, brand, model, color, year from cars");
|
||||
ResultSet result = statement.executeQuery();
|
||||
List<Car> cars = new ArrayList<>();
|
||||
while (result.next()) {
|
||||
cars.add(Car.builder()
|
||||
.withId(result.getLong("id"))
|
||||
.withBrand(result.getString("brand"))
|
||||
.withModel(result.getString("model"))
|
||||
.withColor(result.getString("color"))
|
||||
.withYear(result.getInt("year"))
|
||||
.build());
|
||||
|
||||
}
|
||||
return cars;
|
||||
}
|
||||
}
|
||||
|
||||
private Connection createConnection() throws SQLException {
|
||||
return new Database().getConnection();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package nl.sander.testautomation.database;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class Database {
|
||||
|
||||
private final Connection connection;
|
||||
|
||||
public Database() throws SQLException {
|
||||
this.connection = DriverManager.getConnection("jdbc:h2:~/test", "sa", "");
|
||||
}
|
||||
|
||||
public Connection getConnection() throws SQLException {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
16
src/test/java/nl/sander/testautomation/CucumberTests.java
Normal file
16
src/test/java/nl/sander/testautomation/CucumberTests.java
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package nl.sander.testautomation;
|
||||
|
||||
import io.cucumber.junit.Cucumber;
|
||||
import io.cucumber.junit.CucumberOptions;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@RunWith(Cucumber.class)
|
||||
@CucumberOptions(
|
||||
strict = true,
|
||||
features = {"src/test/java/nl/sander/testautomation/tests"},
|
||||
glue = {"nl.sander.testautomation.steps"},
|
||||
plugin = {"pretty", "html:Reports/cucumber-pretty"}
|
||||
)
|
||||
public class CucumberTests {
|
||||
|
||||
}
|
||||
7
src/test/java/nl/sander/testautomation/Mode.java
Normal file
7
src/test/java/nl/sander/testautomation/Mode.java
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package nl.sander.testautomation;
|
||||
|
||||
public enum Mode {
|
||||
UNIT_TEST,
|
||||
UNIT_INTEGRATION_TEST,
|
||||
DATABASE_INTEGRATION_TEST
|
||||
}
|
||||
29
src/test/java/nl/sander/testautomation/UnitTest.java
Normal file
29
src/test/java/nl/sander/testautomation/UnitTest.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package nl.sander.testautomation;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import nl.sander.testautomation.cars.CarModule;
|
||||
|
||||
import static com.google.inject.util.Modules.override;
|
||||
|
||||
public interface UnitTest {
|
||||
|
||||
Injector injector = Guice.createInjector(
|
||||
override(new CarModule()).with(new UnitTestModule()));
|
||||
|
||||
default <T> T classToTest(Class<T> type) {
|
||||
try {
|
||||
T instance = type.newInstance();
|
||||
injector.injectMembers(instance);
|
||||
return instance;
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
default <T> T mock(Class<T> type) {
|
||||
return injector.getInstance(type);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
28
src/test/java/nl/sander/testautomation/UnitTestModule.java
Normal file
28
src/test/java/nl/sander/testautomation/UnitTestModule.java
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package nl.sander.testautomation;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import org.mockito.Mockito;
|
||||
import org.reflections.Reflections;
|
||||
import org.reflections.scanners.SubTypesScanner;
|
||||
import org.reflections.util.ClasspathHelper;
|
||||
import org.reflections.util.ConfigurationBuilder;
|
||||
|
||||
/**
|
||||
* mocks all classes in the SUT
|
||||
*
|
||||
* Works but could be optimized by only searching for types/annotations
|
||||
*/
|
||||
public class UnitTestModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void configure() {
|
||||
new Reflections(new ConfigurationBuilder()
|
||||
.setUrls(ClasspathHelper.forPackage("nl.sander.testautomation.cars"))
|
||||
.setScanners(new SubTypesScanner(false))).getSubTypesOf(Object.class)
|
||||
.stream()
|
||||
.map(Class.class::cast)
|
||||
.forEach(c -> bind(c).toInstance(Mockito.mock(c)));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package nl.sander.testautomation.steps;
|
||||
|
||||
|
||||
import io.cucumber.java.Before;
|
||||
import nl.sander.testautomation.Mode;
|
||||
|
||||
public class StepDefinitions {
|
||||
|
||||
public static Mode testMode;
|
||||
|
||||
@Before(value = "@DatabaseIntegrationTest", order = 1)
|
||||
public void databaseIntegrationTest() {
|
||||
testMode = Mode.DATABASE_INTEGRATION_TEST;
|
||||
}
|
||||
|
||||
// @Before(value = "@UnitTest", order = 2)
|
||||
// public void unitTest() {
|
||||
// testMode = Mode.UNIT_TEST;
|
||||
// }
|
||||
//
|
||||
// @Before(value = "@UnitIntegrationTest", order = 3)
|
||||
// public void withDatabase() {
|
||||
// testMode = Mode.UNIT_INTEGRATION_TEST;
|
||||
// }
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package nl.sander.testautomation.steps.cars;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import io.cucumber.datatable.DataTable;
|
||||
import io.cucumber.java.After;
|
||||
import io.cucumber.java.Before;
|
||||
import io.cucumber.java8.En;
|
||||
import nl.sander.testautomation.Mode;
|
||||
import nl.sander.testautomation.cars.Car;
|
||||
import nl.sander.testautomation.cars.CarDao;
|
||||
import nl.sander.testautomation.cars.CarModule;
|
||||
import nl.sander.testautomation.database.Database;
|
||||
import nl.sander.testautomation.steps.StepDefinitions;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DatabaseStepDefinitions implements En {
|
||||
|
||||
private CarDao carDao;
|
||||
|
||||
public DatabaseStepDefinitions() {
|
||||
When("^a user enters a car with id (\\d+?): a (.+?) (.+?) (.+?), built in (\\d+?)$", (Long id, String brand, String model, String color, Integer year) -> {
|
||||
Car car = Car.builder()
|
||||
.withId(id)
|
||||
.withBrand(brand)
|
||||
.withModel(model)
|
||||
.withColor(color)
|
||||
.withYear(year).build();
|
||||
|
||||
if (StepDefinitions.testMode == Mode.DATABASE_INTEGRATION_TEST) {
|
||||
this.carDao.store(car);
|
||||
}
|
||||
});
|
||||
|
||||
Then("^a new car is added to the inventory$", (DataTable dataTable) -> {
|
||||
Map<Long, Car> carRecordsById = this.carDao.getAllCars().stream().collect(Collectors.toMap(Car::getId, Function.identity()));
|
||||
dataTable.asMaps().stream()
|
||||
.map(record -> Long.parseLong(record.get("id")))
|
||||
.forEach(carId -> {
|
||||
assertTrue(String.format("car with id %s not found", carId), carRecordsById.containsKey(carId));
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Before(order = 100)
|
||||
public void initdatabase() throws SQLException {
|
||||
if (StepDefinitions.testMode == Mode.DATABASE_INTEGRATION_TEST) {
|
||||
createTable();
|
||||
|
||||
Injector injector = Guice.createInjector(new CarModule());
|
||||
carDao = injector.getInstance(CarDao.class);
|
||||
}
|
||||
}
|
||||
|
||||
private void createTable() throws SQLException {
|
||||
try (Connection connection = createConnection()) {
|
||||
Statement statement = connection.createStatement();
|
||||
statement.execute("create table cars (" +
|
||||
"id BIGINT not null primary key, " +
|
||||
"brand varchar(100), " +
|
||||
"model varchar(100), " +
|
||||
"color varchar(20), " +
|
||||
"year integer)");
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
public void dropDatabase() throws SQLException {
|
||||
if (StepDefinitions.testMode == Mode.DATABASE_INTEGRATION_TEST) {
|
||||
try (Connection connection = createConnection()) {
|
||||
Statement statement = connection.createStatement();
|
||||
statement.execute("drop table cars");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Connection createConnection() throws SQLException {
|
||||
return new Database().getConnection();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package nl.sander.testautomation.tests.cars;
|
||||
|
||||
import nl.sander.testautomation.cars.Car;
|
||||
import nl.sander.testautomation.cars.CarDao;
|
||||
import nl.sander.testautomation.cars.CarService;
|
||||
import nl.sander.testautomation.UnitTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class CarServiceTest implements UnitTest {
|
||||
|
||||
private final CarDao carDao = mock(CarDao.class);
|
||||
private final CarService carService = classToTest(CarService.class);
|
||||
|
||||
@Test
|
||||
public void testGetCar() throws SQLException {
|
||||
when(carDao.getCar(10)).thenReturn(Car.builder().withColor("blue").build());
|
||||
|
||||
Optional<Car> car = carService.getCar(10);
|
||||
|
||||
assertTrue(car.isPresent());
|
||||
assertEquals("blue", car.get().getColor());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
Feature: car module
|
||||
|
||||
@DatabaseIntegrationTest
|
||||
Scenario: add new car
|
||||
When a user enters a car with id 1: a red volkswagen kever, built in 1967
|
||||
|
||||
Then a new car is added to the inventory
|
||||
| id | brand | model | color | year |
|
||||
| 1 | volkswagen | kever | red | 1967 |
|
||||
Loading…
Add table
Reference in a new issue