Piszę testy na 100%

testyBajki

Znowu wpis o mitach. Bo jak często widać, że w jakimś projekcie pokrycie testami wynosi 100%? Częściej się o tym mówi, niż widzi.

Menagiery i biznes się chyba pogodził z tym, że ciężko będzie to osiągnąć i miękną, mówią dobra to robimy 80% albo 60% jeśli projekt mało ważny, a metryki jakieś muszą mieć – a szkoda, bo to nie jest aż taka ciężka sprawa.

Dygresja

Teraz taka myśl przyszła mi do głowy, czy ktoś patrzy w ogóle na te procenty, które kryją kod? Bo jak się uprzeć, to wysoki poziom można zrobić, bez napisania sensownych testów. Np. przetestować wszystkie właściwości: zarówno get i set. I już kilka procent zaliczone.Ale nie o tym chciałem pisać.

Co zrobić, gdy za jakiś kawałek kodu ciężko się zabrać, albo jest tam coś systemowego? Ja wtedy zawsze wracam do mojej mantry, którą jest spychologia. Alternatywą jest microsoft fakes – ale to dużo roboty.

Treść właściwa wpisu nastąpi teraz

Przykład z mojego projektu:

Tworze XmlDocument(@3) oraz ładuje do niego xml ze strumienia (@4), który przychodzi w modelu. Następnie wyciągam elementy (@5), wreszcie w pętli dla każdego sprawdzam czy posiada ważne dla mnie atrybuty (@9,10,11) oraz ostatecznie wrzucam je do worka z kanałami, który na końcu będzie zwrócony do klienta klasy.

Co z tym zrobić

To teraz jak to przetestować? Muszę dostarczyć strumień w modelu, to się da – źródła dostępne na githubie (przyp. autor). Ale jak przetestować tworzenie dokumentu, oraz wyciąganie z niego węzłów xml? A może nie testować tego, tylko wywalić to z klasy i niech ktoś inny (jestem sam w projekcie) się martwi, a póki co przyjąć że ktoś mi je automagicznie dostarczy na podstawie strumienia który mu wyślę. W ten oto sposób pozbywam się tej i każdej kolejnej napotkanej przeszkody w testach.
Nie podoba mi się także ten warunek sprawdzający czy węzeł posiada wymagane informację, to także wywalę z tej metody. Zresztą zobaczcie sami jak wygląda kod po przejściach:

Tak, tam jest String a nie string. O tym też kiedy indziej.

I teraz tak, samo ładownia to magia(@3), potem wyciągam tylko te poprawne (@4 i @13). Potem zamiast z pętli korzystam z LINQ i tworzę kolekcję kanałów (@5-@9) którą ostatecznie zwracam. Kod jest krótszy i czytelniejszy. A jednocześnie mogę teraz spokojnie napisać do niego testy. Co też czynie.

Dzięki tym zabiegom osiągam 100% pokrycia testami w klasie OpmlImporterService.

A tutaj zero

I teraz uwaga uważny czytelniku, klasa do ładowania węzłów opml z xml wygląda tak:

I nie zamierzam jej testować. Uznaje ją za wystarczająco prostą, aby mogła przejść do repozytorium bez testów i tak pozostać.

Poprawki

Co można zrobić lepiej?

  1. Z klasy którą właśnie uznałem za wystarczająco prostą można wynieść ładowanie węzłów (zalążek logiki) i umieścić gdzieś, gdzie można będzie to przestestować
  2. W serwisie do importowania kanałów, wynieść sprawdzanie poprawności węzłów do osobnej klasy, tak aby jeden z serwisow zupelnie uwalnić od zależności od wiedzy o XML.
  3. Zamienić tworzenie RssChannel z XmlNode na AutoMappera.
  4. Poprawić nazwy metod

Wszystkie te kroki przyczynią się do zmniejszenia ilości kodu w serwisie, a to spowoduje zmniejszenie zapotrzebowania na testy, które muszę utrzymywać aby ciągle mieć 100%. Ale tą przyjemność zostawię sobie na później.

