marp | theme | paginate | _paginate | transition |
---|---|---|---|---|
true |
uncover |
true |
false |
fade |
Presentasjon fagdag 25/10-2024 Sondre Eikanger Kvalø @zapodot https://github.com/zapodot
- Om testing
- Hvordan gjorde vi testing før?
- Testcontainers
- Konklusjon
Kode som kjører som en del av standard bygging av et kodeprosjekt som tester ulike utfall som kan gjøres med produksjonskoden
- 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
- 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)
@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));
}
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 gjør det enklere å kjøre containere for å teste som en del av standard bygging
- Kan kjøre en hvilken som helst container, men har egne moduler for mange ofte brukte containere (DBMS, meldingsbrokere etc)
Kilde: Open Container Initiative Image spec v 1.1.0
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
- Kan kjøre på
- Docker (Desktop)
- Podman i med docker emulering
- embedded runtime basert på Alpine Linux (eksperimentell)
- Testcontainers cloud
- 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
- Kan brukes fra ulike språk og med ulike testrammeverk, f.eks:
- Java (JUnit 4/5 eller Spock)
- Kotlin (kotest)
- .NET
- Go
- Node.js
- m.fl
- 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
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"
}
}
})
@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()
//...
}
}
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
)
//...
}
})
public class PostgresDatabaseFixture : IAsyncLifetime
{
private readonly PostgreSqlContainer _container =
new PostgreSqlBuilder()
.WithImage("postgres:12.20-alpine")
.Build();
public NpgsqlDataSource DataSource => new DataSourceFactory(_container.GetConnectionString()).Create();
public Task InitializeAsync()
{
return _container.StartAsync().ContinueWith(t => RunMigrations());
}
public Task DisposeAsync()
=> _container.DisposeAsync().AsTask();
}
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");
}
- 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
- Tar lengre tid å kjøre testene
- Lett for at man ungår å refaktorere koden som integrerer mot tredjepart
- Må ha mulighet for å kjøre containere også i CI-miljø
- 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