Jednostka testująca klienta API i opakowania

14

Krążyłem w kółko, próbując znaleźć najlepszy sposób testowania jednostkowego biblioteki klienta API, którą opracowuję. Biblioteka ma Clientklasę, która w zasadzie ma mapowanie 1: 1 z API, oraz dodatkową Wrapperklasę, która zapewnia bardziej przyjazny dla użytkownika interfejs ponad Client.

Wrapper --> Client --> External API

Najpierw napisałem kilka testów dla obu Clienti Wrapper, skutecznie testując tylko, czy przekazują one odpowiednie funkcje tego, na czym działają ( Wrapperdziała Clienti Clientdział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 Clientwszystko, 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ż Clientjest 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 Wrapperklasie. Widzę następujące opcje:

  1. Usuwam Clientklasę i po prostu testuję, czy wywoływane są odpowiednie metody. W ten sposób robię to samo co powyżej, ale traktuję to Clientjako 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żna Wrapperto bardzo dobrze zaimplementować przy użyciu zupełnie innego klienta.

  2. 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.

  3. Właśnie testuję, czy są wysyłane odpowiednie żądania HTTP. Oznacza to, że Wrapperbędzie wywoływał prawdziwy Clientobiekt 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?

Joseph Mansfield
źródło
Staram się unikać testowania jednostkowego w tych scenariuszach, w których większość pomruków wykonuje bibliotekę stron trzecich, a ja mam tylko opakowanie (głównie dlatego, że nie mam pojęcia, jak to zrobić w sposób, który testuje coś naprawdę znaczącego). Generalnie w takich przypadkach przeprowadzam testy integracyjne, prawdopodobnie z próbną usługą. Może ktoś wie, jak przeprowadzić dla nich naprawdę znaczący test jednostkowy - staram się priorytetowo traktować najbardziej krytyczne komponenty systemu pod naszą kontrolą. Tutaj kluczowa część jest poza naszą kontrolą. :-(

Odpowiedzi:

10

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:

  • Zamień cały kod funkcjonalny w procedurach obsługi REST na wywołania do IWhthingRepository.
  • Utwórz ProductionWhthingRepository z kodem, który został wyodrębniony z zasobów REST, oraz TestWhthingRespository, które zwracają odpowiedzi z puszki do użycia podczas testowania jednostkowego.
  • Użyj kontenera DI, aby wstrzyknąć bibliotekę DLL / klasę ProductionWhthingRepository lub TestWhthingRepository itp., W zależności od konfiguracji.

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

Dan1701
źródło