Powiedzmy, że mam funkcję (napisaną w Ruby, ale powinna być zrozumiała dla wszystkich):
def am_I_old_enough?(name = 'filip')
person = Person::API.new(name)
if person.male?
return person.age > 21
else
return person.age > 18
end
end
W testach jednostkowych stworzyłbym cztery testy, które obejmowałyby wszystkie scenariusze. Każdy będzie używał wyśmiewanego Person::API
obiektu metodami przerywanymi male?
i age
.
Teraz chodzi o pisanie testów integracyjnych. Zakładam, że Person :: API nie powinien już być wyśmiewany. Stworzyłbym więc dokładnie te same cztery przypadki testowe, ale bez kpienia z obiektu Person :: API. Czy to jest poprawne?
Jeśli tak, to po co w ogóle pisać testy jednostkowe, gdybym mógł po prostu napisać testy integracyjne, które dadzą mi więcej pewności siebie (ponieważ pracuję na prawdziwych obiektach, a nie skrótach i próbach)?
unit-testing
testing
ruby
integration-tests
Filip Bartuzi
źródło
źródło
Odpowiedzi:
Nie, testy integracyjne nie powinny tylko powielać zakresu testów jednostkowych. Oni mogą powielać pewne zasięg, ale nie o to chodzi.
Celem testu jednostkowego jest upewnienie się, że określony mały fragment funkcji działa dokładnie i całkowicie zgodnie z przeznaczeniem. Test jednostkowy
am_i_old_enough
pozwoliłby przetestować dane w różnym wieku, z pewnością te w pobliżu progu, być może wszystkie występujące w wieku ludzkim. Po napisaniu tego testu integralnośćam_i_old_enough
nigdy nie powinna już być kwestionowana.Celem testu integracyjnego jest sprawdzenie, czy cały system lub kombinacja znacznej liczby komponentów działa prawidłowo, gdy są używane razem . Klient nie dba o określoną funkcję użyteczności, którą napisałeś, zależy mu na tym, aby jego aplikacja internetowa była odpowiednio zabezpieczona przed dostępem nieletnich, ponieważ w przeciwnym razie organy nadzoru będą miały swoje osły.
Sprawdzanie wiek użytkownika jest jedna niewielka część tej funkcji, ale test integracja nie sprawdza, czy funkcja użyteczności wykorzystuje prawidłową wartość progową. Sprawdza, czy osoba dzwoniąca podejmuje właściwą decyzję na podstawie tego progu, czy funkcja użyteczności jest w ogóle wywoływana, czy spełnione są inne warunki dostępu itp.
Powodem, dla którego potrzebujemy obu typów testów, jest zasadniczo kombinatoryczna eksplozja możliwych scenariuszy dla ścieżki przez bazę kodu, którą może wykonać wykonanie. Jeśli funkcja narzędziowa ma około 100 możliwych danych wejściowych, a istnieją setki funkcji narzędziowych, to sprawdzenie, czy właściwa rzecz dzieje się we wszystkich przypadkach, wymagałoby wielu, wielu milionów przypadków testowych. Po prostu sprawdzając wszystkie przypadki w bardzo małych zakresach, a następnie sprawdzając typowe, istotne lub prawdopodobne kombinacje tych zakresów, przy założeniu, że te małe zakresy są już poprawne, jak wykazano w testach jednostkowych , możemy uzyskać dość pewną ocenę, że system działa co powinno, bez utonięcia w alternatywnych scenariuszach do przetestowania.
źródło
The customer doesn't care about a particular utility function you wrote, they care that their web app is properly secured against access by minors
-> To bardzo sprytne nastawienie, dzięki! Problem polega na tym, że projektujesz dla siebie. Trudno jest rozdzielić sposób myślenia między byciem programistą a byciem menedżerem produktu w tym samym momencieKrótka odpowiedź brzmi „nie”. Bardziej interesujące jest to, dlaczego / jak taka sytuacja może się pojawić.
Myślę, że powstaje zamieszanie, ponieważ próbujesz przestrzegać ścisłych praktyk testowych (testy jednostkowe vs. testy integracyjne, kpiny itp.) W poszukiwaniu kodu, który nie wydaje się przestrzegać ścisłych praktyk.
Nie oznacza to, że kod jest „zły” lub że określone praktyki są lepsze niż inne. Po prostu, niektóre założenia przyjęte przez praktyki testowania mogą nie mieć zastosowania w tej sytuacji, i może pomóc zastosować podobny poziom „rygorystyczności” w praktykach kodowania i praktykach testowania; lub przynajmniej, aby uznać, że mogą być niezrównoważone, co spowoduje, że niektóre aspekty nie będą miały zastosowania lub będą zbędne.
Najbardziej oczywistym powodem jest to, że twoja funkcja wykonuje dwa różne zadania:
Person
według ich nazwy. Wymaga to testów integracyjnych, aby upewnić się, że może znaleźćPerson
obiekty, które są prawdopodobnie tworzone / przechowywane gdzie indziej.Person
jest wystarczająco stary, na podstawie ich płci. Wymaga to testów jednostkowych, aby upewnić się, że obliczenia przebiegają zgodnie z oczekiwaniami.Dzięki pogrupowaniu tych zadań w jeden blok kodu nie można uruchomić jednego bez drugiego. Gdy chcesz przetestować obliczenia za pomocą jednostki, musisz sprawdzić
Person
(z prawdziwej bazy danych lub z kodu pośredniczącego / próbnego). Jeśli chcesz przetestować, czy wyszukiwanie integruje się z resztą systemu, musisz również wykonać obliczenia dotyczące wieku. Co powinniśmy zrobić z tymi obliczeniami? Czy powinniśmy to zignorować, czy sprawdzić? Wydaje się, że jest to dokładne położenie, które opisujesz w swoim pytaniu.Jeśli wyobrażamy sobie alternatywę, możemy mieć obliczenia na własną rękę:
Ponieważ jest to czysta kalkulacja, nie musimy przeprowadzać na niej testów integracyjnych.
Możemy też pokusić się o osobne napisanie zadania wyszukiwania:
Jednak w tym przypadku funkcjonalność jest tak bliska
Person::API.new
, że powinieneś użyć jej zamiast tego (jeśli domyślna nazwa jest konieczna, czy lepiej byłoby ją przechowywać gdzie indziej, na przykład atrybut klasy?).Pisząc testy integracyjne dla
Person::API.new
(lubperson_from_name
) wszystko, o co musisz się martwić, to to, czy odzyskasz oczekiwanePerson
; wszystkie obliczenia oparte na wieku są przeprowadzane gdzie indziej, więc testy integracyjne mogą je zignorować.źródło
Kolejną kwestią, którą chciałbym dodać do odpowiedzi Killian, jest to, że testy jednostkowe przebiegają bardzo szybko, więc możemy mieć ich tysiące. Test integracji zwykle trwa dłużej, ponieważ wywołuje usługi sieciowe, bazy danych lub inne zależności zewnętrzne, więc nie możemy uruchomić tych samych testów (1000) dla scenariuszy integracji, ponieważ zajęłyby one zbyt dużo czasu.
Ponadto testy jednostkowe zwykle uruchamiane są w czasie kompilacji (na komputerze kompilacji), a testy integracyjne są uruchamiane po wdrożeniu na środowisku / maszynie.
Zazwyczaj uruchamia się nasze 1000 testów jednostkowych dla każdej kompilacji, a następnie 100 lub więcej testów integracyjnych o wysokiej wartości po każdym wdrożeniu. Nie możemy zabrać każdej kompilacji do wdrożenia, ale jest to w porządku, ponieważ kompilacja, którą zastosujemy do wdrożenia, zostaną uruchomione testy integracyjne. Zazwyczaj chcemy ograniczyć te testy do uruchomienia w ciągu 10 lub 15 minut, ponieważ nie chcemy zbyt długo wstrzymywać wdrożenia.
Ponadto w ramach tygodniowego harmonogramu możemy przeprowadzać regresyjny zestaw testów integracyjnych, które obejmują więcej scenariuszy w weekend lub inne przestoje. Może to potrwać dłużej niż 15 minut, ponieważ omówionych zostanie więcej scenariuszy, ale zazwyczaj nikt nie pracuje nad Sat / Sun, więc możemy poświęcić więcej czasu na testy.
źródło