Skip to content

Latest commit

 

History

History
238 lines (208 loc) · 8.09 KB

Presentasjon.md

File metadata and controls

238 lines (208 loc) · 8.09 KB
marp theme paginate _paginate transition
true
rose-pine
true
false
fade

Testcontainers

Javabin Bergen 19/9-2024 Sondre Eikanger Kvalø Sonat Consulting Bergen @zapodot https://github.com/zapodot/test-containers-prez bg 30% right


Plan for dagen

  1. Bakgrunn
  2. Hvordan gjorde vi testing før?
  3. Fordeler og ulemper
  4. Testcontainers 101 med eksempler
  5. Konklusjon

Hva er en container?

width:1000 Kilde: Open Container Initiative Image spec v 1.1.0


width:1000 Kilde: [Docker overview] (https://docs.docker.com/get-started/docker-overview/)


Hva menes med test?

Kode som kjører som en del av standard bygging av et kodeprosjekt som tester ulike utfall som kan gjøres med produksjonskoden


Noen ulike former for tester

  • enhetstest - tester en enkelt funksjon eller klasse. Fokus på f.eks grenseverdier. Mock-er alle avhengigheter
  • komponenttest - blackbox testing av en enkelt komponent. Mocker typisk alle eksterne komponenter
  • integrasjonstest - tester som fokuserer på grensesnittet mellom egen kode og tredjepart

Hvordan gjorde vi dette før?

  • Testet ikke integrasjonskode før produksjon
  • Testet mot faktisk test/produksjonsmiljø
  • Kjørte mocks/stubs som lot deg dekke en del av behovet (f.eks H2Database i kompabilitetsmodus)

Eksempel: enhetstest

@ExtendWith(MockitoExtension.class)
@SuppressWarnings("unchecked")
class RoleReadRepositoryTest {

    @Mock
    private NamedParameterJdbcOperations namedParameterJdbcOperations;

    @InjectMocks
    private RoleReadRepository roleReadRepository;

    @DisplayName("findById uten treff")
    @Test
    void findByIdWithoutResult() {
        when(namedParameterJdbcOperations.queryForObject(anyString(), anyMap(), isA(RowMapper.class)))
            .thenThrow(new IncorrectResultSizeDataAccessException(0));
        assertThat(roleReadRepository.findById(Long.MAX_VALUE)).isNull();
        verify(namedParameterJdbcOperations).queryForObject(anyString(), anyMap(), isA(RowMapper.class));
    }

Vi trenger fortsatt enhetstester

Tester som fokuserer på en enkelt funksjon eller klasse ved bruk av mocks gir en pekepinn på om koden vår har rett nivå av avhengigheter (coupling).


Testcontainers

Testing med Testcontainers er å anse som integrasjonstester og fokuset bør først og fremst være på å sjekke at integrasjonskoden virker mot de tredjeparts avhengighetene vi har i produksjon


Funksjonalitet

  • Kan kjøre på
    • Docker (Desktop)
    • Podman i med docker emulering
    • embedded runtime basert på Alpine Linux (eksperimentell)
    • Testcontainers cloud

Kjøre containere i sky

  • Trenger ikke ha en container runtime installert
  • Pay-as-you-go modell
  • Kanskje mest nyttig i CI-sammenheng?
  • Etter at Docker Inc kjøpte opp Atomic Jar som laget Testcontainers kan vi anta at det vil henge sammen med deres cloud-løsning

Livssyklus til testcontainere

width:100%


Funksjonalitet

  • Kan brukes fra ulike språk og med ulike testrammeverk, f.eks:
    • Java (JUnit 4/5 eller Spock)
    • Kotlin (kotest)
    • .NET
    • Go
    • Node.js

Funksjonalitet

  • Kan kjøre alle containere
  • Men: for en del mye brukte containere finnes det egne moduler som gjør jobben litt lettere
  • For eksempel finnes det ferdige moduler for de fleste DBMS-systemer og de mest vanlige meldingssystemer

Eksempel: generisk container 101

class GenericContainerTest : StringSpec({
    val genericContainer = GenericContainer("testcontainers/helloworld:1.1.0")
        .withExposedPorts(8080)
        .waitingFor(Wait.forHttp("/"))

    listeners(genericContainer.perTest())

    "Ping skal gi PONG" {
        val httpClient = HttpClient(CIO)
        httpClient.get("http://${genericContainer.host}:${genericContainer.getMappedPort(8080)}/ping")
            .asClue { response ->
                response.status.value shouldBe 200
                response.readBytes().toString(Charsets.UTF_8) shouldBe "PONG"
            }
    }
})

Eksempel: Bruk av PostgresSQL modul med Java/JUnit 5

@Testcontainers(disabledWithoutDocker = true)
public class ContainerBaseRepositoryTests {

    @Container
    private PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:12.20-alpine");

    @Test
    void testnoemotdatabasen() {
        final var dataSourceConfig = new DataSourceConfig(
                postgreSQLContainer.getJdbcUrl(),
                postgreSQLContainer.getUsername(),
                postgreSQLContainer.getPassword()
        //...
    }

}

Eksempel: Bruk av PostgresSQL modul med Kotlin/kotest

class UserReadRepositoryTest : StringSpec({
    val postgres = PostgreSQLContainer("postgres:12.20-alpine")

    listeners(postgres.perSpec())

    "Test databasekode" {
        val exposedConnection = Database.connect(
            url = postgres.jdbcUrl,
            driver = postgres.driverClassName,
            user = postgres.username,
            password = postgres.password
        )
        //...
     }
})

Koble sammen containere (code smell?)

try (
    Network network = Network.newNetwork();
    GenericContainer<?> foo = new GenericContainer<>(TestImages.TINY_IMAGE)
        .withNetwork(network)
        .withNetworkAliases("foo")
        .withCommand(
            "/bin/sh",
            "-c",
            "while true ; do printf 'HTTP/1.1 200 OK\\n\\nyay' | nc -l -p 8080; done"
        );
    GenericContainer<?> bar = new GenericContainer<>(TestImages.TINY_IMAGE)
        .withNetwork(network)
        .withCommand("top")
) {
    foo.start();
    bar.start();

    String response = bar.execInContainer("wget", "-O", "-", "http://foo:8080").getStdout();
    assertThat(response).as("received response").isEqualTo("yay");
}

Fordeler ved å skrive tester som bruker testcontainers

  • Får testet integrasjonskode mot noe som ligner veldig på det du bruker i produksjon
  • Får testet kode som er strenger i kodebasen, f.eks SQL som kompilatoren ikke har et forhold til

Ulemper med å ha tester som bruker testcontainers

  • Tar lengre tid å kjøre testene
  • Lett for at man ungår å refaktorere koden som integrerer mot tredjepart

Konklusjon

  • Tester som er avhengig av en tredjepart er ikke enhetstester men integrasjonstester
  • Kan velge å skille ut testene som krever containere
  • Enhetstester er fremdeles viktig for å sikre at man opprettholder så løs kobling som mulig