Testowanie oparte na właściwościach

Magda Stożek

BigDecimal calculateDiscount(Customer customer, LocalDate now);

Przykład: Liczenie zniżki

Staż:

0-1

1-2

2 +

0%

30%

10%

20%

urodziny

class Customer {
    private final String name;
    private final LocalDate joinedAt;
    private final LocalDate dateOfBirth;
}

Zniżka:

Testy oparte na przykładach

@Test
void shouldCalculateNoDiscount() {...}
@Test
void shouldCalculate10percentDiscount() {...}
@Test
void shouldCalculate20percentDiscount() {...}
@Test
void shouldCalculateBirthdayDiscount() {...}
@Test
void shouldCalculate20percentDiscountFor3Years() {...}

Oczekiwany scenariusz:

Przypadki brzegowe:

A w języku właściwości?

BigDecimal calculateDiscount(Customer customer, LocalDate now);

Przykład: Liczenie zniżki

Staż:

0-1

1-2

2 +

0%

30%

10%

20%

urodziny

class Customer {
    private final String name;
    private final LocalDate joinedAt;
    private final LocalDate dateOfBirth;
}

Zniżka:

Biblioteki

  • Początek:
  • Potem:
    • C, C++, C#, Chicken, Clojure, Common Lisp, D, Elm, Elixir, Erlang, F#, Factor, Go, Io, Java, JavaScript, Julia, Kotlin, Logtalk, Lua, Node.js, Objective-C, OCaml, Perl, Prolog, PHP, Pony, Python, R, Racket, Ruby, Rust, Scala, Scheme, Smalltalk, Standard ML, Swift, Typescript, VB.NET, ...

Co dla Javy?

@Property
boolean discountShouldNotBeOver30percent(@ForAll("validCustomer") Customer customer, @ForAll("futureDate") LocalDate now) {

    BigDecimal discount = calculator.calculateDiscount(customer, now);
    return discount.compareTo(new BigDecimal("0.3")) <= 0;
}

Właściwość: zniżka nigdy nie przekracza 30%

@Property
boolean shouldNotBeOver30percent(
    @ForAll Customer customer, 
    @ForAll("futureDate") LocalDate now) 
{
    BigDecimal discount = calculator.calculateDiscount(customer, now);
    Assertions.assertThat(discount).compareTo(new BigDecimal("0.3")) <= 0;
}
                              |-------------------jqwik-------------------
tries = 374                   | # of calls to property
checks = 374                  | # of not rejected calls
generation-mode = RANDOMIZED  | parameters are randomly generated
after-failure = PREVIOUS_SEED | use the previous seed
seed = -272292185905571017    | random seed to reproduce generated values
sample = 
[Customer{name='AAA', joinedAt=2010-01-01, dateOfBirth=1980-02-29}, 2019-10-06]
original-sample = 
[Customer{name='DtQDpPfEx', joinedAt=2010-04-21, dateOfBirth=1980-02-29}, 2185-03-17]

Wykryty błąd

Wyzwania

Trudne pytania wcześniej

Wyzwanie #1

Niedeterminizm

Wyzwanie #2

Wymyślanie właściwości

Wyzwanie #3

Strategie

  • najpierw przykłady, potem właściwości
  • właściwości dla kluczowych elementów

Podsumowanie

  • większy koszt, większa korzyść
  • przypadki brzegowe i luki w rozumowaniu
  • wymyślanie właściwości
  • proporcje

Więcej

@Property
boolean endOfPresentation(@ForAll Attendee attendee) {
    Assert.that(attendee.wantsToTryIt);
}