Większość dostępnych tu samouczków / przykładów testowania jednostek zazwyczaj obejmuje zdefiniowanie danych do przetestowania dla każdego testu. Myślę, że jest to część teorii „wszystko powinno być testowane w izolacji”.
Jednak odkryłem, że w przypadku aplikacji wielowarstwowych z dużą ilością DI kod wymagany do skonfigurowania każdego testu jest bardzo długi. Zamiast tego zbudowałem wiele klas baz testowych, które mogę teraz odziedziczyć, i które mają wiele gotowych rusztowań testowych.
W ramach tego buduję również fałszywe zestawy danych, które reprezentują bazę danych działającej aplikacji, chociaż zwykle z jednym lub dwoma wierszami w każdej „tabeli”.
Czy przyjętą praktyką jest predefiniowanie, jeśli nie wszystkie, większości danych testowych we wszystkich testach jednostkowych?
Aktualizacja
Z poniższych komentarzy wynika, że robię więcej integracji niż testowania jednostkowego.
Mój obecny projekt to ASP.NET MVC, w którym najpierw używam Jednostki pracy nad Entity Framework Code i Moq do testowania. Wyśmiewałem UoW i repozytoria, ale używam prawdziwych klas logiki biznesowej i testuję działania kontrolera. Testy często sprawdzają, czy UoW zostało popełnione, np .:
[TestClass]
public class SetupControllerTests : SetupControllerTestBase {
[TestMethod]
public void UserInvite_ExistingUser_DoesntInsertNewUser() {
// Arrange
var model = new Mandy.App.Models.Setup.UserInvite() {
Email = userData.First().Email
};
// Act
setupController.UserInvite(model);
// Assert
mockUserSet.Verify(m => m.Add(It.IsAny<UserProfile>()), Times.Never);
mockUnitOfWork.Verify(m => m.Commit(), Times.Once);
}
}
SetupControllerTestBase
buduje fałszywy UoW i tworzy instancję userLogic
.
Wiele testów wymaga posiadania istniejącego użytkownika lub produktu w bazie danych, więc wstępnie wypełniłem to, co zwraca fałszywy UoW, w tym przykładzie userData
, który zawiera tylko IList<User>
jeden rekord użytkownika.
źródło
Odpowiedzi:
Ostatecznie chcesz napisać jak najmniej kodu, aby uzyskać jak najwięcej wyników. Posiadanie wielu tego samego kodu w wielu testach a) powoduje kodowanie i kopiowanie i wklejanie, a b) oznacza, że jeśli zmieni się podpis metody, możesz w końcu naprawić wiele zepsutych testów.
Używam podejścia polegającego na posiadaniu standardowych klas TestHelper, które zapewniają mi wiele typów danych, z których rutynowo korzystam, dzięki czemu mogę tworzyć zestawy standardowych klas encji lub klas DTO , aby moje testy mogły odpytywać i dokładnie wiedzieć, co otrzymam za każdym razem. Więc mogę zadzwonić,
TestHelper.GetFooRange( 0, 100 )
aby uzyskać zakres 100 obiektów Foo ze wszystkimi ustawionymi ich zależnymi klasami / polami.Zwłaszcza tam, gdzie w systemie typu ORM skonfigurowane są złożone relacje, które muszą być obecne, aby wszystko działało poprawnie, ale niekoniecznie są istotne dla tego testu, który może zaoszczędzić dużo czasu.
W sytuacjach, w których testuję blisko poziomu danych, czasami tworzę testową wersję mojej klasy repozytorium, do której można uzyskiwać zapytania w podobny sposób (ponownie jest to środowisko typu ORM i nie byłoby to istotne w przypadku prawdziwa baza danych), ponieważ wykrywanie dokładnych odpowiedzi na zapytania jest bardzo pracochłonne i często zapewnia jedynie niewielkie korzyści.
Należy jednak zachować ostrożność podczas testów jednostkowych:
źródło
Cokolwiek czyni intencję twojego testu bardziej czytelnym.
Zgodnie z ogólną zasadą:
Jeśli dane są częścią testu (np. Nie powinny wypisywać wierszy ze stanem 7), to zakoduj je w teście, aby było jasne, co autor zamierzał zrobić.
Jeśli dane są tylko wypełniacz, aby upewnić się, że ma coś do pracy (np. Nie powinno znaku rejestrze jako kompletne, jeżeli usługa przetwarzania rzuca wyjątek), a następnie za wszelką cenę mieć metodę BuildDummyData lub klasę testową, która utrzymuje nieistotnych danych z testu .
Ale zauważ, że staram się wymyślić dobry przykład tego drugiego. Jeśli masz ich wiele w urządzeniu do testów jednostkowych, prawdopodobnie masz inny problem do rozwiązania ... być może testowana metoda jest zbyt skomplikowana.
źródło
Różne metody testowania
Najpierw określ, co robisz: testy jednostkowe lub testy integracyjne . Liczba warstw nie ma znaczenia dla testów jednostkowych, ponieważ najprawdopodobniej testowana jest tylko jedna klasa. Reszta wyśmiewasz. W przypadku testów integracji nieuniknione jest testowanie wielu warstw. Jeśli masz dobre testy jednostkowe, sztuczka polega na tym, aby testy integracyjne nie były zbyt skomplikowane.
Jeśli twoje testy jednostkowe są dobre, nie musisz powtarzać testowania wszystkich szczegółów podczas testowania integracji.
Warunki, których używamy, są nieco zależne od platformy, ale można je znaleźć na prawie wszystkich platformach testowych / programistycznych:
Przykładowa aplikacja
W zależności od technologii, której używasz, nazwy mogą się różnić, ale wykorzystam to jako przykład:
Jeśli masz prostą aplikację CRUD z modelem Product, ProductsController i widokiem indeksu, który generuje tabelę HTML z produktami:
Efektem końcowym aplikacji jest wyświetlenie tabeli HTML z listą wszystkich aktywnych produktów.
Testów jednostkowych
Model
Model, który możesz dość łatwo przetestować. Istnieją na to różne metody; używamy urządzeń. Myślę, że to właśnie nazywacie „fałszywymi zestawami danych”. Dlatego przed każdym uruchomieniem testu tworzymy tabelę i wstawiamy oryginalne dane. Większość platform ma na to metody. Na przykład w klasie testowej metoda setUp () uruchamiana przed każdym testem.
Następnie uruchamiamy nasz test, na przykład: produkty testGetAllActive .
Testujemy więc bezpośrednio do testowej bazy danych. Nie kpimy z źródła danych; sprawiamy, że zawsze jest tak samo. Dzięki temu możemy na przykład przetestować nową wersję bazy danych i pojawią się wszelkie problemy z zapytaniami.
W prawdziwym świecie nie zawsze możesz podążać w 100% za pojedynczą odpowiedzialnością . Jeśli chcesz to zrobić jeszcze lepiej, możesz użyć źródła danych, które wyśmiewasz. Dla nas (używamy ORM), który wydaje się testowaniem już istniejącej technologii. Również testy stają się znacznie bardziej złożone i tak naprawdę nie testują zapytań. Trzymamy to w ten sposób.
Zaszyfrowane dane są osobno przechowywane w urządzeniach. To urządzenie jest jak plik SQL z instrukcją tworzenia tabeli i wstawkami do rekordów, których używamy. Utrzymujemy je małe, chyba że istnieje rzeczywista potrzeba testowania z dużą ilością zapisów.
Kontroler
Kontroler wymaga więcej pracy, ponieważ nie chcemy z nim testować modelu. Więc to, co robimy, to kpina z modelu. Oznacza to: Testujemy metodę: index (), która powinna zwrócić listę rekordów.
Wyśmiewamy więc metodę modelową getAllActive () i dodajemy do niej ustalone dane (na przykład dwa rekordy). Teraz testujemy dane, które kontroler wysyła do widoku i porównujemy, czy naprawdę odzyskamy te dwa rekordy.
Wystarczy. Staramy się dodawać jak najmniej funkcji do kontrolera, ponieważ utrudnia to testowanie. Ale oczywiście zawsze jest w nim trochę kodu. Na przykład testujemy wymagania takie jak: Pokaż te dwa rekordy tylko wtedy, gdy jesteś zalogowany.
Tak więc kontroler potrzebuje normalnie jednej próbki i niewielkiej ilości zakodowanych danych. Dla systemu logowania może być inny. W naszym teście mamy do tego metodę pomocniczą: setLoggedIn (). Ułatwia to testowanie przy użyciu logowania lub bez logowania.
Widoki
Testowanie widoków jest trudne. Najpierw wyodrębniamy logikę, która się powtarza. Umieszczamy to w Pomocnikach i testujemy te klasy w ścisły sposób. Oczekujemy zawsze takiej samej wydajności. Na przykład: generujHtmlTableFromArray ().
Następnie mamy pewne widoki specyficzne dla projektu. Nie testujemy ich. Testowanie jednostkowe nie jest tak naprawdę pożądane. Trzymamy je do testów integracyjnych. Ponieważ wyodrębniliśmy dużą część kodu do widoków, mamy tutaj mniejsze ryzyko.
Jeśli zaczniesz testować te, prawdopodobnie będziesz musiał zmieniać testy za każdym razem, gdy zmieniasz fragment HTML, który nie jest przydatny w większości projektów.
Testy integracyjne
W zależności od platformy możesz tutaj pracować z historiami użytkowników itp. Może to być strona internetowa Selenium lub inne porównywalne rozwiązania.
Ogólnie po prostu ładujemy bazę danych z urządzeniami i stwierdzamy, które dane powinny być dostępne. Do pełnego testowania integracji używamy ogólnie bardzo globalnych wymagań. Tak: Ustaw produkt jako aktywny, a następnie sprawdź, czy produkt stanie się dostępny.
Nie testujemy wszystkiego ponownie, na przykład, czy dostępne są odpowiednie pola. Tutaj testujemy większe wymagania. Ponieważ nie chcemy powielać naszych testów ze sterownika lub widoku. Jeśli coś jest naprawdę kluczową / podstawową częścią aplikacji lub ze względów bezpieczeństwa (sprawdź, czy hasło NIE jest dostępne), dodajemy je, aby upewnić się, że jest poprawne.
Zaszyfrowane dane są przechowywane w urządzeniach.
źródło
Jeśli piszesz testy, które wymagają dużej ilości DI i okablowania, aż do korzystania z „prawdziwych” źródeł danych, prawdopodobnie opuściłeś obszar zwykłych testów jednostkowych i wszedłeś w domenę testów integracyjnych.
Myślę, że w przypadku testów integracyjnych wspólna logika konfiguracji danych nie jest złym pomysłem. Głównym celem takich testów jest udowodnienie, że wszystko jest poprawnie skonfigurowane. Jest to raczej niezależne od konkretnych danych przesyłanych przez twój system.
Z drugiej strony, w przypadku testów jednostkowych, zalecałbym, aby cel klasy testowej stanowił jedną „prawdziwą” klasę i wyśmiewał wszystko inne. Następnie powinieneś naprawdę zakodować dane testowe, aby upewnić się, że zakryłeś jak najwięcej ścieżek błędów specjalnych / poprzednich błędów.
Aby dodać do testów półokreślony / losowy element, lubię wprowadzać fabryki modeli losowych. W teście z wykorzystaniem instancji mojego modelu używam następnie tych fabryk, aby utworzyć prawidłowy, ale całkowicie losowy obiekt modelu, a następnie na stałe zakodować tylko te właściwości, które są interesujące dla danego testu. W ten sposób określasz wszystkie istotne dane bezpośrednio w teście, oszczędzając jednocześnie potrzeby określania wszystkich nieistotnych danych i (w pewnym stopniu) testowania, czy nie ma niezamierzonych zależności od innych pól modelu.
źródło
Wydaje mi się, że kodowanie większości danych do testów jest dość powszechne.
Rozważ prostą sytuację, w której określony zestaw danych powoduje wystąpienie błędu. Możesz specjalnie utworzyć test jednostkowy dla tych danych, aby wykonać poprawkę i upewnić się, że błąd nie wróci. Z czasem twoje testy będą miały zestaw danych, które obejmują wiele przypadków testowych.
Wstępnie zdefiniowane dane testowe pozwalają również na zbudowanie zestawu danych obejmującego szeroki i znany zakres sytuacji.
To powiedziawszy, myślę, że warto mieć trochę przypadkowych danych w swoich testach.
źródło