poniedziałek, 16 maja 2011

JUnitParams

Niedawno dopisałem do Tumblera możliwość definiowania scenariuszy parametryzowanych. Chodzi o to, żeby móc zdefiniować parę zestawów parametrów scenariusza i mieć ten sam jeden scenariusz wykonany dla wszystkich zestawów, weryfikując w ten sposób jego poprawność dla różnych danych. Ale jako, że nie wszyscy chcą używać Tumblera (i nie zawsze jest sens), przepisałem tę funkcjonalność do czystego JUnit'a. Po co? Głównie dlatego, że testy parametryzowane w JUnit to porażka. Z runnerem Parameterized Testy są wg. mnie mniej czytelne, tworzy się dużo klas, generalnie kiszka. Na dodatek do Parameterized są też teorie, które miały rozwiązać parę problemów, ale chyba nikt ich nie używa.
Ostatnio jednak mieliśmy w projekcie sytuację, w której testy parametryzowane byłyby wygodne... gdyby były wygodne. Więc wyciągnąłem ten kawałek Tumbler'a na zewnątrz i tak powstał JUnitParams. Teraz testy parametryzowane w JUnit są dużo przyjemniejsze.

Jak to działa?
1. Trzeba zdefiniować runner (JUnitParamsRunner) -  bo JUnit nie umie wykonać tego samego testu parę razy, a przecież o to nam chodzi.
2. Stworzyć metodę testową z parametrami metody testowej oraz zadnotować ją za pomocą @Parameters podając parametry bezpośrednio w adnotacji, lub podając klasę dostarczającą te wartości z zewnątrz. Jeśli chcemy podać parametry bezpośrednio, to podajemy je jako tablicę String'ów, której każdy element jest jednym zestawem parametrów:
@Parameters({"1, Jaś, true", "2, Andżelika, false" })
Tak podane parametry są następnie parsowane i rzutowane na odpowiednie podstawowe typy javowe.
Jeśli zaś zestawów parametrów ma być więcej, lub chcemy je pobrać dynamicznie np. z pliku bądź bazy, możemy podać klasę dostarczającą wartości parametrów:
@Parameters(source=NamesParamsProvider.class)
Co do tej klasy nie ma żadnych wymagań poza posiadaniem przynajmniej jednej publicznej statycznej bezparametrowej metody zaczynającej się od provide. Wszystkie takie metody są uruchamiane, ich wyniki zbierane i dostarczane do metody testowej.
Można też przekazywać parametry bez zewnętrznej klasy - podając metodę testu zwracającą dane w adnotacji:
@Parameters(method="samplePeople")
3. Już. Żadnych konstruktorów, pól w klasie testowej, czy innych zbędnych dodatków.

Przykładowy test z parametrami wygląda np. tak:

@RunWith(JUnitParamsRunner.class)
public class PersonTest {

  @Test
  @Parameters(method = "adultValues")
  public void isAdult(int age, boolean valid) {
    assertThat(new Person(age).isAdult(), is(valid));
  }

  private Object[] adultValues() {
    return $(
      $(17, false),
      $(22, true)
    );
  }
}

Więcej przykładów na stronie projektu.

7 komentarzy:

  1. gratulacje, ale dlaczego po prostu nie przesiadłeś się na TestNG gdzie eleganckie parametryzowane testy masz out-of-the-box?

    --
    pozdrawiam
    Tomek Kaczanowski

    OdpowiedzUsuń
  2. Dopisuję się do Tomka, pragmatycznie to było by użyć testNG.
    Dziwi mnie, że ludzie ignorują go, pomimo iż przesiadka to 0rowy koszt.

    OdpowiedzUsuń
  3. 1. Mamy setki testów JUnitowych.
    2. Wiele innych zespołów też.
    3. Tak jakoś zawsze był JUnit, wszyscy znają JUnita...

    Napisałem, bo potrzebowaliśmy czegoś na szybko. A jak już było, to głupio nie opublikować.

    OdpowiedzUsuń
  4. No zerowy koszt to nie jest. Jak masz 3 testy, to zerowy. Jak 1500 to już nie.

    OdpowiedzUsuń
  5. zaiste siła inercji wielką jest :)

    pozdrawiam!
    Tomek

    OdpowiedzUsuń
  6. tak tylko gwoli ścisłości, nie setki a tysiące.
    Dokładniej ok 5 tyś.

    OdpowiedzUsuń
  7. Koszt = blisko zero bo można konwertować się pomiędzy nimi automatycznie więc 3, 300, 3000 nie ma znaczenia. Po konwersji sprawdzamy czy wszystko działa.
    Część będąca bliską 0 to czynnik zaufania do narzędzia.
    Jako, że umiem sobie wyobrazić jak to narzędzie działa, to miałbym dość wysoki poziom zaufania do niego, o ile wiem, że nie robiłem haków w testach / nie polegam na stanie testów.
    Znacie najlepiej swoje testy, wasz wybór.
    Cieszę się jednak, że opublikowaliście kod :) Wpierw nie zauważyłem, że to open source jest

    OdpowiedzUsuń