Dlaczego potrzebuję testów jednostkowych do testowania metod repozytorium?

19

Muszę wcielić się w diabła, który jest zwolennikiem tego pytania, ponieważ nie mogę go dobrze bronić z powodu braku doświadczenia. Oto oferta, dostaję koncepcyjnie różnice między testowaniem jednostkowym a testowaniem integracyjnym. Skupiając się w szczególności na metodach trwałości i repozytorium, test jednostkowy użyłby makiety prawdopodobnie za pośrednictwem struktury takiej jak Moq, aby stwierdzić, że wyszukiwane zamówienie zostało zwrócone zgodnie z oczekiwaniami.

Załóżmy, że zbudowałem następujący test jednostkowy:

[TestMethod]
public void GetOrderByIDTest()
{
   //Uses Moq for dependency for getting order to make sure 
   //ID I set up in 'Arrange' is same one returned to test in 'Assertion'
}

Więc jeśli skonfiguruję OrderIdExpected = 5i mój próbny obiekt zwróci 5jako identyfikator, mój test przejdzie. Rozumiem. I jednostka przetestowany kod, aby upewnić się co moi preform kod zwraca oczekiwanego obiektu i identyfikator, a nie coś innego.

Otrzymam następujący argument:

„Dlaczego po prostu nie pominąć testów jednostkowych i nie wykonać testów integracyjnych? To ważne jest testowanie procedury składowanej bazy danych i kodu. Wydaje się, że to zbyt dużo pracy, aby mieć testy jednostkowe i testy integracyjne, kiedy ostatecznie chcę wiedzieć, czy baza danych wywołuje i kod działa. Wiem, że testy trwają dłużej, ale muszą być uruchamiane i testowane niezależnie, więc wydaje mi się, że nie ma sensu mieć obu. Po prostu przetestuj, co jest ważne. ”

Mógłbym go obronić za pomocą definicji z podręcznika: „Cóż, to test integracyjny i musimy osobno przetestować kod jako test jednostkowy i, yada, yada, yada ...” Jest to przypadek, w którym purystyczne wyjaśnienie praktyk a rzeczywistość przegrywa. Czasami się na to natrafiam i jeśli nie jestem w stanie obronić rozumowania stojącego za kodem testów jednostkowych, który ostatecznie opiera się na zewnętrznych zależnościach, to nie mogę tego uzasadnić.

Dziękujemy za pomoc w tym pytaniu, dziękuję!

atconway
źródło
2
powiadam, wyślij to prosto do testowania przez użytkownika ... i tak zmienią wymagania ...
Nathan Hayfield
+1 za sarkazm, by od czasu do czasu wszystko
świeciło
2
Prosta odpowiedź jest taka, że ​​każda metoda może mieć x liczbę przypadków krawędzi (powiedzmy, że chcesz przetestować pozytywne identyfikatory, identyfikatory 0 i negatywne identyfikatory). Jeśli chcesz przetestować niektóre funkcje korzystające z tej metody, która sama ma 3 przypadki brzegowe, musisz napisać 9 przypadków testowych, aby przetestować każdą kombinację przypadków brzegowych. Izolując je, wystarczy napisać 6. Ponadto testy dają bardziej szczegółowe wyobrażenie o tym, dlaczego coś się zepsuło. Być może Twoje repozytorium zwraca wartość NULL w przypadku niepowodzenia, a wyjątek NULL jest zgłaszany kilkaset wierszy w dół kodu.
Rob
Myślę, że jesteś zbyt surowy w definiowaniu testu „jednostkowego”. Co to jest „jednostka pracy” dla klasy repozytorium?
Caleb
I upewnij się, że zastanawiasz się nad tym: jeśli test jednostkowy Wyśmiewa wszystko, co próbujesz przetestować, co tak naprawdę testujesz?
Caleb

Odpowiedzi:

20

Testy jednostkowe i testy integracyjne mają różne cele.

Testy jednostkowe weryfikują funkcjonalność kodu ... czy otrzymujesz to, czego oczekujesz od metody po jej wywołaniu. Testy integracyjne sprawdzają, jak zachowuje się kod, gdy są połączone razem jako system. Nie należy oczekiwać, że testy jednostkowe oceniają zachowanie systemu, ani nie oczekuje się, że testy integracyjne zweryfikują określone wyniki konkretnej metody.

Testy jednostkowe, jeśli wykonane poprawnie, są łatwiejsze do skonfigurowania niż testy integracyjne. Jeśli polegasz wyłącznie na testach integracyjnych, Twoje testy będą:

  1. Ogólnie trudniej jest pisać
  2. Bądź bardziej kruchy, ze względu na wszystkie wymagane zależności i
  3. Oferuj mniejszy zasięg kodu.

Konkluzja: Użyj testu integracji, aby sprawdzić, czy połączenia między obiektami działają poprawnie, ale najpierw przetestuj jednostkę, aby zweryfikować wymagania funkcjonalne.


