Tydzień temu omówiliśmy najprostszy przykład testowania kodu w elixir za pomocą przykładów w dokumentacji metody. W tym tygodniu skoncentrujemy się na tym na jakiej zasadzie to działa i jak dodać jakieś bardziej złożone testy. Warto tutaj zaznaczyć, że elixir jest językiem, który przychodzi z testami jednostkowymi i są one nieodłączną cechą języka.
Jak rozpocząć przygodę z testowaniem w elixir?
Jeżeli korzystamy z mix
przy tworzeniu projektu to nic nie musimy zrobić. Zostaną za nas utworzone dwa pliki:
test_helper.exs appname_test.exs
Pierwszy z nich będzie zawierał tylko i wyłącznie jedną metodę:
ExUnit.start
Która przygotuje i odpali nam serwer testowy. Jest to bardzo ważne, bez tego test nie zostaną wykonane.
Drugi plik zaś zawiera prosty moduł:
defmodule AppNameTest do use ExUnit.Case doctest AppName test "the truth" do assert 1 + 1 == 2 end end
Na samym początku, zostanie użyte makro które wciągnie nam moduł dając możliwość wykorzystania makr w nim zawartych (tak zwane require
) oraz zostanie wykonana metoda która skonfiguruje nasz moduł – zaimplementuje domyślne callbacki itp. Dzięki czemu nie musimy prawie nic innego pisać. Co lepsze metoda __using__
, zaimportuje nam makra które umożliwią tworzenie testów i sprawdzanie wartości – umożliwi napisanie test i assert zamiast ExUnit.Case.test
i ExUnit.Assertions.assert
.
Co śmieszne, ExUnit.Case
który wymagamy sam siebie importuje ;)
Od teraz w naszym module możemy pisać testy zaczynając od makra test
, następnie opisując co test ma robić i implementując test, wiedząc, że mamy dostęp do AppName.method
.
Asynchroniczny czy nie
Możemy, jeżeli chcemy uruchamiać testy asynchronicznie lub synchronicznie. To jak uruchomimy testy będzie zależało od naszego kodu. Jeżeli jest on odizolowany i może być testowany jednostkowo wtedy async
ma ręce i nogi. Jeżeli jednak zabieramy się za testowanie bardziej integracyjne, dotykające kilku warstw i które polega na jakimś globalnym stanie, to async
niekoniecznie musi być dobrym wyjściem.
Konfigurację dotyczącą tego jak testy są wykonywane podajemy w trakcie użycia ExUnit.Case
:
use ExUnit.Case, asyc: true|false
Domyślnie, async
ustawiany jest na false
.
Sprawdzanie wyniku
To jest całkowicie opcjonalne. Jeżeli chcemy mieć więcej informacji, to możemy się stosować do makr tutaj opisanych. Jeżeli jednak nas to nie interesuje, to wystarczy, ze nasz kod będzie zwracał jakiś błąd. Na przykład match error:
test "test" do 2 = 3 end
To do dostaniemy to:
1) test the truth (BencodeTest) test/bencode_test.exs:5 ** (MatchError) no match of right hand side value: 3 stacktrace: test/bencode_test.exs:6: (test)
Zamiast:
1) test the truth (BencodeTest) test/bencode_test.exs:5 Assertion with == failed code: 2 == 3 lhs: 2 rhs: 3 stacktrace: test/bencode_test.exs:6: (test)
assert i refute
Mamy dwie możliwości z assert
. Możemy założyć, że coś ma daną wartość:
1 + 1 == 2
Lub możemy założyć, że coś nie ma takiej wartości
1 + 1 == 3
W pierwszym wypadku korzystamy z assert
, w drugim refute
.
assert_raise
Podobnie też jak w językach obiektowych, możemy założyć, że coś wywali błąd: assert_raise
który przyjmuje typ błędu a potem lambdę która ma ten błąd wywołać:
assert_raise ArithmeticError, fn -> 1 + "test" end
assert_received i assert_receive
Makra przydają się kiedy chcemy sprawdzić czy proces otrzymał wiadomość wysłaną za pomocą send
. Na wykonanie metody się nie czeka (received
) albo czeka (receive
). Makra weryfikują, czy wiadomość znajduje się w skrzynce odbiorczej procesu. Jeżeli nie, to dostaniemy wyjątek.
test "receives ping" do send self(), :ping assert_received :ping end
inne
Jest jeszcze parę opcji, jak na przykład catch_error
czy catch_exit
ale z nich nie korzystałem i nawet nie wiem zbytnio co i jak. Ogólnie to tak jakby oczekiwać, że będzie błąd i zwrócić ten błąd. Nie wiem zbytnio czemu miałbym użyć to a nie assert_raise
. Chyba że diabeł tkwi w szczegółach.
Co to jest doctest?
doctest
to makro, które działa podobnie jak use, z tym wyjątkiem, że zamiast wywoływać metodę __using__
robi to dla metody __doctests__
która jest odpowiedzialna za:
- Wczytanie wszystkich komentarzy w danym module
- Przefiltrowanie komentarzy po parametrach doctest (możemy określić jakie metody mają działać
:only
, lub które mają nie być brane pod uwagę:except
) - Wygenerowanie kodu w locie którego zawartość testu jest równa przykładowi, a porównanie naszemu wynikowi
Dosłownie doctest wykorzystuje to, że elixir jest meta językiem i w locie tworzy nam kod, który ma zostać przekompilowany i uruchomiony.
Nie ma tutaj więc magii, ładowany jest nasz plik modułu, analizowany i na końcu wypluta jest konwersja.
Podsumowanie
Na tym zakończymy część pierwszą. W drugiej zajmiemy się parametrami, grupowaniem i może już też pisaniem testów, jak nie to zrobimy to w trzeciej. Jak się okazuje, tego jest tyle, że by to ogarnąć i napisać rozsądnie a nie po łebkach to musiałem się zatrzymać.
Szczegółów doctest – implementacyjnych – nie omawiam, bo to będzie dobry pomysł by zrobić osobny post może dwa jak coś takiego sami możemy zrobić. Bardzo fajna opcja.
Do przeczytania za tydzień!