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?