To powiedziawszy, możesz nie potrzebować testów jednostkowych dla niektórych metod repozytorium. Testy jednostkowe nie powinny być wykonywane na trywialnych metodach; jeśli wszystko, co robisz, to przekazanie żądania do kodu wygenerowanego przez ORM i zwrócenie wyniku, w większości przypadków nie musisz tego testować jednostkowo; test integracji jest odpowiedni.

Robert Harvey
źródło
Tak, dziękuję za szybką odpowiedź Doceniam to. Moją jedyną krytyką odpowiedzi jest to, że dotyczy ona mojego ostatniego akapitu. Mój sprzeciw nadal usłyszy tylko abstrakcyjne wyjaśnienia i definicje, a nie jakieś głębsze rozumowanie. Na przykład, czy mógłbyś podać uzasadnienie zastosowane do mojego przypadku użycia testowania procedury składowanej / bazy danych w odniesieniu do mojego kodu i dlaczego testy jednostkowe są nadal cenne w odniesieniu do tego konkretnego przypadku użycia?
atconway
1
Jeśli SP tylko zwraca wynik z bazy danych, test integracji może być odpowiedni. Jeśli ma w sobie nietrywialną logikę, wskazane są testy jednostkowe. Zobacz także stackoverflow.com/questions/1126614/... i msdn.microsoft.com/en-us/library/aa833169(v=vs.100).aspx (specyficzne dla SQL Server).
Robert Harvey
12

Jestem po stronie pragmatyków. Nie testuj swojego kodu dwa razy.

Testy integracyjne piszemy tylko dla naszych repozytoriów. Testy te zależą tylko od prostej konfiguracji testu, która działa na bazie danych w pamięci. Myślę, że zapewniają wszystko, co robi test jednostkowy i wiele więcej.

  1. Mogą zastępować testy jednostkowe podczas wykonywania TDD. Chociaż przed przystąpieniem do prawdziwego kodu jest jeszcze więcej do napisania kodu testowego, działa on bardzo dobrze z podejściem czerwonym / zielonym / refaktorem, gdy wszystko jest już na swoim miejscu.
  2. Testują prawdziwy kod repozytorium - kod zawarty w ciągach SQL lub komendach ORM. Ważniejsze jest sprawdzenie, czy zapytanie jest poprawne, niż sprawdzenie, czy rzeczywiście wysłano jakiś ciąg do instrukcji StatementExecutor.
  3. Są doskonałe jako testy regresji. Jeśli zawiodą, zawsze jest to spowodowane prawdziwym problemem, takim jak zmiana schematu, która nie została uwzględniona.
  4. Są one niezbędne przy zmianie schematu bazy danych. Możesz być pewien, że nie zmieniłeś schematu w sposób, który przerywa działanie aplikacji, dopóki test się nie powiedzie. (Test jednostkowy jest w tym przypadku bezużyteczny, ponieważ gdy procedura składowana już nie istnieje, test jednostkowy nadal przejdzie.)
jemiołucha
źródło
Naprawdę bardzo pragmatyczny. Doświadczyłem przypadku, że baza danych w pamięci faktycznie zachowuje się inaczej (nie pod względem wydajności) niż moja prawdziwa baza danych, z powodu nieco innej ORM. Zadbaj o to w testach integracyjnych.
Marcel
1
@Marcel, natknąłem się na to. Rozwiązałem go, czasami uruchamiając wszystkie moje testy na prawdziwej bazie danych.
Winston Ewert
4

Testy jednostkowe zapewniają poziom modułowości, którego nie są w stanie przeprowadzić testy integracyjne (zgodnie z projektem). Kiedy system zostanie ponownie skomponowany lub ponownie skomponowany, (i tak się stanie) testy jednostkowe mogą być często ponownie użyte, podczas gdy testy integracyjne muszą być często przepisywane. Testy integracyjne, które próbują wykonać coś, co powinno znaleźć się w teście jednostkowym, często robią za dużo, co utrudnia ich utrzymanie.

Dodatkowo, w tym testowanie jednostkowe ma następujące zalety:

  1. Testy jednostkowe pozwalają szybko zdekomponować nieudany test integracji (prawdopodobnie z powodu błędu regresji) i zidentyfikować przyczynę. Co więcej, przekaże to problem całemu zespołowi szybciej niż schematy lub inna dokumentacja.
  2. Testy jednostkowe mogą służyć jako przykłady i dokumentacja (rodzaj dokumentacji, która faktycznie się kompiluje ) wraz z twoim kodem. W przeciwieństwie do innych dokumentów, będziesz wiedział natychmiast, kiedy są nieaktualne.
  3. Testy jednostkowe mogą służyć jako podstawowe wskaźniki wydajności podczas próby rozłożenia większych problemów z wydajnością, podczas gdy testy integracyjne zwykle wymagają dużej ilości oprzyrządowania, aby znaleźć problem.

