Share
Embed
Beyond JUnit: Pragmatic Ways to Increase Code Quality
You and your team are writing and running unit tests - great! Better yet, they pass (most of the time)! But can you do more to ensure the quality of your code? Come to this talk where you will learn some practical new skills to help increase the quality of your code and catch bugs early in the development cycle. You'll learn how ArchUnit can help you write tests for your application architecture, and how TestCcontainers can help ease your integration testing burden. At the end of this talk, you will know several new techniques to bring to your codebase that will ultimately make your customers happy. This talk is aimed at Java developers who have some basic testing knowledge and want to move to the next level.
Todd Ginsberg
August 08, 2022
Transcript
218 slides
-
Beyond JUnit: KCDC 2022 2022-08-08 Todd Ginsberg Platform Architect Pragmatic
Ways to Increase Code Quality @ToddGinsberg -
@ToddGinsberg Beyond JUnit: Pragmatic Ways to Increase Code Quality “Dealing
with things sensibly and realistically in a way that is based on practical rather than theoretical considerations.” (Thanks, Google!) -
@ToddGinsberg I Am Making Some Assumptions About You… • You
already write some kind of tests using a modern framework like JUnit 5. -
@ToddGinsberg I Am Making Some Assumptions About You… • You
already write some kind of tests using a modern framework like JUnit 5. • You have covered the basics of code quality with static analysis tools, and maybe an enforced coding style. -
@ToddGinsberg What You Will See In This Talk 1) How
to test the architecture of your code. -
@ToddGinsberg What You Will See In This Talk 1) How
to test the architecture of your code. 2) How to test that your code plays well with others. -
@ToddGinsberg Any fool can write code that a computer can
understand. Good programmers write code that humans can understand. – Martin Fowler Refactoring: Improving the Design of Existing Code -
@ToddGinsberg Any fool can write code that a computer can
understand. Good programmers write code that humans can understand. – Martin Fowler Refactoring: Improving the Design of Existing Code -
@ToddGinsberg Testing shows the presence, not the absence of bugs.
– Edsger W. Dijkstra October, 1969 -
@ToddGinsberg What’s the Problem? API Layer Service Layer Data Layer
Controller Service Controller Service Repository Repository -
@ToddGinsberg What’s the Problem? API Layer Service Layer Data Layer
Controller Service Controller Service Repository Repository -
@ToddGinsberg What’s the Problem? API Layer Service Layer Data Layer
Controller Service Controller Service Repository Repository -
@ToddGinsberg What’s the Problem? API Layer Service Layer Data Layer
Controller Service Controller Service Repository Repository -
@ToddGinsberg Adding To Our Project testImplementation( 'com.tngtech.archunit:archunit-junit5:0.23.1' ) <dependency> <groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId> <version>0.23.1</version> <scope>test</scope> </dependency> -
@ToddGinsberg Our First Architectural Test… @Test public void implNamingRule() {
JavaClasses classes = // TODO } -
@ToddGinsberg Our First Architectural Test… @Test public void implNamingRule() {
JavaClasses classes = // TODO ArchRule theRule = // TODO } -
@ToddGinsberg Our First Architectural Test… @Test public void implNamingRule() {
JavaClasses classes = // TODO ArchRule theRule = // TODO theRule.check(classes); } -
@ToddGinsberg Our First Architectural Test… @Test public void implNamingRule() {
JavaClasses classes = new ClassFileImporter() .importPackages( "com.example.donutapi" ); } -
@ToddGinsberg Our First Architectural Test… @Test public void implNamingRule() {
JavaClasses classes = new ClassFileImporter() .importPackages( "com.example.donutapi", "com.example.utilities" ); } -
@ToddGinsberg Our First Architectural Test… @Test public void implNamingRule() {
JavaClasses classes = new ClassFileImporter() .importPackages("com.example.donutapi"); ArchRule theRule = theRule.check(classes); } -
@ToddGinsberg Our First Architectural Test… @Test public void implNamingRule() {
JavaClasses classes = new ClassFileImporter() .importPackages("com.example.donutapi"); ArchRule theRule = noClasses() theRule.check(classes); } -
@ToddGinsberg Our First Architectural Test… @Test public void implNamingRule() {
JavaClasses classes = new ClassFileImporter() .importPackages("com.example.donutapi"); ArchRule theRule = noClasses() .should() theRule.check(classes); } -
@ToddGinsberg Our First Architectural Test… @Test public void implNamingRule() {
JavaClasses classes = new ClassFileImporter() .importPackages("com.example.donutapi"); ArchRule theRule = noClasses() .should() .haveNameMatching(".*Impl"); theRule.check(classes); } -
@ToddGinsberg Our First Architectural Test… ArchRule theRule = noClasses() .should()
.haveNameMatching(".*Impl"); -
@ToddGinsberg Our First Architectural Test… ArchRule theRule = noClasses() .should()
.haveNameMatching(".*Impl"); Architecture Violation [Priority: MEDIUM] - Rule 'no classes should have name matching '.*Impl'' was violated (1 times): Class <com.example.donutapi.service.SomeServiceImpl> matches '.*Impl' in (SomeServiceImpl.java:0) -
@ToddGinsberg Our First Architectural Test… ArchRule theRule = noClasses() .should()
.haveNameMatching(".*Impl"); Architecture Violation [Priority: MEDIUM] - Rule 'no classes should have name matching '.*Impl'' was violated (1 times): Class <com.example.donutapi.service.SomeServiceImpl> matches '.*Impl' in (SomeServiceImpl.java:0) -
@ToddGinsberg Our First Architectural Test… ArchRule theRule = noClasses() .should()
.haveNameMatching(".*Impl"); Architecture Violation [Priority: MEDIUM] - Rule 'no classes should have name matching '.*Impl'' was violated (1 times): Class <com.example.donutapi.service.SomeServiceImpl> matches '.*Impl' in (SomeServiceImpl.java:0) -
@ToddGinsberg Our First Architectural Test… ArchRule theRule = noClasses() .should()
.haveNameMatching(".*Impl"); Architecture Violation [Priority: MEDIUM] - Rule 'no classes should have name matching '.*Impl'' was violated (1 times): Class <com.example.donutapi.service.SomeServiceImpl> matches '.*Impl' in (SomeServiceImpl.java:0) -
@ToddGinsberg Our First Architectural Test… ArchRule theRule = noClasses() .should()
.haveNameMatching(".*Impl"); -
@ToddGinsberg Our First Architectural Test… ArchRule theRule = noClasses() .should()
.haveNameMatching(".*Impl") .because("it doesn't tell us anything useful"); -
@ToddGinsberg Our First Architectural Test… ArchRule theRule = noClasses() .should()
.haveNameMatching(".*Impl") .because("it doesn't tell us anything useful"); Architecture Violation [Priority: MEDIUM] - Rule 'no classes should have name matching '.*Impl', because it doesn't tell us anything useful' was violated (1 times): Class <com.example.donutapi.service.SomeServiceImpl> matches '.*Impl' in (SomeServiceImpl.java:0) -
@ToddGinsberg Our First Architectural Test (Simplified) @AnalyzeClasses( packages = {"com.example.donutapi"},
importOptions = {ImportOption.DoNotIncludeTests.class} ) public class ExampleTest { @ArchTest static final ArchRule implNaming = noClasses() .should() .haveNameMatching(".*Impl") .because("it doesn't tell us anything useful"); } -
@ToddGinsberg Our First Architectural Test (Simplified) @AnalyzeClasses( packages = {"com.example.donutapi"},
importOptions = {ImportOption.DoNotIncludeTests.class} ) public class ExampleTest { @ArchTest static final ArchRule implNaming = noClasses() .should() .haveNameMatching(".*Impl") .because("it doesn't tell us anything useful"); } -
@ToddGinsberg Our First Architectural Test (Simplified) @AnalyzeClasses( packages = {"com.example.donutapi"},
importOptions = {ImportOption.DoNotIncludeTests.class} ) public class ExampleTest { @ArchTest static final ArchRule implNaming = noClasses() .should() .haveNameMatching(".*Impl") .because("it doesn't tell us anything useful"); } -
@ToddGinsberg Our First Architectural Test (Simplified) @AnalyzeClasses( packages = {"com.example.donutapi"},
importOptions = {ImportOption.DoNotIncludeTests.class} ) public class ExampleTest { @ArchTest static final ArchRule implNaming = noClasses() .should() .haveNameMatching(".*Impl") .because("it doesn't tell us anything useful"); } -
@ToddGinsberg Our First Architectural Test (Simplified) @AnalyzeClasses( packages = {"com.example.donutapi"},
importOptions = {ImportOption.DoNotIncludeTests.class} ) public class ExampleTest { @ArchTest static final ArchRule implNaming = noClasses() .should() .haveNameMatching(".*Impl") .because("it doesn't tell us anything useful"); } -
@ToddGinsberg Our First Architectural Test (Simplified) @AnalyzeClasses( packages = {"com.example.donutapi"},
importOptions = {ImportOption.DoNotIncludeTests.class} ) public class ExampleTest { @ArchTest static final ArchRule implNaming = noClasses() .should() .haveNameMatching(".*Impl") .because("it doesn't tell us anything useful"); } -
@ToddGinsberg Our First Architectural Test (Simplified) @ArchTest static final ArchRule
implNaming = noClasses() .should() .haveNameMatching(".*Impl") .because("it doesn't tell us anything useful"); -
@ToddGinsberg Our First Architectural Test (Simplified) noClasses() .should() .haveNameMatching(".*Impl") .because("it
doesn't tell us anything useful"); -
@ToddGinsberg Use Case: Prohibit Dependencies noClasses() .should() .dependOnClassesThat() .resideInAnyPackage( "org.apache.log4j",
"org.apache.logging.log4j" ) .because("our standard is logback"); -
@ToddGinsberg Use Case: Prohibit Dependencies Architecture Violation [Priority: MEDIUM] -
Rule 'no classes should depend on classes that reside in any package ['org.apache.log4j', 'org.apache.logging.log4j'], because our standard is logback' was violated (2 times): -
@ToddGinsberg Simplify Violation Messages Architecture Violation [Priority: MEDIUM] - Rule
'no classes should depend on classes that reside in any package ['org.apache.log4j', 'org.apache.logging.log4j'], because our standard is logback' was violated (2 times): -
@ToddGinsberg Simplify Violation Messages noClasses() .should() .dependOnClassesThat() .resideInAnyPackage( "org.apache.log4j", "org.apache.logging.log4j"
) .because("our standard is logback"); -
@ToddGinsberg Simplify Violation Messages noClasses() .should() .dependOnClassesThat() .resideInAnyPackage( "org.apache.log4j", "org.apache.logging.log4j"
) .as("stop using log4j") .because("our standard is logback"); -
@ToddGinsberg Simplify Violation Messages Architecture Violation [Priority: MEDIUM] - Rule
'stop using log4j, because our standard is logback' was violated (2 times): -
@ToddGinsberg Simplify Violation Messages Architecture Violation [Priority: MEDIUM] - Rule
'stop using log4j, because our standard is logback' was violated (2 times): -
@ToddGinsberg Parts of a Rule classes() .that() .areAnnotatedWith(Controller.class) .or().areAnnotatedWith(RestController.class) .should()
.resideInAPackage("..controller..") .because("controllers belong in a controller package"); -
@ToddGinsberg Parts of a Rule classes() .that() .areAnnotatedWith(Controller.class) .or().areAnnotatedWith(RestController.class) .should()
.resideInAPackage("..controller..") .because("controllers belong in a controller package"); -
@ToddGinsberg Parts of a Rule classes() .that() .areAnnotatedWith(Controller.class) .or().areAnnotatedWith(RestController.class) .should()
.resideInAPackage("..controller..") .because("controllers belong in a controller package"); -
@ToddGinsberg Class Predicates areAnnotatedWith areAnnotations areAnonymousClasses areAssignableFrom areAssignableTo areEnums areInnerClasses
areInterfaces areLocalClasses areMemberClasses areMetaAnnotatedWith areNestedClasses areNotAnnotatedWith areNotAnnotations areNotAnonymousClasses areNotAssignableFrom areNotAssignableTo areNotEnums areNotInnerClasses areNotInterfaces areNotLocalClasses areNotMemberClasses areNotMetaAnnotatedWith areNotNestedClasses areNotPackagePrivate areNotPrivate areNotProtected areNotPublic areNotRecords areNotTopLevelClasses arePackagePrivate arePrivate -
@ToddGinsberg Class Predicates areProtected arePublic areRecords areTopLevelClasses belongToAnyOf containAnyCodeUnitsThat containAnyConstructorsThat
containAnyFieldsThat containAnyMembersThat containAnyMethodsThat containAnyStaticInitializersThat doNotBelongToAnyOf doNotHaveFullyQualifiedName doNotHaveModifier doNotHaveSimpleName doNotImplement haveFullyQualifiedName haveModifier haveNameMatching haveNameNotMatching haveSimpleName haveSimpleNameContaining haveSimpleNameEndingWith haveSimpleNameNotContaining haveSimpleNameNotEndingWith haveSimpleNameNotStartingWith haveSimpleNameStartingWith implement resideInAPackage resideInAnyPackage resideOutsideOfPackage resideOutsideOfPackages -
@ToddGinsberg Parts of a Rule classes() .that() .areAnnotatedWith(Controller.class) .or().areAnnotatedWith(RestController.class) .should()
.resideInAPackage("..controller..") .because("controllers belong in a controller package"); -
@ToddGinsberg Class Conditions accessClassesThat accessField accessFieldWhere accessTargetWhere be beAnnotatedWith beAnonymousClasses
beAssignableFrom beAssignableTo beEnums beInnerClasses beInterfaces beLocalClasses beMemberClasses beMetaAnnotatedWith beNestedClasses bePackagePrivate bePrivate beProtected bePublic beRecords beTopLevelClasses callCodeUnitWhere callConstructor callConstructorWhere callMethod callMethodWhere containNumberOfElements dependOnClassesThat getField getFieldWhere haveFullyQualifiedName -
@ToddGinsberg Class Conditions haveModifier haveNameMatching haveNameNotMatching haveOnlyFinalFields haveOnlyPrivateConstructors haveSimpleName haveSimpleNameContaining
haveSimpleNameEndingWith haveSimpleNameNotContaining haveSimpleNameNotEndingWith haveSimpleNameNotStartingWith haveSimpleNameStartingWith implement notBe notBeAnnotatedWith notBeAnonymousClasses notBeAssignableFrom notBeAssignableTo notBeEnums notBeInnerClasses notBeInterfaces notBeLocalClasses notBeMemberClasses notBeMetaAnnotatedWith notBeNestedClasses notBePackagePrivate notBePrivate notBeProtected notBePublic notBeRecords -
@ToddGinsberg Class Conditions notBeRecords notBeTopLevelClasses notHaveFullyQualifiedName notHaveModifier notHaveSimpleName notImplement onlyAccessClassesThat
onlyAccessFieldsThat onlyAccessMembersThat onlyCallCodeUnitsThat onlyCallConstructorsThat onlyCallMethodsThat onlyDependOnClassesThat onlyHaveDependentClassesThat resideInAnyPackage resideInAPackage resideOutsideOfPackage resideOutsideOfPackages setField setFieldWhere transitivelyDependOnClassesThat -
@ToddGinsberg Positive or Negative Inclusion classes() .that() .areAnnotatedWith(Controller.class) .or().areAnnotatedWith(RestController.class) .should()
.resideInAPackage("..controller..") .because("controllers belong in a controller package"); -
@ToddGinsberg Positive or Negative Inclusion noClasses() .that() .areAnnotatedWith(Controller.class) .or().areAnnotatedWith(RestController.class) .should()
.resideOutsideOfPackage("..controller..") .because("controllers belong in a controller package"); -
@ToddGinsberg Use Case: Layered Architecture API Layer Service Layer Data
Layer Controller Service Controller Service Repository Repository -
@ToddGinsberg Use Case: Layered Architecture The hard way… noClasses() .that()
.resideOutsideOfPackage("..controller..") .should() .accessClassesThat() .resideInAnyPackage("..controller.."); -
@ToddGinsberg Use Case: Layered Architecture The hard way… classes() .that()
.resideInAnyPackage("..service..") .should() .onlyBeAccessed() .byAnyPackage("..controller.."); -
@ToddGinsberg Use Case: Layered Architecture The easy way… layeredArchitecture() .layer("Controller").definedBy("..controller..")
.layer("Service").definedBy("..service..") .layer("Data").definedBy("..data..") -
@ToddGinsberg Use Case: Layered Architecture The easy way… layeredArchitecture() .layer("Controller").definedBy("..controller..")
.layer("Service").definedBy("..service..") .layer("Data").definedBy("..data..") .whereLayer("Controller").mayNotBeAccessedByAnyLayer() .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller") .whereLayer("Data").mayOnlyBeAccessedByLayers("Service"); -
@ToddGinsberg Use Case: Onion Architecture The easy way… onionArchitecture() .domainModels("..model..")
.domainServices("..service..") .applicationServices("..app..") .adapter("cli", "..cli..") .adapter("data", "..data..") .adapter("rest", "..rest.."); -
@ToddGinsberg Use Case: PlantUML Diagram @startuml [Address] <<..address..>> [Customer] <<..customer..>>
[Order] <<..order..>> #white/PowderBlue [Products] <<..product..>> #white/PowderBlue [Product Catalog] <<..catalog..>> as catalog #PowderBlue [Product Import] <<..importer..>> as import [XML] <<..xml.processor..>> <<..xml.types..>> as xml [Order] ---> [Customer] : is placed by [Order] --> [Products] [Customer] --> [Address] [Products] <--[#green]- catalog import -left-> catalog : parse products import --> xml @enduml -
@ToddGinsberg Use Case: PlantUML Diagrams URL myDiagram = getClass().getResource("project.puml"); classes()
.should( adhereToPlantUmlDiagram( myDiagram, consideringAllDependencies() ) ); -
@ToddGinsberg ArchUnit-Provided Rules • Don’t use standard streams (System.out, System.in,
printStackTrace) • Don’t throw generic exceptions (Throwable, Exception, RuntimeException) • Don’t use java.util.logging • Don’t use Joda Time • Prevent field-level autowiring (@Autowired, @Value, @Inject, @Resource, etc) -
@ToddGinsberg What About Legacy Applications? This is a README. Pay
attention to it! This repository is not a place of honor... no highly esteemed code is kept here. What is here was dangerous and repulsive to us. This repository is best shunned and left untouched. -
@ToddGinsberg Use Case: Freezing Rules layeredArchitecture() .layer("Controller").definedBy("..controller..") .layer("Service").definedBy("..service..") .layer("Data").definedBy("..data..") .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
.whereLayer("Service").mayOnlyBeAccessedByLayers("Controller") .whereLayer("Data").mayOnlyBeAccessedByLayers("Service"); -
@ToddGinsberg Use Case: Freezing Rules FreezingArchRule.freeze( layeredArchitecture() .layer("Controller").definedBy("..controller..") .layer("Service").definedBy("..service..") .layer("Data").definedBy("..data..")
.whereLayer("Controller").mayNotBeAccessedByAnyLayer() .whereLayer("Service").mayOnlyBeAccessedByLayers("Controller") .whereLayer("Data").mayOnlyBeAccessedByLayers("Service") ); -
@ToddGinsberg Use Case: Freezing Rules archunit.properties Run Tests stored.rules uuid-named
files stored.rules uuid-named files -
@ToddGinsberg Testing Our Rules classes() .that() .resideInAnyPackage("that doesn’t exist") .should()
// ... This rule will FAIL if should() operates on an empty set of classes! -
@ToddGinsberg Testing Our Rules classes() .that() .resideInAnyPackage("that doesn’t exist") .should()
// Some fun conditions here .allowEmptyShould(true) -
@ToddGinsberg Testing Our Rules ArchRule auroraBorealisLocationAndTimeOfDay = //... JavaClasses classes
= new ClassFileImporter().importClasses(SkinnersKitchen.class); -
@ToddGinsberg Testing Our Rules ArchRule auroraBorealisLocationAndTimeOfDay = //... JavaClasses classes
= new ClassFileImporter().importClasses(SkinnersKitchen.class); auroraBorealisLocationAndTimeOfDay.check(classes) -
@ToddGinsberg Testing Our Rules ArchRule auroraBorealisLocationAndTimeOfDay = //... JavaClasses classes
= new ClassFileImporter().importClasses(SkinnersKitchen.class); assertThatThrownBy( () -> auroraBorealisLocationAndTimeOfDay.check(classes) ) .isInstanceOf(AssertionError.class) .hasMessageStartingWith("Architecture Violation"); -
@ToddGinsberg Why Use ArchUnit? • Quickly enforce architectural rules from
within our project. -
@ToddGinsberg Why Use ArchUnit? • Quickly enforce architectural rules from
within our project. • Runs as part of test cycle for early, inescapable feedback. -
@ToddGinsberg Why Use ArchUnit? • Quickly enforce architectural rules from
within our project. • Runs as part of test cycle for early, inescapable feedback. • Documents intent. -
@ToddGinsberg Why Use ArchUnit? • Quickly enforce architectural rules from
within our project. • Runs as part of test cycle for early, inescapable feedback. • Documents intent. • Helps move legacy systems towards enforced standards. -
@ToddGinsberg Central DB Our Machine What About A Shared Instance?
Our Application Randos -
@ToddGinsberg Central DB Our Machine What About A Shared Instance?
Our Application Randos -
@ToddGinsberg Central DB Our Machine What About A Shared Instance?
Our Application Randos V1 V1 -
@ToddGinsberg Central DB Our Machine What About A Shared Instance?
Our Application Randos V1 V2 -
@ToddGinsberg Docker Daemon How Does It Work? Docker REST API
Our Dependencies -
@ToddGinsberg Docker Daemon How Does It Work? Docker REST API
Docker CLI Our Dependencies -
@ToddGinsberg Docker Daemon How Does It Work? Docker REST API
Our Application Our Dependencies -
@ToddGinsberg Docker Daemon How Does It Work? Docker REST API
Our Application Our Dependencies Ryuk -
@ToddGinsberg Testcontainer Requirements 1) Docker Environment 2) Java 8+ 3)
JUnit4 or JUnit5 or Spock -
@ToddGinsberg Testcontainer Requirements 1) Docker Environment 2) Java 8+ 3)
JUnit4 or JUnit5 or Spock -
@ToddGinsberg Adding To Our Project <dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.17.3</version> <scope>test</scope>
</dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <version>1.17.3</version> <scope>test</scope> </dependency> -
@ToddGinsberg Use Case: Database Tests @SpringBootTest public class PostgresRelatedTest {
@Autowired private DonutRepository repository; // ??? } -
@ToddGinsberg Use Case: Database Tests @SpringBootTest @Testcontainers public class PostgresRelatedTest
{ @Autowired private DonutRepository repository; // ??? } -
@ToddGinsberg Use Case: Database Tests @Autowired private DonutRepository repository; @Container
private static PostgreSQLContainer database = new PostgreSQLContainer("postgres:14.2"); -
@ToddGinsberg Use Case: Database Tests @Autowired private DonutRepository repository; @Container
private static PostgreSQLContainer database = new PostgreSQLContainer("postgres:14.2"); @DynamicPropertySource public static void props(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", database::getJdbcUrl); registry.add("spring.datasource.username", database::getUsername); registry.add("spring.datasource.password", database::getPassword); } -
@ToddGinsberg Use Case: Database Tests @Autowired private DonutRepository repository; @Container
private static PostgreSQLContainer database = new PostgreSQLContainer("postgres:14.2") .withDatabaseName("setec_astronomy") .withUsername("mbishop") .withPassword("T00ManyS3crets"); -
@ToddGinsberg Use Case: Database Tests @Autowired private DonutRepository repository; @Container
private static PostgreSQLContainer database = new PostgreSQLContainer("postgres:14.2") .withConnectionTimeoutSeconds(20) .withStartupTimeoutSeconds(20); -
@ToddGinsberg Use Case: Database Tests @Autowired private DonutRepository repository; @Container
private static PostgreSQLContainer database = new PostgreSQLContainer("postgres:14.2") .withInitScript("src/init.sql"); -
@ToddGinsberg Supported Database Modules JDBC R2DBC Cassandra CockroachDB Clickhouse DB2
Dynalite Influx MariaDB MongoDB MS SQL Server MySQL Neo4j Oracle-XE OrientDB Postgres Presto Trinio -
@ToddGinsberg Supported Database Modules JDBC R2DBC Cassandra CockroachDB Clickhouse DB2
Dynalite Influx MariaDB MongoDB MS SQL Server MySQL Neo4j Oracle-XE OrientDB Postgres Presto Trinio -
@ToddGinsberg Use Case: Database Tests (But Simpler!) jdbc:tc:postgresql:14.2://localhost:5432/somedb Our Application
Testcontainers JDBC Driver Data Layer -
@ToddGinsberg Use Case: Database Tests (But Simpler!) jdbc:tc:postgresql:14.2://localhost:5432/somedb Our Application
Testcontainers JDBC Driver Data Layer -
@ToddGinsberg Use Case: Database Tests (But Simpler!) jdbc:tc:postgresql:14.2://localhost:5432/somedb Our Application
Testcontainers JDBC Driver Data Layer -
@ToddGinsberg Use Case: Database Tests (But Simpler!) jdbc:tc:postgresql:14.2://localhost:5432/somedb Our Application
Testcontainers JDBC Driver PosgreSQL JDBC Driver Data Layer -
@ToddGinsberg Use Case: Database Tests (But Simpler!) jdbc:tc:postgresql:14.2://localhost:5432/somedb Our Application
Testcontainers JDBC Driver PosgreSQL JDBC Driver Data Layer -
@ToddGinsberg Use Case: Database Tests (But Simpler!) @SpringBootTest public class
JdbcUrlTest { static { } } -
@ToddGinsberg Use Case: Database Tests (But Simpler!) @SpringBootTest public class
JdbcUrlTest { static { System.setProperty( "spring.datasource.url", "jdbc:tc:postgresql:14.2://localhost:5432/somedb" ); } } -
@ToddGinsberg Use Case: Database Tests (But Simpler!) @SpringBootTest public class
JdbcUrlTest { static { System.setProperty( "spring.datasource.url", "jdbc:tc:postgresql:14.2://localhost:5432/somedb" ); } } -
@ToddGinsberg Use Case: Database Tests (But Simpler!) @SpringBootTest public class
JdbcUrlTest { static { System.setProperty( "spring.datasource.url", "jdbc:tc:postgresql:14.2://localhost:5432/" ); } } -
@ToddGinsberg Use Case: Database Tests (But Simpler!) @SpringBootTest public class
JdbcUrlTest { static { System.setProperty( "spring.datasource.url", "jdbc:tc:postgresql:14.2://localhost:5432/" ); } } -
@ToddGinsberg Use Case: Database Tests (But Simpler!) @SpringBootTest public class
JdbcUrlTest { static { System.setProperty( "spring.datasource.url", "jdbc:tc:postgresql:14.2:///" ); } } -
@ToddGinsberg JDBC Init Script Options… jdbc:tc:postgresql:14.2:/// ?TC_INITFUNCTION=JdbcTest::initFunction public class JdbcTest
{ public static void initFunction(Connection connection) throws SQLException { // Flyway, Liquibase, etc... } } -
@ToddGinsberg Use Case: Database Tests (But Simpler!) @SpringBootTest public class
JdbcUrlTest { static { System.setProperty( "spring.datasource.driverClassName", "org.testcontainers.jdbc.ContainerDatabaseDriver" ); System.setProperty( "spring.datasource.url", "jdbc:tc:postgresql:14.2:///" ); } } -
@ToddGinsberg Use Case: Database Tests (But Simpler!) @SpringBootTest public class
JdbcUrlTest { static { System.setProperty( "spring.datasource.driverClassName", "org.testcontainers.jdbc.ContainerDatabaseDriver" ); System.setProperty( "spring.datasource.url", "jdbc:tc:postgresql:14.2:///" ); } } -
@ToddGinsberg In A Base Class For All Tests public abstract
class AbstractBaseTest { static PostgreSQLContainer database; static GenericContainer redis; static { database = new PostgreSQLContainer("postgres:14.2"); database.start(); redis = new GenericContainer("redis:6.4.2-alpine"); redis.start(); } } -
@ToddGinsberg More Supported Modules Azure Docker Compose ElasticSearch GCloud HiveMQ
K3S Kafka LocalStack MockServer Nginx Apache Pulsar RabbitMQ Solr Toxiproxy Hashicorp Vault WebDriver -
@ToddGinsberg More Supported Modules Azure Docker Compose ElasticSearch GCloud HiveMQ
K3S Kafka LocalStack MockServer Nginx Apache Pulsar RabbitMQ Solr Toxiproxy Hashicorp Vault WebDriver -
@ToddGinsberg More Supported Modules Azure Docker Compose ElasticSearch GCloud HiveMQ
K3S Kafka LocalStack MockServer Nginx Apache Pulsar RabbitMQ Solr Toxiproxy Hashicorp Vault WebDriver -
@ToddGinsberg More Supported Modules Azure Docker Compose ElasticSearch GCloud HiveMQ
K3S Kafka LocalStack MockServer Nginx Apache Pulsar RabbitMQ Solr Toxiproxy Hashicorp Vault WebDriver -
@ToddGinsberg Why Use Testcontainers? • Isolated environments against real components
• App start + DB created in a single test -
@ToddGinsberg Why Use Testcontainers? • Isolated environments against real components
• App start + DB created in a single test • Easier to test failure cases -
@ToddGinsberg Why Use Testcontainers? • Isolated environments against real components
• App start + DB created in a single test • Easier to test failure cases • Run tests offline* -
@ToddGinsberg Why Use Testcontainers? • Isolated environments against real components
• App start + DB created in a single test • Easier to test failure cases • Run tests offline* • Code and Integration Tests in the same PR and repo -
@ToddGinsberg More On Testcontainers! Removing complexity from integration tests using
Testcontainers! By Sergei Egorov Tuesday @1:00pm Room 2214 (DevOps Track)