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