Możliwe jest podzielenie pracy (i wymuszenie OSUSZANIA) podczas korzystania zarówno z testów jednostkowych, jak i integracyjnych. Po prostu polegaj na testach jednostkowych dla małych jednostek funkcjonalności i nie powtarzaj żadnej logiki, która jest już w teście jednostkowym w teście integracji. To często prowadzi do mniejszej pracy (a tym samym mniejszej ilości ponownej pracy).

Zachary Yates
źródło
4

Testy są przydatne, gdy się psują: proaktywnie lub ponownie aktywnie.

Testy jednostkowe są proaktywne i mogą stanowić ciągłą weryfikację funkcjonalną, podczas gdy testy integracyjne są reaktywne na danych etapowych / fałszywych.

Jeśli rozważany system jest bliski / zależny od danych bardziej niż logiki obliczeniowej, testy integracji powinny zyskać na znaczeniu.

Na przykład, jeśli budujemy system ETL, najbardziej odpowiednie testy będą dotyczyły danych (etapowych, sfałszowanych lub na żywo). Ten sam system będzie miał kilka testów jednostkowych, ale tylko wokół sprawdzania poprawności, filtrowania itp.

Wzorzec repozytorium nie powinien mieć logiki obliczeniowej ani biznesowej. Jest bardzo blisko bazy danych. Ale repozytorium jest również zbliżone do logiki biznesowej, która z niego korzysta. Jest to więc kwestia zachowania równowagi.

Testy jednostkowe mogą testować JAK DZIAŁA repozytorium. Testy integracyjne mogą przetestować CO NAPRAWDĘ STAŁO się, gdy wywołano repozytorium.

Teraz „CO NAPRAWDĘ SIĘ STAŁO” może wydawać się bardzo kuszące. Ale może to być bardzo kosztowne w przypadku ciągłego i powtarzalnego działania. Obowiązuje tutaj klasyczna definicja testów kruchości.

Przez większość czasu po prostu uważam, że wystarczy napisać Test jednostkowy na obiekcie korzystającym z repozytorium. W ten sposób sprawdzamy, czy została wywołana odpowiednia metoda repozytorium i czy zwrócone są prawidłowe wyniki Mocked.

Otpidus
źródło
Podoba mi się to: „Teraz”, CO NAPRAWDĘ STAŁO SIĘ „może wydawać się bardzo kuszące. Ale może to być bardzo kosztowne, aby biegać konsekwentnie i wielokrotnie”.
atconway
2

Istnieją 3 osobne rzeczy, które wymagają przetestowania: procedura przechowywana, kod wywołujący procedurę przechowywaną (tj. Klasa repozytorium) i konsument. Zadaniem repozytorium jest wygenerowanie zapytania i konwersja zwróconego zestawu danych na obiekt domeny. Jest wystarczający kod do obsługi testów jednostkowych, który różni się od faktycznego wykonania zapytania i utworzenia zestawu danych.

Więc (jest to bardzo bardzo uproszczony przykład):

interface IOrderRepository
{
    Order GetOrderByID(Guid id);
}

class OrderRepository : IOrderRepository
{
    private readonly ISqlExecutor sqlExecutor;
    public OrderRepository(ISqlExecutor sqlExecutor)
    {
        this.sqlExecutor = sqlExecutor;
    }

    public Order GetOrderByID(Guid id)
    {
        var sql = "SELECT blah, blah FROM Order WHERE OrderId = @p0";
        var dataset = this.sqlExecutor.Execute(sql, p0 = id);
        var result = this.orderFromDataset(dataset);
        return result;
    }
}

Następnie podczas testowania OrderRepositoryprzekaż wyśmiewany ISqlExecutori sprawdź, czy testowany obiekt przekazuje poprawny kod SQL (to jest jego zadanie) i zwraca odpowiedni Orderobiekt, biorąc pod uwagę zbiór danych wynikowych (również wyśmiewany). Prawdopodobnie jedynym sposobem przetestowania konkretnej SqlExecutorklasy są testy integracyjne, co jest dość uczciwe, ale jest to klasa cienkiego opakowania i rzadko się to zmienia, więc wielka sprawa.

Nadal musisz przetestować również procedury składowane.

Scott Whitlock
źródło
0

Mogę sprowadzić to do bardzo prostej linii myślenia: faworyzuj testy jednostkowe, ponieważ pomaga to zlokalizować błędy. I rób to konsekwentnie, ponieważ niespójności powodują zamieszanie.

Dalsze powody wynikają z tych samych zasad, które kierują rozwojem OO, ponieważ dotyczą one również testów. Na przykład zasada pojedynczej odpowiedzialności.

Jeśli wydaje się, że niektóre testy jednostkowe nie są warte wykonania, być może jest to znak, że temat nie jest w rzeczywistości wart. A może jego funkcjonalność jest bardziej abstrakcyjna niż jej odpowiednik, a testy dla niego (a także jego kodu) można wyodrębnić na wyższy poziom.

Jak w przypadku wszystkiego, są wyjątki. A programowanie wciąż jest w pewnym sensie sztuką, dlatego wiele problemów jest wystarczająco różnych, aby uzasadnić ich ocenę pod kątem najlepszego podejścia.

CL22
źródło