Niektórzy twierdzą, że testy integracyjne są wszelkiego rodzaju złe i złe - wszystko musi być testowane jednostkowo, co oznacza, że musisz kpić z zależności; opcja, która z różnych powodów nie zawsze mi się podoba.
Uważam, że w niektórych przypadkach test jednostkowy po prostu niczego nie dowodzi.
Jako przykład weźmy następującą (trywialną, naiwną) implementację repozytorium (w PHP):
class ProductRepository
{
private $db;
public function __construct(ConnectionInterface $db) {
$this->db = $db;
}
public function findByKeyword($keyword) {
// this might have a query builder, keyword processing, etc. - this is
// a totally naive example just to illustrate the DB dependency, mkay?
return $this->db->fetch("SELECT * FROM products p"
. " WHERE p.name LIKE :keyword", ['keyword' => $keyword]);
}
}
Powiedzmy, że chcę udowodnić w teście, że to repozytorium może faktycznie znaleźć produkty pasujące do różnych podanych słów kluczowych.
Bez testowania integracji z rzeczywistym obiektem połączenia, skąd mam wiedzieć, że generuje to prawdziwe zapytania - i że te zapytania faktycznie robią to, co myślę?
Jeśli muszę kpić z obiektu połączenia w teście jednostkowym, mogę tylko udowodnić, że „generuje oczekiwane zapytanie” - ale to nie znaczy, że faktycznie zadziała ... to znaczy, może generuje zapytanie Spodziewałem się, ale może to zapytanie nie robi tego, co myślę, że działa.
Innymi słowy, czuję się jak test, który wysuwa twierdzenia na temat wygenerowanego zapytania, jest w zasadzie bez wartości, ponieważ testuje sposób implementacjifindByKeyword()
metody , ale to nie dowodzi, że faktycznie działa .
Ten problem nie ogranicza się do repozytoriów lub integracji bazy danych - wydaje się, że ma zastosowanie w wielu przypadkach, w których stwierdzenia dotyczące użycia próbnego (podwójnego testu) tylko dowodzą, jak rzeczy są realizowane, a nie czy zamierzają faktycznie działa.
Jak sobie radzisz w takich sytuacjach?
Czy testy integracyjne są naprawdę „złe” w takim przypadku?
Rozumiem, że lepiej przetestować jedną rzecz, a także rozumiem, dlaczego testowanie integracji prowadzi do niezliczonych ścieżek kodu, z których wszystkie nie mogą być testowane - ale w przypadku usługi (takiej jak repozytorium), której jedynym celem jest w celu interakcji z innym komponentem, w jaki sposób można naprawdę przetestować coś bez testowania integracji?
źródło
Odpowiedzi:
Twój współpracownik ma rację, że wszystko, co może być testowane jednostkowo, powinno być testowane jednostkowo, i masz rację, że testy jednostkowe zabiorą cię tylko tak daleko i nie dalej, szczególnie podczas pisania prostych opakowań wokół złożonych usług zewnętrznych.
Powszechnym sposobem myślenia o testowaniu jest piramida testowa . Jest to koncepcja często związana z Agile i wiele o niej pisało, w tym Martin Fowler (który przypisuje ją Mike'owi Cohnowi w „ Sukcesie z Agile” ), Alistair Scott i Blog testowy Google .
Chodzi o to, że szybkie, odporne testy jednostkowe są podstawą procesu testowania - powinny być bardziej ukierunkowane testy jednostkowe niż testy systemowe / integracyjne, a więcej testów systemowych / integracyjnych niż testy kompleksowe. W miarę zbliżania się do szczytu testy zwykle wymagają więcej czasu / zasobów, poddawane są kruchości i kruchości, a także mniej precyzyjne określanie, który system lub plik jest uszkodzony ; naturalnie lepiej jest unikać bycia „ciężkim”.
Do tego momentu testy integracyjne nie są złe , ale duże poleganie na nich może wskazywać, że nie zaprojektowałeś poszczególnych komponentów, aby były łatwe do przetestowania. Pamiętaj, że celem jest sprawdzenie, czy Twoja jednostka działa zgodnie ze specyfikacją, przy minimalnej liczbie innych systemów łamliwych : możesz wypróbować bazę danych w pamięci (którą liczę jako test przyjazny dla jednostki podwójnie obok prób ), na przykład w przypadku ciężkich testów skrajnych przypadków, a następnie napisz kilka testów integracyjnych z prawdziwym silnikiem bazy danych, aby ustalić, że główne przypadki działają podczas montażu systemu.
Na marginesie wspomniałeś, że pisane przez ciebie makiety po prostu sprawdzają, jak coś jest implementowane, a nie czy to działa . To coś w rodzaju antypatternu: test, który jest idealnym odzwierciedleniem jego implementacji, tak naprawdę wcale nie testuje. Zamiast tego sprawdź, czy każda klasa lub metoda zachowuje się zgodnie z własną specyfikacją , na dowolnym wymaganym poziomie abstrakcji lub realizmu.
źródło
To trochę jak powiedzenie, że antybiotyki są złe - wszystko należy wyleczyć za pomocą witamin.
Testy jednostkowe nie wychwytują wszystkiego - testują jedynie działanie komponentu w kontrolowanym środowisku . Testy integracyjne sprawdzają, czy wszystko działa razem , co jest trudniejsze, ale w końcu bardziej znaczące.
Dobry, kompleksowy proces testowania wykorzystuje oba typy testów - testy jednostkowe w celu weryfikacji reguł biznesowych i innych rzeczy, które mogą być testowane niezależnie, oraz testy integracyjne, aby upewnić się, że wszystko działa razem.
Państwo mogłoby jednostka przetestować go na poziomie bazy danych . Uruchom zapytanie z różnymi parametrami i sprawdź, czy uzyskasz oczekiwane wyniki. Oczywiście oznacza to kopiowanie / wklejanie wszelkich zmian z powrotem do „prawdziwego” kodu. ale to nie pozwalają, aby przetestować niezależnie zapytania wszelkich innych zależności.
źródło
Testy jednostkowe nie wychwytują wszystkich wad. Są jednak tańsze w konfiguracji i (ponownym) uruchamianiu w porównaniu z innymi rodzajami testów. Testy jednostkowe są uzasadnione przez połączenie umiarkowanej wartości i niskich do umiarkowanych kosztów.
Oto tabela przedstawiająca wskaźniki wykrywania defektów dla różnych rodzajów testów.
źródło: str.470 w Code Complete 2 autorstwa McConnell
źródło
Nie, nie są złe. Mamy nadzieję, że należy przeprowadzić testy jednostkowe i integracyjne. Są używane i działają na różnych etapach cyklu rozwoju.
Testy jednostkowe
Testy jednostkowe powinny być uruchamiane na serwerze kompilacji i lokalnie, po skompilowaniu kodu. Jeśli jakiekolwiek testy jednostkowe zakończą się niepowodzeniem, należy zakończyć kompilację lub nie zatwierdzić aktualizacji kodu, dopóki testy nie zostaną naprawione. Powodem, dla którego chcemy izolować testy jednostkowe, jest to, że chcemy, aby serwer kompilacji mógł uruchamiać wszystkie testy bez wszystkich zależności. Następnie moglibyśmy uruchomić kompilację bez wszystkich wymaganych wymaganych zależności i przeprowadzić wiele testów, które działają bardzo szybko.
Tak więc w przypadku bazy danych należy mieć coś takiego:
Teraz rzeczywista implementacja IRepository trafi do bazy danych, aby uzyskać produkty, ale w celu przeprowadzenia testów jednostkowych można wyśmiewać IRepository z fałszywym, aby uruchomić wszystkie testy w razie potrzeby bez bazy danych Actaul, ponieważ możemy symulować wszelkiego rodzaju listy produktów są zwracane z fałszywej instancji i testują dowolną logikę biznesową z fałszywymi danymi.
Testy integracyjne
Testy integracyjne są zazwyczaj testami przekraczania granic. Chcemy uruchamiać te testy na serwerze wdrażania (prawdziwe środowisko), piaskownicy, a nawet lokalnie (wskazane na piaskownicy). Nie są uruchamiane na serwerze kompilacji. Po wdrożeniu oprogramowania w środowisku zwykle będą one uruchamiane jako działania po wdrożeniu. Można je zautomatyzować za pomocą narzędzi wiersza poleceń. Na przykład możemy uruchomić nUnit z wiersza poleceń, jeśli podzielimy na kategorie wszystkie testy integracyjne, które chcemy wywołać. W rzeczywistości wywołują one prawdziwe repozytorium z prawdziwym wywołaniem bazy danych. Tego typu testy pomagają w:
Testy te są czasem trudniejsze do przeprowadzenia, ponieważ może być konieczne ustawienie i / lub zburzenie. Rozważ dodanie produktu. Prawdopodobnie chcemy dodać produkt, zapytać go, czy został dodany, a następnie po zakończeniu usunąć go. Nie chcemy dodawać setek lub tysięcy produktów „integracyjnych”, dlatego wymagana jest dodatkowa konfiguracja.
Testy integracyjne mogą okazać się bardzo cenne przy sprawdzaniu poprawności środowiska i upewnieniu się, że działa.
Trzeba mieć oba.
źródło
Testy integracji bazy danych nie są złe. Co więcej, są one konieczne.
Prawdopodobnie masz aplikację podzieloną na warstwy, i to dobrze. Możesz przetestować każdą warstwę w izolacji, wyśmiewając sąsiednie warstwy, i to też dobrze. Ale bez względu na to, ile warstw abstrakcji utworzysz, w pewnym momencie musi być warstwa, która wykonuje brudną robotę - faktycznie rozmawia z bazą danych. O ile go nie przetestujesz, w ogóle go nie testujesz. Jeśli testujesz warstwę n przez kpinę z warstwy n-1, oceniasz założenie, że warstwa n działa pod warunkiem, że warstwa n-1 działa. Aby to zadziałało, musisz jakoś udowodnić, że warstwa 0 działa.
Podczas gdy teoretycznie możesz testować bazę danych, analizując i interpretując wygenerowany SQL, o wiele łatwiej i pewniej jest stworzyć testową bazę danych w locie i z nią porozmawiać.
Wniosek
Jaka jest pewność, jaką wzbudza jednostka testująca twoje repozytorium abstrakcyjne , Ethereal Object-Relational-Mapper , Generic Active Record , Theoretic Persistence layer, kiedy ostatecznie wygenerowany SQL zawiera błąd składniowy?
źródło
Potrzebujesz obu.
W twoim przykładzie, jeśli testujesz bazę danych w określonych warunkach, po uruchomieniu
findByKeyword
metody otrzymasz dane z powrotem, oczekujesz, że jest to dobry test integracji.W każdym innym kodzie używającym tej
findByKeyword
metody chcesz kontrolować, co jest wprowadzane do testu, abyś mógł zwrócić wartości null lub odpowiednie słowa do testu, czy cokolwiek wtedy kpisz z zależności bazy danych, abyś dokładnie wiedział, co będzie w teście odbierać (i tracisz narzut związany z połączeniem z bazą danych i upewnieniem się, że dane w niej są poprawne)źródło
Autor artykułu na blogu, do którego się odwołujesz, zajmuje się głównie potencjalną złożonością, która może wynikać ze zintegrowanych testów (chociaż jest napisana w bardzo opiniotwórczy i kategoryczny sposób). Jednak testy zintegrowane niekoniecznie są złe, a niektóre z nich są bardziej przydatne niż testy jednostkowe. To zależy od kontekstu twojej aplikacji i tego, co próbujesz przetestować.
Wiele dzisiejszych aplikacji po prostu nie działałoby, gdyby ich serwer bazy danych przestał działać. Pomyśl o tym przynajmniej w kontekście funkcji, którą próbujesz przetestować.
Z jednej strony, jeśli to, co próbujesz przetestować, nie zależy od bazy danych, lub możesz sprawić, że w ogóle nie będzie zależało, to napisz test w taki sposób, aby nawet nie próbował użyć baza danych (wystarczy podać fałszywe dane zgodnie z wymaganiami). Na przykład, jeśli próbujesz przetestować logikę uwierzytelniania podczas udostępniania strony internetowej (na przykład), prawdopodobnie dobrze jest całkowicie odłączyć ją od bazy danych (zakładając, że nie polegasz na bazie danych do uwierzytelniania, lub że możesz łatwo z niego kpić).
Z drugiej strony, jeśli jest to funkcja, która jest bezpośrednio zależna od bazy danych i która w ogóle nie działałaby w prawdziwym środowisku, gdyby baza danych była niedostępna, to kpiąc sobie z tego, co DB robi w kodzie klienta DB (tj. Z warstwy używającej tego DB) niekoniecznie ma sens.
Na przykład, jeśli wiesz, że twoja aplikacja będzie polegać na bazie danych (i być może na konkretnym systemie baz danych), kpowanie ze zachowania bazy danych ze względu na to często będzie stratą czasu. Silniki baz danych (zwłaszcza RDBMS) są złożonymi systemami. Kilka wierszy SQL może faktycznie wykonać wiele pracy, co byłoby trudne do symulacji (w rzeczywistości, jeśli zapytanie SQL ma kilka wierszy, istnieje szansa, że będziesz potrzebował znacznie więcej wierszy Java / PHP / C # / Python kod do wygenerowania tego samego wyniku wewnętrznie): powielanie logiki, którą już zaimplementowałeś w DB, nie ma sensu, a sprawdzenie tego kodu testowego stałoby się problemem samo w sobie.
Niekoniecznie traktowałbym to jako problem testu jednostkowego w porównaniu z testem zintegrowanym , ale raczej patrzę na zakres tego, co jest testowane. Pozostają ogólne problemy z testowaniem jednostkowym i integracyjnym: potrzebujesz rozsądnie realistycznego zestawu danych testowych i przypadków testowych, ale coś, co jest również wystarczająco małe, aby testy mogły zostać wykonane szybko.
Czas na zresetowanie bazy danych i ponowne wypełnienie danymi testowymi jest aspektem do rozważenia; ogólnie oceniasz to w stosunku do czasu, jaki zajmuje napisanie tego fałszywego kodu (który ostatecznie będziesz musiał zachować).
Inną kwestią do rozważenia jest stopień zależności aplikacji od bazy danych.
źródło
Masz rację, że taki test jednostkowy jest niekompletny. Niekompletność polega na wyśmiewaniu interfejsu bazy danych. Oczekiwania lub twierdzenia naiwnego kpiny są niepełne.
Aby go ukończyć, musisz poświęcić wystarczająco dużo czasu i zasobów, aby napisać lub zintegrować silnik reguł SQL, który zagwarantuje, że instrukcja SQL emitowana przez badany podmiot spowoduje oczekiwane operacje.
Jednak często zapomnianą i nieco kosztowną alternatywą / towarzyszem kpin jest „wirtualizacja” .
Czy potrafisz rozpakować tymczasową instancję DB w pamięci, ale „prawdziwą”, do testowania pojedynczej funkcji? tak ? tam masz lepszy test, ten, który sprawdza rzeczywiste dane zapisane i odzyskane.
Można powiedzieć, że zamieniłeś test jednostkowy w test integracyjny. Istnieją różne poglądy na temat tego, gdzie należy wyznaczyć linię do klasyfikowania między testami jednostkowymi a testami integracji. IMHO, „jednostka” jest dowolną definicją i powinna pasować do twoich potrzeb.
źródło
Unit Tests
iIntegration Tests
są do siebie prostopadłe . Oferują one inne spojrzenie na budowaną aplikację. Zwykle chcesz obu . Ale moment w czasie jest inny, kiedy chcesz, jakiego rodzaju testy.Najczęściej chcesz
Unit Tests
. Testy jednostkowe koncentrują się na niewielkiej części testowanego kodu -unit
czytelnikowi pozostaje dokładnie to, co nazywa się a . Ale cel jest prosty: uzyskiwanie szybkiej informacji zwrotnej o tym, kiedy i gdzie pękł kod . Powiedział, że powinno być jasne, że wzywa do rzeczywistej DB jest nono .Z drugiej strony istnieją rzeczy, które mogą być testowane jednostkowo tylko w trudnych warunkach bez bazy danych. Być może w twoim kodzie jest warunek wyścigu, a wywołanie DB powoduje naruszenie,
unique constraint
które może zostać rzucone tylko wtedy, gdy faktycznie używasz swojego systemu. Ale tego rodzaju testy są drogie , których nie możesz (i nie chcesz) uruchamiać tak często, jakunit tests
.źródło
W świecie .Net mam zwyczaj tworzenia projektu testowego i tworzenia testów jako metody kodowania / debugowania / testowania w obie strony minus interfejs użytkownika. To dla mnie skuteczny sposób na rozwój. Nie byłem tak zainteresowany przeprowadzaniem wszystkich testów dla każdej kompilacji (ponieważ spowalnia to moje prace rozwojowe), ale rozumiem przydatność tego dla większego zespołu. Niemniej jednak można wprowadzić regułę, zgodnie z którą przed zatwierdzeniem kodu wszystkie testy powinny zostać uruchomione i zdane (jeśli uruchomienie testów potrwa dłużej, ponieważ baza danych jest faktycznie atakowana).
Wyśmiewanie warstwy dostępu do danych (DAO) i nie trafianie do bazy danych, nie tylko nie pozwala mi kodować w sposób, w jaki lubię i przyzwyczaiłem się, ale brakuje dużej części rzeczywistej bazy kodu. Jeśli tak naprawdę nie testujesz warstwy dostępu do danych i bazy danych i po prostu udajesz, a następnie spędzasz dużo czasu na wyśmiewaniu się, nie rozumiem przydatności tego podejścia do testowania mojego kodu. Testuję mały kawałek zamiast większego za pomocą jednego testu. Rozumiem, że moje podejście może być bardziej zgodne z testem integracyjnym, ale wydaje się, że test jednostkowy z próbą jest zbędną stratą czasu, jeśli faktycznie napisasz test integracyjny raz i pierwszy. To także dobry sposób na rozwijanie i debugowanie.
W rzeczywistości od jakiegoś czasu zdaję sobie sprawę z TDD i Behaviour Driven Design (BDD) i zastanawiam się, jak go użyć, ale trudno jest dodać testy jednostkowe z mocą wsteczną. Być może się mylę, ale napisanie testu, który obejmuje więcej kodów od końca do końca z dołączoną bazą danych, wydaje się być o wiele bardziej kompletnym testem o wyższym priorytecie, który obejmuje więcej kodów i jest bardziej wydajnym sposobem pisania testów.
W rzeczywistości uważam, że coś takiego jak Behaviour Driven Design (BDD), które próbuje przetestować od końca do końca w języku specyficznym dla domeny (DSL), powinno być dobrym rozwiązaniem. Mamy SpecFlow w .Net świecie, ale zaczął się jako open source z Cucumber.
https://cucumber.io/
Po prostu nie jestem pod wrażeniem prawdziwej użyteczności testu, który napisałem, wyśmiewając warstwę dostępu do danych i nie uderzając w bazę danych. Zwrócony obiekt nie trafił do bazy danych i nie został wypełniony danymi. Był to całkowicie pusty przedmiot, który musiałem kpić w nienaturalny sposób. Myślę, że to strata czasu.
Zgodnie z przepełnieniem stosu kpina jest stosowana, gdy rzeczywiste obiekty są niepraktyczne do włączenia do testu jednostkowego.
https://stackoverflow.com/questions/2665812/what-is-mocking
„Wyśmiewanie jest stosowane przede wszystkim w testach jednostkowych. Testowany obiekt może być zależny od innych (złożonych) obiektów. Aby wyizolować zachowanie obiektu, który chcesz przetestować, zastąp inne obiekty próbnymi symulacjami zachowania rzeczywistych obiektów. Jest to przydatne, jeśli rzeczywiste obiekty nie są praktyczne do włączenia do testu jednostkowego. ”
Moim argumentem jest to, że jeśli koduję coś od końca do końca (interfejs WWW do warstwy biznesowej do warstwy dostępu do danych do bazy danych, podróż w obie strony), zanim zarejestruję coś jako programista, przetestuję ten przepływ w obie strony. Jeśli wycinę interfejs użytkownika i debuguję i przetestuję ten przepływ, zaczynając od testu, testuję wszystko poza interfejsem użytkownika i zwracam dokładnie to, czego oczekuje interfejs użytkownika. Pozostało mi tylko wysłać interfejs użytkownika, czego chce.
Mam bardziej kompletny test, który jest częścią mojego naturalnego procesu rozwoju. Dla mnie powinien to być test o najwyższym priorytecie, który obejmuje testowanie rzeczywistej specyfikacji użytkownika w jak największym stopniu. Jeśli nigdy nie utworzę żadnych bardziej szczegółowych testów, przynajmniej mam ten jeden pełny test, który dowodzi, że moja pożądana funkcjonalność działa.
Współzałożyciel Stack Exchange nie jest przekonany o zaletach posiadania 100% pokrycia testem jednostkowym. Ja też nie jestem. Zrobiłbym bardziej kompletny „test integracyjny”, który uderzył w bazę danych, utrzymując kilka próbnych baz danych każdego dnia.
https://www.joelonsoftware.com/2009/01/31/from-podcast-38/
źródło
Zależności zewnętrzne powinny być wyśmiewane, ponieważ nie można ich kontrolować (mogą przejść przez fazę testów integracyjnych, ale zawiodą w produkcji). Dyski mogą ulec awarii, połączenia z bazą danych mogą się nie powieść z wielu powodów, mogą wystąpić problemy z siecią itp. Testy integracyjne nie dają żadnej dodatkowej pewności, ponieważ wszystkie one mogą wystąpić w czasie wykonywania.
Dzięki prawdziwym testom jednostkowym testujesz w granicach piaskownicy i powinno być jasne. Jeśli programista napisał zapytanie SQL, które nie powiodło się w QA / PROD, oznacza to, że nawet wcześniej go nie przetestował.
źródło