commit 46eec5da55508cd6f5c12eda54204035a0f8980e Author: Sander Hautvast Date: Wed Jan 22 16:01:50 2020 +0100 first commit: pure unit tests and cucumber bdd tests with database integration diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d2f40f --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea/ +Reports/ +target/ +*.iml +.DS_Store + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..02ab3af --- /dev/null +++ b/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + nl.sander + testautomatisering + 0.0.1-SNAPSHOT + testautomatisering + POC van dynamische (testgedreven) manier om met dependency injection van mocks om te gaan + + + UTF-8 + 8 + 8 + + + + + com.h2database + h2 + 1.4.200 + test + + + com.google.inject + guice + 4.2.2 + + + org.reflections + reflections + 0.9.11 + + + + + junit + junit + 4.12 + test + + + org.mockito + mockito-all + 1.10.19 + test + + + io.cucumber + cucumber-core + 5.0.0-RC4 + test + + + io.cucumber + cucumber-java + 5.0.0-RC4 + test + + + io.cucumber + cucumber-java8 + 5.0.0-RC4 + test + + + io.cucumber + cucumber-junit + 5.0.0-RC4 + test + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M4 + + + **/*Tests.java + **/*Test.java + + + + + + diff --git a/src/main/java/nl/sander/testautomation/cars/Car.java b/src/main/java/nl/sander/testautomation/cars/Car.java new file mode 100644 index 0000000..c096657 --- /dev/null +++ b/src/main/java/nl/sander/testautomation/cars/Car.java @@ -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; + } + } +} diff --git a/src/main/java/nl/sander/testautomation/cars/CarDao.java b/src/main/java/nl/sander/testautomation/cars/CarDao.java new file mode 100644 index 0000000..adf493e --- /dev/null +++ b/src/main/java/nl/sander/testautomation/cars/CarDao.java @@ -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 getAllCars() throws SQLException; + + void store(Car car) throws SQLException; +} diff --git a/src/main/java/nl/sander/testautomation/cars/CarModule.java b/src/main/java/nl/sander/testautomation/cars/CarModule.java new file mode 100644 index 0000000..b7f272f --- /dev/null +++ b/src/main/java/nl/sander/testautomation/cars/CarModule.java @@ -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()); + } +} diff --git a/src/main/java/nl/sander/testautomation/cars/CarService.java b/src/main/java/nl/sander/testautomation/cars/CarService.java new file mode 100644 index 0000000..914ce6d --- /dev/null +++ b/src/main/java/nl/sander/testautomation/cars/CarService.java @@ -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 getCar(long carId) { + try { + return Optional.of(carDao.getCar(carId)); + } catch (SQLException e) { + return Optional.empty(); + } + } +} diff --git a/src/main/java/nl/sander/testautomation/cars/JdbcCarDao.java b/src/main/java/nl/sander/testautomation/cars/JdbcCarDao.java new file mode 100644 index 0000000..6355d13 --- /dev/null +++ b/src/main/java/nl/sander/testautomation/cars/JdbcCarDao.java @@ -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 getAllCars() throws SQLException { + try (Connection connection = createConnection()) { + PreparedStatement statement = connection.prepareStatement("select id, brand, model, color, year from cars"); + ResultSet result = statement.executeQuery(); + List 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(); + } +} diff --git a/src/main/java/nl/sander/testautomation/database/Database.java b/src/main/java/nl/sander/testautomation/database/Database.java new file mode 100644 index 0000000..7f72c21 --- /dev/null +++ b/src/main/java/nl/sander/testautomation/database/Database.java @@ -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; + } +} diff --git a/src/test/java/nl/sander/testautomation/CucumberTests.java b/src/test/java/nl/sander/testautomation/CucumberTests.java new file mode 100644 index 0000000..ad71941 --- /dev/null +++ b/src/test/java/nl/sander/testautomation/CucumberTests.java @@ -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 { + +} diff --git a/src/test/java/nl/sander/testautomation/Mode.java b/src/test/java/nl/sander/testautomation/Mode.java new file mode 100644 index 0000000..90ef168 --- /dev/null +++ b/src/test/java/nl/sander/testautomation/Mode.java @@ -0,0 +1,7 @@ +package nl.sander.testautomation; + +public enum Mode { + UNIT_TEST, + UNIT_INTEGRATION_TEST, + DATABASE_INTEGRATION_TEST +} diff --git a/src/test/java/nl/sander/testautomation/UnitTest.java b/src/test/java/nl/sander/testautomation/UnitTest.java new file mode 100644 index 0000000..feac121 --- /dev/null +++ b/src/test/java/nl/sander/testautomation/UnitTest.java @@ -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 classToTest(Class type) { + try { + T instance = type.newInstance(); + injector.injectMembers(instance); + return instance; + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + default T mock(Class type) { + return injector.getInstance(type); + } + + +} diff --git a/src/test/java/nl/sander/testautomation/UnitTestModule.java b/src/test/java/nl/sander/testautomation/UnitTestModule.java new file mode 100644 index 0000000..b97ac19 --- /dev/null +++ b/src/test/java/nl/sander/testautomation/UnitTestModule.java @@ -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))); + + } +} diff --git a/src/test/java/nl/sander/testautomation/steps/StepDefinitions.java b/src/test/java/nl/sander/testautomation/steps/StepDefinitions.java new file mode 100644 index 0000000..8ab535c --- /dev/null +++ b/src/test/java/nl/sander/testautomation/steps/StepDefinitions.java @@ -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; +// } +} + diff --git a/src/test/java/nl/sander/testautomation/steps/cars/DatabaseStepDefinitions.java b/src/test/java/nl/sander/testautomation/steps/cars/DatabaseStepDefinitions.java new file mode 100644 index 0000000..71a7ea8 --- /dev/null +++ b/src/test/java/nl/sander/testautomation/steps/cars/DatabaseStepDefinitions.java @@ -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 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(); + } +} diff --git a/src/test/java/nl/sander/testautomation/tests/cars/CarServiceTest.java b/src/test/java/nl/sander/testautomation/tests/cars/CarServiceTest.java new file mode 100644 index 0000000..769f332 --- /dev/null +++ b/src/test/java/nl/sander/testautomation/tests/cars/CarServiceTest.java @@ -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 = carService.getCar(10); + + assertTrue(car.isPresent()); + assertEquals("blue", car.get().getColor()); + } +} diff --git a/src/test/java/nl/sander/testautomation/tests/cars/new_car.feature b/src/test/java/nl/sander/testautomation/tests/cars/new_car.feature new file mode 100644 index 0000000..026f51f --- /dev/null +++ b/src/test/java/nl/sander/testautomation/tests/cars/new_car.feature @@ -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 |