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:
Usuwam
Clientklasę i po prostu testuję, czy wywoływane są odpowiednie metody. W ten sposób robię to samo co powyżej, ale traktuję toClientjako 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żnaWrapperto 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
Wrapperbędzie wywoływał prawdziwyClientobiekt 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?