Czy 100% jest fajne? Czy warto? Cóż dla posiadania 100% pewnie tak. Czy daje pewność? Nigdy w życiu. Ja pomimo posiada 100% nie sprawdziłem jeszcze negatywnych ścieżek, które potrafię wymyślić. Zostają jeszcze przypadki, które nie przychodzą nam do głowy.

Co daje w takim razie 100% – moim zdaniem inną ważną rzecz, każda zmiana która spowoduje zmianę zachowania w serwisie zostanie teraz bardzo szybko zauważona, każda nowa zależność, zmiana przepływu, logiki, etc natychmiast się ujawni – co pozwala na szybsze ogarnięcie projektu jako całości ,oraz ułatwi zachowanie wcześniej przejętego schematu zachować aplikacji – jest spójności.No i fajnie wygląda.

Nie spinałbym się żeby osiągać 100% pokrycia testami w projekcie, ja robię to dla siebie ponieważ mogę, mam czas i nikt i nic mnie nie goni. Robię to ponieważ siadam do programowania z różną częstotliwością i zdarza mi się zapomnieć tego co wymyśliłem sobie wcześniej. To dzięki testom pewne „ustalenia” wracają i wszystko działa tak jak powinno.

7 thoughts on “Piszę testy na 100%

  1. Pingback: dotnetomaniak.pl
  2. W metodzie GetOutlines możesz popełnić literówkę w stałej tekstowej „outline”. Ja bym dopisał do niej test nie dlatego żeby mieć 100% pokrycia, lecz dlatego, że jest to potencjalne miejsce pomyłki.

    Żeby było ciekawiej, pomyłka nie musi nastąpić teraz. Wyobraź sobie, że ktoś kiedyś w przyszłości zmieni „outline” na coś innego. Albo ktoś inny zmieni sposób generowanie dokumentów XML.

    I bez testu mamy buuuuuuuum.

    1. Kupuje to review, masz 100% racji – dzięki! A jeśli popatrzysz na rzeczy do poprawki, to punkt pierwszy o tym też wspomina. To właśnie ten element jest na tyle ryzykowny, że warto go poprawić – jak tylko będzie czas i chęci.

  3. A dziękuję.

    Uważam, że cały kod który napisaliśmy powinien być przetestowany bo może być błędny. Czyli mierząc pokrycie testami, to 100% linii kodu musi mieć pokrycie, chyba że świadomie podejmiemy decyzję o rezygnacji z testów.

    Ale rzecz w tym, że mierzenie pokrycia testami tylko na podstawie linii kodu prowadzi do błędnych wniosków. Prosty przykład:

    function Dzielenie(float a, float b)
    return a / b

    Pojedynczy test Dzielenie(1, 1) daje 100% pokrycia kodu. Wystarczy? Cel osiągnięty? No chyba nie.

    Prawdziwe pokrycie należy mierzyć przypadkami użycia, a dla tego przykładu mogę dopisać:
    Dzielenie(1, 0)
    Dzielenie(1, inf)
    Dzielenie(1, nan)
    i wiele, wiele innych.

    Pokrycie testami mierzone na podstawie linii kodu wyniesie zapewne coś około 2000% albo i więcej. I dopiero taki test ma sens.

    PS
    Czy test dla property należy pisać?
    Dla automatycznych mówię nie, a dla property ze zmienną? Popatrz na to.

    private int a;
    private int b;

    public int A set { a= value; } get { return a; }
    public int B set { a= value; } get { return b; }

    I jak, test niepotrzebny?

    1. Znowu jestem na tak 🙂
      Co do testowania właściwości: jeśli jesteś pewien że nigdy, przenigdy, za żadne skarby nie będzie tam logiki, to możesz pominąć, nawet jeśli jest backfield. Ale jeśli dopuszczasz taką myśl do siebie, że może kiedyś będzie trzeba coś sprawdzić na set, albo na get – wtedy oczywiście należy napisać testy.
      Jeśli chcesz mieć idealny projekt i być gotowy na wszystko napisz testy wcześniej, bo nie wiesz czy ktoś inny w projekcie nie dołoży logiki wcześniej i proste właściwości stracą na swojej prostocie.

Dodaj komentarz