Przykłady, Własności, Mutacje
czyli wypaśne testy jednostkowe
Plan
- Co to są te testy?
- Narzędzia, metodyki i co je łączy.
- Jak nie pisać (za dużo) testów.

Blog

Kod
Piszemy testy
Kodujemy, kodujemy
testy też kodujemy
Problemik?
- Czy odkryłem wszystkie przypadki brzegowe?
- Czy test obejmuje wszystkie ścieżki?
- Czy asercja nie powtarza logiki kodu?
Asercja powtarza logikę
class SimpleCalculatorWithExampleTests implements SimpleCalculator { @Override public int sum(int a, int b) { return a + b; } }
class SimpleCalculatorByExampleTestSample { //…
@Test
void add2And2() {
int sum = sut.sum(2, 2);
org.junit.jupiter.api.Assertions.assertEquals(4, sum);
org.assertj.core.api.Assertions.assertThat(sum).isEqualTo(4);
}
}
Czy przeszliśmy wszystkie ścieżki?
Czy odkryliśmy wszystkie przypadki brzegowe?
class SimpleCalculatorByExampleFull implements SimpleCalculator { @Override public int sum(int a, int b) { int sum = a + b; if (a > 0 && b > 0) Preconditions.checkState(sum >= a && sum >= b, "Result overflow MAX_INT"); if (a < 0 && b < 0) Preconditions.checkState(sum <= a && sum <= b, "Result overflow MIN_INT"); return sum; } }
Co to jest test?
Co to jest kod?
Kod jako twierdzenie
- Niech nasz kod będzie „twierdzeniem”.
- Twierdzenie opiera się o aksjomaty i reguły wnioskowania.
- Twierdzenie powinno być możliwe do udowodnienia.
Test jako dowód
- Test jest dowodem twierdzenia.
- Dowód metodą „słabej” indukcji
- Warunek pokrycia całej dziedziny
Nope

Kurt Gödel
Przykłady
Czyli smutny jak TDD
Test Driven Development
RED
GREEN
REFACTOR ?
Kiedy przestać?
Gdy będzie dobrze
Dobrze w kodzie
- DRY, KISS, YAGNI
Dobrze w biznesie
- Działająca funkcjonalność
- Czytelny kod
- Spełnione metryki
- Zgodnie z harmonogramem
- „Jest dobrze”
TDD „nie działa”
- Wymaga znacznie większej dyscypliny niż inne metodyki.
- Trzeba odkryć wszystkie ścieżki i warunki.
- Nikt nie lubi czerwonej fazy.
- Nadprodukcja testów.
TDD „oszukuje” twierdzenia Gödela!
Jak?
Testy parametryzowane
revert("TDD")
Jak przetestować dużo danych
- Testy mają pewne schematy.
- setUp i tearDown to tylko część schematu.
- DRY w testach.
class SimpleCalculatorParametersTest {
@ParameterizedTest @CsvFileSource(resources = "/positive-data.csv") void simpleAddPositiveNumbers(int a, int b, int expectedSum) { int sum = sut.sum(a, b); assertThat(sum).isEqualTo(expectedSum); }
@ParameterizedTest @CsvFileSource(resources = "/exceptional-data.csv") void simpleAddOverflow(int a, int b, String expectedMessage) { assertThrows(IllegalStateException.class, () -> sut.sum(a, b), expectedMessage); }
}

Krasnoludzka logika
DDT
Data Driven Testing
Czy to ma sens?
Kiedy to ma sens?
DDT – gdzie się opłaca wdrożyć?
- Duże, już istniejące, zbiory danych
- Detekcja heisenbugów
- Regresja dla błędów z produkcji

