Pracuję z następującym systemem:
Network Data Feed -> Third Party Nio Library -> My Objects via adapter pattern
Niedawno mieliśmy problem polegający na tym, że zaktualizowałem używaną przeze mnie bibliotekę, co spowodowało między innymi zmianę znaczników czasu (zwracanych przez bibliotekę zewnętrzną long
) z milisekund po epoce na nanosekundy po epoce.
Problem:
Jeśli napiszę testy, które kpią z obiektów biblioteki innej firmy, mój test będzie błędny, jeśli popełniłem błąd co do obiektów biblioteki innej firmy. Na przykład nie zdawałem sobie sprawy, że znaczniki czasu zmieniły precyzję, co spowodowało potrzebę zmiany w teście jednostkowym, ponieważ moja próbka zwróciła nieprawidłowe dane. To nie jest błąd w bibliotece , stało się tak, ponieważ coś przeoczyłem w dokumentacji.
Problem polega na tym, że nie mogę mieć pewności co do danych zawartych w tych strukturach danych, ponieważ nie mogę wygenerować prawdziwych bez prawdziwego pliku danych. Obiekty te są duże i skomplikowane i zawierają wiele różnych danych. Dokumentacja biblioteki innej firmy jest słaba.
Pytanie:
Jak skonfigurować testy, aby przetestować to zachowanie? Nie jestem pewien, czy uda mi się rozwiązać ten problem w teście jednostkowym, ponieważ sam test może łatwo się pomylić. Dodatkowo zintegrowany system jest duży i skomplikowany i łatwo coś przeoczyć. Na przykład w powyższej sytuacji poprawnie wyregulowałem obsługę znaczników czasu w kilku miejscach, ale przeoczyłem jedno z nich. W moim teście integracyjnym system wydawał się robić przede wszystkim właściwe rzeczy, ale kiedy wdrożyłem go do produkcji (która ma dużo więcej danych), problem stał się oczywisty.
Obecnie nie mam procesu testów integracyjnych. Testowanie jest w gruncie rzeczy: staraj się utrzymać testy jednostkowe w dobrym stanie, dodaj więcej testów, gdy coś się psuje, a następnie wdróż na moim serwerze testowym i upewnij się, że wszystko wydaje się rozsądne, a następnie wdróż w produkcji. Ten problem ze znacznikiem czasu przeszedł testy jednostkowe, ponieważ symulacje zostały utworzone niepoprawnie, a następnie przeszedł test integracji, ponieważ nie spowodował żadnych natychmiastowych, oczywistych problemów. Nie mam działu kontroli jakości.
Timestamp
klasę (zawierający żadnej reprezentacji chcą) i zapewniają nazwanych metod (.seconds()
,.milliseconds()
,.microseconds()
,.nanoseconds()
) i oczywiście nazwanych konstruktorów. Wtedy nie byłoby problemów.Odpowiedzi:
Wygląda na to, że już robisz należytą staranność. Ale ...
Na najbardziej praktycznym poziomie zawsze dołączaj do swojego zestawu garść testów integracyjnych z pełną pętlą dla własnego kodu i pisz więcej asercji, niż ci się wydaje. W szczególności powinieneś mieć garść testów, które wykonują pełny cykl create-read- [do_stuff] -validate.
I wygląda na to, że już to robisz. Po prostu masz do czynienia z niestabilną i / lub skomplikowaną biblioteką. I w takim przypadku dobrze jest przedstawić kilka takich testów, które sprawdzają twoje rozumienie biblioteki i służą jako przykłady korzystania z biblioteki.
Załóżmy, że musisz zrozumieć i polegać na tym, jak parser JSON interpretuje każdy „typ” w łańcuchu JSON. Pomocne i trywialne jest umieszczanie czegoś takiego w swoim pakiecie:
Ale po drugie, pamiętaj, że zautomatyzowane testy dowolnego rodzaju i na prawie każdym poziomie rygorystyczności nadal nie ochronią cię przed wszystkimi błędami. Zupełnie powszechne jest dodawanie testów w miarę wykrycia problemów. Brak działu kontroli jakości oznacza, że wiele z tych problemów zostanie odkrytych przez użytkowników końcowych.
I w znacznym stopniu jest to po prostu normalne.
I po trzecie, kiedy biblioteka zmienia znaczenie wartości zwracanej lub pola bez zmiany nazwy pola lub metody lub w inny sposób „łamania” kodu zależnego (może przez zmianę jego typu), byłbym cholernie niezadowolony z tego wydawcy. I twierdzę, że nawet jeśli prawdopodobnie powinieneś przeczytać dziennik zmian, jeśli taki istnieje, prawdopodobnie powinieneś również trochę stresować wydawcę. Twierdziłbym, że potrzebują konstruktywnej krytyki ...
źródło
(new JSONParser()).parse(datastream)
, ponieważNetworkInterface
pobierają dane bezpośrednio z a, a wszystkie klasy, które wykonują rzeczywistą analizę, są pakietami prywatnymi i rozwijanymi.NetworkInterface
... czy jest to coś, do czego można karmić dane, podłączając interfejs do portu na localhost czy coś takiego?NetworkInterface
. Jest to obiekt niskiego poziomu do bezpośredniej pracy z kartą sieciową i otwierania na niej gniazd itp.Krótka odpowiedź: to trudne. Prawdopodobnie masz wrażenie, że nie ma dobrych odpowiedzi, a to dlatego, że nie ma łatwych odpowiedzi.
Długa odpowiedź: jak mówi @ptyx , potrzebujesz testów systemowych i testów integracji, a także testów jednostkowych:
Kilka konkretnych sugestii:
Widziałem programowanie opisane jako czynność uczenia się o problemie i przestrzeni rozwiązań. Osiągnięcie perfekcji z wyprzedzeniem może nie być możliwe, ale możesz się uczyć po fakcie. („Poprawiłem obsługę znaczników czasu w kilku miejscach, ale pominąłem jedno. Czy mogę zmienić typy danych lub klasy, aby uczynić obsługę znaczników czasu bardziej wyraźnym i trudniejszym do pominięcia, lub też uczynić ją bardziej scentralizowaną, aby mieć tylko jedno miejsce do zmiany? Czy mogę modyfikować moje testy w celu zweryfikowania większej liczby aspektów obsługi znaczników czasu? Czy mogę uprościć środowisko testowe, aby ułatwić to w przyszłości? Czy mogę sobie wyobrazić jakieś narzędzie, które by to ułatwiło, a jeśli tak, to czy mogę znaleźć takie narzędzie w Google? „Itd.)
źródło
Zdecydowanie się z tobą nie zgadzam. Jest to błąd w bibliotece , w rzeczywistości dość podstępny. Zmienili semantyczny typ zwracanej wartości, ale nie zmienili programowego typu zwracanej wartości. Może to spowodować wszelkiego rodzaju spustoszenie, zwłaszcza jeśli był to niewielki guz wersji, ale nawet jeśli był poważny.
Powiedzmy, że zamiast tego biblioteka zwróciła rodzaj
MillisecondsSinceEpoch
prostego opakowania zawierającegolong
. Gdy zmienili go naNanosecondsSinceEpoch
wartość, Twój kod nie skompilowałby się i oczywiście wskazałby ci miejsca, w których musisz dokonać zmian. Zmiana nie mogła po cichu uszkodzić twojego programu.Jeszcze lepiej byłby
TimeSinceEpoch
obiekt, który mógłby dostosować interfejs, ponieważ dodano większą precyzję, na przykład dodając#toLongNanoseconds
metodę obok#toLongMilliseconds
metody, nie wymagającą żadnych zmian w kodzie.Kolejnym problemem jest to, że nie masz niezawodnego zestawu testów integracyjnych w bibliotece. Powinieneś to napisać. Lepiej byłoby stworzyć interfejs wokół tej biblioteki, aby obudować ją z dala od reszty aplikacji. Rozwiązuje to kilka innych odpowiedzi (a kolejne pojawiają się podczas pisania). Testy integracyjne powinny być uruchamiane rzadziej niż testy jednostkowe. Właśnie dlatego posiadanie warstwy buforowej pomaga. Pogrupuj testy integracyjne w osobny obszar (lub nazwij je inaczej), abyś mógł je uruchomić w razie potrzeby, ale nie za każdym razem, gdy przeprowadzasz test jednostkowy.
źródło
...Ex()
metod w Win32API). Jeśli nie jest to możliwe, „zerwanie” kontraktu poprzez zmianę nazwy funkcji (lub jej typu zwracanego) byłoby lepsze niż zmiana zachowania.Potrzebujesz integracji i testów systemowych.
Testy jednostkowe wspaniale sprawdzają, czy kod zachowuje się zgodnie z oczekiwaniami. Jak zdajesz sobie sprawę, nic nie podważa twoich założeń lub zapewnia, że twoje oczekiwania są zdrowe.
O ile twój produkt nie ma niewielkiej interakcji z systemami zewnętrznymi lub nie wchodzi w interakcje z systemami tak dobrze znanymi, stabilnymi i udokumentowanymi, że można je śmiało wyśmiewać (rzadko zdarza się to w prawdziwym świecie) - testy jednostkowe nie są wystarczające.
Im wyższy poziom testów, tym bardziej będą cię chronić przed nieoczekiwanym. Jest to kosztowne (wygoda, szybkość, kruchość ...), więc testy jednostkowe powinny pozostać fundamentem twoich testów, ale potrzebujesz innych warstw, w tym - w końcu - odrobiny testów na ludziach, które mają długą drogę do złapania głupie rzeczy, o których nikt nie myślał.
źródło
Najlepiej byłoby stworzyć minimalny prototyp i zrozumieć, jak dokładnie działa biblioteka. W ten sposób zyskasz trochę wiedzy o bibliotece ze słabą dokumentacją. Prototyp może być minimalistycznym programem, który korzysta z tej biblioteki i wykonuje funkcje.
W przeciwnym razie nie ma sensu pisać testów jednostkowych z półokreślonymi wymaganiami i słabym zrozumieniem systemu.
Jeśli chodzi o konkretny problem - dotyczący używania niewłaściwych wskaźników: potraktowałbym to jako zmianę wymagań. Po rozpoznaniu problemu zmień testy jednostkowe i kod.
źródło
Jeśli korzystasz z popularnej, stabilnej biblioteki, możesz być może założyć, że nie zagra ona na ciebie paskudnych sztuczek. Ale jeśli rzeczy takie jak to, co opisałeś, zdarzają się w tej bibliotece, to oczywiście nie jest to jedno. Po tym złym doświadczeniu za każdym razem, gdy coś pójdzie nie tak podczas interakcji z biblioteką, będziesz musiał zbadać nie tylko możliwość popełnienia błędu, ale także możliwość popełnienia błędu przez bibliotekę. Powiedzmy, że jest to biblioteka „niepewna”.
Jedną z technik stosowanych w bibliotekach, co do których „nie jesteśmy pewni”, jest zbudowanie warstwy pośredniej między naszym systemem a wymienionymi bibliotekami, co wyodrębnia funkcjonalność bibliotek, potwierdza, że nasze oczekiwania względem biblioteki są słuszne, a także znacznie upraszcza w naszym życiu w przyszłości, jeśli zdecydujemy się na uruchomienie tej biblioteki i zastąpienie jej inną biblioteką, która zachowuje się lepiej.
źródło
assert
kluczowe (lub funkcja lub funkcja, w zależności od używanego języka) jest Twoim przyjacielem. Nie mówię o asercjach w testach jednostkowych / integracyjnych, mówię, że warstwa izolacyjna powinna być bardzo ciężka z asercjami, zapewniając wszystko, co można domagać się na temat zachowania biblioteki.