Krążyłem w kółko, próbując znaleźć najlepszy sposób testowania jednostkowego biblioteki klienta API, którą opracowuję. Biblioteka ma Client
klasę, która w zasadzie ma mapowanie 1: 1 z API, oraz dodatkową Wrapper
klasę, która zapewnia bardziej przyjazny dla użytkownika interfejs ponad Client
.
Wrapper --> Client --> External API
Najpierw napisałem kilka testów dla obu Client
i Wrapper
, skutecznie testując tylko, czy przekazują one odpowiednie funkcje tego, na czym działają ( Wrapper
działa Client
i Client
działa na połączeniu HTTP). Zacząłem jednak czuć się nieswojo, ponieważ mam wrażenie, że testuję implementację tych klas, a nie interfejs. Teoretycznie mógłbym zmienić klasy tak, aby miały kolejną doskonale poprawną implementację, ale moje testy zakończyłyby się niepowodzeniem, ponieważ funkcje, których oczekiwano, że zostaną wywołane, nie są wywoływane. Dla mnie to brzmi jak kruche testy.
Potem pomyślałem o interfejsie klas. Testy powinny zweryfikować, czy klasy faktycznie wykonują zadanie, które powinny wykonać, a nie jak to robią. Jak mogę to zrobić? Pierwszą rzeczą, która przychodzi na myśl, jest kasowanie zewnętrznych żądań API. Niepokoi mnie jednak nadmierne uproszczenie usług zewnętrznych. Wiele przykładów zagubionych interfejsów API, które widziałem, daje po prostu odpowiedzi w puszkach, co wydaje się bardzo prostym sposobem na sprawdzenie, czy kod działa poprawnie z fałszywym interfejsem API. Alternatywą jest wyśmiewanie się z usługi, która jest po prostu niewykonalna i wymagałaby aktualizacji na bieżąco, gdy zmienia się rzeczywista usługa - wydaje się, że to przesada i strata czasu.
Na koniec przeczytałem to z innej odpowiedzi na temat programistów SE :
Zadaniem zdalnego klienta API jest wykonywanie określonych połączeń - nie więcej, nie mniej. Dlatego jego test powinien zweryfikować, czy wykonuje te połączenia - nie więcej, nie mniej.
A teraz jestem mniej więcej przekonany - podczas testowania Client
wszystko, co muszę przetestować, to to, że wysyła prawidłowe żądania do API (Oczywiście, zawsze istnieje możliwość, że API się zmieni, ale moje testy nadal przechodzą - ale to gdzie przydatne byłyby testy integracyjne). Ponieważ Client
jest to tylko mapowanie 1: 1 z interfejsem API, moja obawa przed zmianą z jednej ważnej implementacji na inną tak naprawdę nie ma zastosowania - istnieje tylko jedna poprawna implementacja dla każdej metody Client
.
Nadal jednak utknąłem w Wrapper
klasie. Widzę następujące opcje:
Usuwam
Client
klasę i po prostu testuję, czy wywoływane są odpowiednie metody. W ten sposób robię to samo co powyżej, ale traktuję toClient
jako stand-in dla API. To przywraca mnie do początku. Po raz kolejny daje mi to niewygodne wrażenie testowania implementacji, a nie interfejsu. MożnaWrapper
to bardzo dobrze zaimplementować przy użyciu zupełnie innego klienta.Tworzę próbę
Client
. Teraz muszę zdecydować, jak daleko posunąć się z wyśmiewaniem go - utworzenie pełnej makiety usługi wymagałoby dużo wysiłku (więcej pracy niż w samej bibliotece). Sam interfejs API jest prosty, ale usługa jest dość złożona (zasadniczo jest to magazyn danych z operacjami na tych danych). I znowu będę musiał zsynchronizować swoją próbę z rzeczywistościąClient
.Właśnie testuję, czy są wysyłane odpowiednie żądania HTTP. Oznacza to, że
Wrapper
będzie wywoływał prawdziwyClient
obiekt w celu wykonania tych żądań HTTP, więc tak naprawdę nie testuję go w izolacji. To sprawia, że jest to trochę okropny test jednostkowy.
Nie jestem więc szczególnie zadowolony z żadnego z tych rozwiązań. Co byś zrobił? Czy jest na to właściwy sposób?
Odpowiedzi:
TLDR : Mimo trudności powinieneś zlikwidować usługę i użyć jej do testowania jednostek klienta.
Nie jestem pewien, czy „zadaniem zdalnego klienta API jest wydawanie określonych wywołań, nie więcej, nie mniej ...”, chyba że interfejs API składa się tylko z punktów końcowych, które zawsze zwracają stały status i nie zużywają ani nie produkują jakiekolwiek dane. To nie byłby najbardziej użyteczny interfejs API ...
Chciałbyś również sprawdzić, czy klient nie tylko wysyła prawidłowe żądania, ale również odpowiednio obsługuje treść odpowiedzi, błędy, przekierowania itp. I przetestuj dla wszystkich tych przypadków.
Jak zauważyłeś, powinieneś mieć testy integracyjne obejmujące cały stos z opakowania -> klient -> usługa -> DB i nie tylko, ale aby odpowiedzieć na główne pytanie, chyba że masz środowisko, w którym testy integracyjne mogą być uruchamiane w ramach każdego Kompilacja CI bez wielu problemów (współużytkowane testowe bazy danych itp.), Powinieneś poświęcić czas na utworzenie kodu API.
Kod pośredniczący pozwoli ci stworzyć działającą implementację usługi, ale bez konieczności implementowania jakiejkolwiek warstwy poniżej samej usługi.
Aby to osiągnąć, możesz rozważyć zastosowanie rozwiązania opartego na DI, z implementacją wzorca repozytorium pod zasobami REST:
W każdym razie, chyba że nie będzie mowy o politycznym ani praktycznym wykluczeniu stubowania i / lub refaktoryzacji usługi, prawdopodobnie podjąłbym coś podobnego do powyższego. Jeśli nie jest to możliwe, upewnij się, że mam naprawdę dobry zasięg integracji i uruchamiam je tak często, jak to możliwe, biorąc pod uwagę dostępne ustawienia testowe.
HTH
źródło