Generowanie danych
Property Based Testing
Inne spojrzenie na kod
Properties – czyli co?
- Podzbiór danych o pewnych cechach.
- Ścisły wynik nie jest najważniejszy.
- Generowanie danych w ścisłych kontekstach.
Schemat działania
Dla każdego elementu ze zbioru (x, y, …)
Gdy wykonam pewną operację A.
Spełnione będą następujące warunki (a, b, …).
Dla każdych niepustych ciągów znaków a, b, c
ciąg powstały z ich połączenia
zawiera w sobie ciąg b.
To jest ten sam schemat!
Gdzie jest zysk?
- Możliwość pokrycia 100% wejścia
- Minimal Failing Example – jak mało trzeba by popsuć świat.
- Losowość i powtarzalność
Gdzie jest zysk?
- Pełne pokrycie funkcjonalności
- Łatwość w implementacji
- Działa z TDD tylko trochę lepiej
Quis custodiet ipsos custodes?
Metryki
i Mutanty
Najwyższą formą testów jest produkcja
Fetysz pokrycia kodu
- Mierzalność
- Sprzedawalność
- Prostota koncepcji
Inne metryki jakości
- Liczba znalezionych błędów w testach manualnych
- Liczba zgłoszeń od użytkowników
- N dni bez wypadku
JUŻ JEBŁO!
Testy mutacyjne
Czy twoje testy wykryją zmiany?
Koncepcja
- Testy powinien sprawdzać poprawność ścieżki.
- Zmiana kodu to zmian ścieżki
- Zmiana kodu bez zmiany testu/ów → porażka

Zalety
- Wykrycie niepokrytych ścieżek
Wady
- Automatyczny tester „białkowy”
- Wykrycie niejednoznaczności
- Bardzo wysoki koszt jednostkowy
- Musi być możliwość mutacji
- Wykładniczy wzrost kosztów
- Nie ratuje przed brakiem testów
- Wymusza automatyzację
A co z BDD?
BDD = TDD + DDD
@ExtendWith(JGivenExtension.class)
class SimpleCalculatorBddStyleTest {
@ScenarioStage
private GivenStage given;
@ScenarioStage
private WhenStage when;
@ScenarioStage
private ThenStage then;
//... @Test void add2And2() { given.forAddends(2, 2); when.sut(sut).performAdd(); then.sumIsEqualTo(4); } }
To nie jest BDD
To jest infrastruktura BDD
Jeszcze inne podejście
Nikt nie lubi fazy RED
Jak najmniejsze testy
TCR
Test && Commit || Revert
Dlaczego?
- Chęć utrzymania małych zmian
- Publikujemy tylko działający kod
- Metoda małego kija i dużej marchewki
mvn test ; if (( $? )) ; then git checkout HEAD -- src/main/. fi
Jak to działa?

Atomic Mickey Mouse
Kiedy stosować?
- Kata i nauka
- Pair programming
- W codziennej pracy – choć to trudne!
Jak pisać mniej testów?

Klasyczna piramida testów

Docelowa piramida testów
Dlaczego tak się dzieje?
- Rozproszenie komponentów
- Integracja jest coraz ważniejsza
- „Frameworkowanie” kodu
Value Object

Zaufanie do kompilatora

Serio?
Mniejsze testy → mniej testów
- Testowanie jest łatwiejsze.
- Większa granulacja to węższe spektrum.
- Przypadki biznesowe są przeliczalne.
class Customer { private final String firstName; private final String lastName; public Customer(String firstName, String lastName) { // ??? this.firstName = firstName; this.lastName = lastName; }
Ile testów trzeba?
14
class CustomerWithValidator { @Name private final String firstName; @Name private final String lastName; public CustomerWithValidator(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
Ile testów trzeba?
7 + biblioteka
BŁĄD!!!
class CustomerWithTypes { private final Name firstName; private final Name lastName; public CustomerWithTypes(Name firstName, Name lastName) { Preconditions.checkArgument(firstName!=null); Preconditions.checkArgument(lastName!=null); this.firstName = firstName; this.lastName = lastName; } }
Ile testów trzeba?
10
Co się stało?
- Część testów przejął kompilator.
- Nadal jest problem z null.
- Validation Framework może pomóc.
A co z bibliotekami?
Kod dołączony do naszego projektu zawsze może być źródłem problemów. Musimy ufać twórcom, że odpowiednio testują swoje rozwiązania.

HAHAHAHAHA!!!
Co dalej?
Architektura testów
Wydajność testowania
Automatyzacja
Regresja
Materiały
- JUnit – https://junit.org/junit5/
- AssertJ – https://joel-costigliola.github.io/assertj/
- Jqwik – https://jqwik.net/
- Pitest – https://pitest.org/
- jGiven – http://jgiven.org/
- TCR – https://medium.com/@kentbeck_7670/test-commit-revert-870bbd756864
Przykłady, Własności, Mutanty, czyli wypaśne testy jednostkowe
By Bartek Kuczyński
Przykłady, Własności, Mutanty, czyli wypaśne testy jednostkowe
Różne podejścia do testowania jednostkowego kodu, które pozwalają na pisanie znacznie lepszych testów i oszczędność czasu.
- 692