2012-01-12 19 views
10

W jaki sposób zarządza się fikcyjnymi danymi używanymi do testów? Przechowywać je w odpowiednich jednostkach? W osobnym projekcie testowym? Załaduj je Serializerem z zasobów zewnętrznych? Lub po prostu odtworzyć je w razie potrzeby?Strategie testów danych i testów jednostkowych w modułowym stosie aplikacji

Mamy stos aplikacji z kilkoma modułami, w zależności od innego, z każdym elementem zawierającym. Każdy moduł ma własne testy i wymaga użycia danych fikcyjnych.

Teraz moduł, który ma wiele zależności, będzie potrzebować wielu fałszywych danych z innych modułów. Jednak nie publikują swoich fałszywych obiektów, ponieważ są częścią zasobów testowych, więc wszystkie moduły muszą ustawiać wszystkie fałszywe obiekty, których potrzebują ponownie.

także: większość pól w naszych jednostkach nie są pustych więc nawet uruchomione transakcje przeciwko warstwie obiektu wymaga od nich zawiera jakąś wartość, przez większość czasu z dalszymi ograniczeniami jak wyjątkowość, długości, itp

Czy istnieje najlepsza praktyka wyjścia z tego lub wszystkie rozwiązania kompromisowe?


Więcej szczegółów

Nasz stos wygląda tak:

jeden moduł:

src/main/java --> gets jared (.../entities/*.java contains the entities) 
src/main/resources --> gets jared 
src/test/java --> contains dummy object setup, will NOT get jared 
src/test/resources --> not jared 

Używamy Maven obsłużyć zależności. Przykładem

moduł:

  • Moduł A ma jakieś fikcyjne przedmioty
  • Moduł B potrzebuje własnych obiektów i tak samo jak moduł A

Wariant A)

Moduł testowy T może przechowywać wszystkie fałszywe obiekty i dostarczać je w zakresie testowym (aby załadowane zależności nie były jared) do wszystkich testów we wszystkich modułach. Czy to będzie działało? Znaczenie: Jeśli załadować T w A i uruchomienia instalacji w NIE będzie zawierać odniesienia wprowadzone przez T szczególnie nie B? Wtedy jednak A będzie wiedział o datamodelu B.

Opcja b)

Moduł A dostarcza manekina obiektów gdzieś w src/main/java../entities/dummy pozwalając B je zdobyć podczas nie wie o B „s dane fikcyjne

Opcja c)

Każdy moduł zawiera zasoby zewnętrzne, które są serializowanymi sztucznymi obiektami. Mogą być deserializowane przez środowisko testowe, które ich potrzebuje, ponieważ ma zależność od modułu, do którego należą. Będzie to wymagało jednak, aby każdy moduł tworzył i serializował swoje fałszywe obiekty i jak by to zrobić? Jeśli z innym testem jednostkowym wprowadza zależności między testami jednostkowymi, które nigdy nie powinny się zdarzyć lub ze skryptem, będzie trudny do debugowania i nie będzie elastyczny.

Wariant d)

Użyj mock ramy i przypisać wymagane pola ręcznie dla każdego testu, ile potrzeba. Problem polega na tym, że większość pól w naszych jednostkach nie jest null, a zatem będzie wymagać wywołania setterów lub konstruktorów, co spowoduje, że znowu będziemy na początku.

Co nie chcemy

nie chcemy, aby skonfigurować statyczny z bazy danych statycznych jak struktura wymagane obiekty będzie ciągle zmieniać. Dużo teraz, trochę później. Tak więc chcemy, aby hibernacja ustawiała wszystkie tabele i kolumny i wypełniała je danymi w czasie testowania jednostkowego. Również statyczna baza danych wprowadziłaby wiele potencjalnych błędów i zależności między testami.


Czy moje myśli zmierzają we właściwym kierunku? Jaka jest najlepsza praktyka radzenia sobie z testami wymagającymi dużej ilości danych? Będziemy dysponować kilkoma współzależnymi modułami, które będą wymagać obiektów wypełnionych pewnymi danymi z kilku innych modułów.


EDIT

Niektóre więcej informacji o tym, jak robimy to teraz w odpowiedzi na drugą odpowiedź:

więc dla uproszczenia, że ​​mamy trzy moduły: Person, Product, Order. Person będzie testować kilka metod, za pomocą menedżera MockPerson obiektu:

(w osobę/src/test/java :)

public class MockPerson { 

    public Person mockPerson(parameters...) { 
     return mockedPerson; 
    } 
} 

public class TestPerson() { 
    @Inject 
    private MockPerson mockPerson; 
    public testCreate() { 
     Person person = mockPerson.mockPerson(...); 
     // Asserts... 
    } 
} 

Klasa MockPerson nie zostaną zapakowane.

To samo dotyczy testy produktu:

(w produktu/src/test/java :)

public class MockProduct() { ... } 
public class TestProduct { 
    @Inject 
    private MockProduct mockProduct; 
    // ... 
} 

MockProduct jest potrzebne, ale nie zostaną zapakowane.

Teraz Testy Zamówienie będzie wymagać MockPerson i MockProduct, więc teraz mamy obecnie trzeba tworzyć zarówno jak MockOrder przetestować Order.

(w zlecenia/src/test/java :)

Są duplikaty i będą musiały być zmieniane za każdym razem Person lub Product Zmiany

public class MockProduct() { ... } 
public class MockPerson() { ... } 

Jest to jedyna klasa powinno być tutaj:

public class MockOrder() { ... } 

public class TestOrder() { 
    @Inject 
    private order.MockPerson mockPerson; 
    @Inject 
    private order.MockProduct mockProduct; 
    @Inject 
    private order.MockOrder mockOrder; 
    public testCreate() { 

     Order order = mockOrder.mockOrder(mockPerson.mockPerson(), mockProduct.mockProduct()); 
     // Asserts... 
    } 
} 

The probl em jest, że teraz musimy zaktualizować person.MockPerson i order.MockPerson, gdy zmienimy Person.

Czy nie lepiej jest opublikować Mocks przy użyciu słoika, aby każdy inny test, który ma taką zależność, mógł po prostu wywołać Mock.mock i uzyskać ładnie skonfigurowany obiekt? A może to ciemna strona - prosta droga?

Odpowiedz

3

To może ale nie musi mieć zastosowania - jestem ciekawy, jak wygląda przykład twoich obojętnych obiektów i kod instalacyjny. (Aby lepiej zrozumieć, czy ma to zastosowanie do twojej sytuacji.) Ale to, co zrobiłem w przeszłości, wcale nie wprowadza tego rodzaju kodu do testów. Jak opisujesz, ciężko jest produkować, debugować, a zwłaszcza pakować i utrzymywać.

Co mam usaully zrobić (i AFAIKT w Javie to najlepszych praktyk) to spróbuj użyć wzorzec testowy danych Builder, jak opisana przez Nat Pryce w jego Test Data Builders postu.

Jeśli uważasz, że to jest dość istotne, sprawdzić te out:

+0

Hej, cwash! Dziękuję za wskaźnik fabryczny. Pamiętam, że korzystałem z tego w samouczku rails;) To może być rozwiązanie, muszę to sprawdzić dalej. – Pete

+0

@Pete - fajnie, czy możesz zostawić notatkę/aktualizację, informując nas, co postanowiłeś? – cwash

+0

Wygląda więc na to, że możemy go użyć jako centralnego importera danych. W końcu jednak jest to tylko opcja a), co oznacza, że ​​jakiś projekt zewnętrzny będzie zawierał wszystkie wymagane generatory danych. Wciąż zastanawiasz się, czy to najlepsza droga. Mając nadzieję na więcej opinii na ten temat ... – Pete

1

Zastanawiam się, czy nie można rozwiązać problemu, zmieniając podejście testowe.

Jednostka Testowanie modułu, który zależy od innych modułów, a przez to na danych testowych innych modułów nie jest rzeczywistym testem jednostkowym!

Co zrobić, jeśli wstrzyknie się próbę dla wszystkich zależności testowanego modułu, aby można było przetestować go w pełnej izolacji. W takim przypadku nie trzeba konfigurować kompletnego środowiska, w którym każdy moduł zależny ma potrzebne dane, konfiguruje się tylko dane dla modułu, które faktycznie testuje.

Jeśli wyobrażasz sobie piramidę, to podstawą będą twoje testy jednostkowe, powyżej masz testy funkcjonalne i na górze masz kilka testów scenariuszy (lub jak Google nazywa je, małe, średnie i duże testy).

Będziesz miał ogromną liczbę testów jednostkowych, które mogą przetestować każdą ścieżkę kodu, ponieważ wyśmiewane zależności są całkowicie konfigurowalne. Wtedy możesz zaufać poszczególnym częściom i jedyne, co zrobią testy funkcjonalne i scenariuszowe, to sprawdzenie, czy każdy moduł jest prawidłowo podłączony do innych modułów.

Oznacza to, że dane testu modułu nie są udostępniane we wszystkich testach, ale tylko w przypadku kilku zgrupowanych razem.

Wzorzec Buildera wspomniany przez cwash z pewnością pomoże w testach funkcjonalnych. Korzystamy z .NET Builder, który jest skonfigurowany do budowania kompletnego drzewa obiektów i generowania wartości domyślnych dla każdej właściwości, więc kiedy zapisujemy to w bazie danych, wszystkie wymagane dane są obecne.

+0

Dziękujemy za odpowiedź. Czy możesz podać pseudo kod dla lepszego zrozumienia? Przynajmniej dla części testującej jednostkę. Nie testujemy jeszcze funkcjonalności i scenariuszy. O ile cię rozumiem, robimy właśnie to, co mówisz teraz. Każdy moduł ma klasy Mock, które zapewniają konfigurowalne dane i są wstrzykiwane do zestawów testowych. Każdy test następnie wywołuje fałszywy obiekt w celu utworzenia wymaganych danych. Przekażę trochę kodu źródłowego i więcej informacji, aby szczegółowo objaśnić nasze podejście i obawy, abyś mógł sprawdzić, czy o tym właśnie mówisz. – Pete

+0

@Pete Nie jestem programistą java, więc czy mógłbyś wyjaśnić, co masz na myśli: "Problem polega na tym, że teraz musimy zaktualizować person.MockPerson i order.MockPerson, gdy tylko osoba zostanie zmieniona." Co musisz zmienić? Czy nie wyśmiewasz tylko właściwości i metod, które są ważne? –

+0

Może moje pytanie jest zbyt długie, więc trudno jest znaleźć potrzebne szczegóły. Przepraszam ... Opisałem, jak musimy kpić z wszystkich pól, ponieważ większość z nich to 'nullable = false'. Więc jeśli dodaję nową kolumnę do jednostki "Osoba", będę musiał dodać szyderstwa do wszystkich wystąpień MockPerson w każdym module. – Pete

3

Cóż, uważnie przeczytałem wszystkie dotychczasowe oceny i jest to bardzo dobre pytanie. Widzę następujące podejścia do problemu:

  1. Konfiguracja (statyczna) baza danych testowych;
  2. Każdy test ma własne dane konfiguracyjne, które tworzą (dynamiczne) dane testowe przed uruchomieniem testów jednostkowych;
  3. Użyj obiektu obojętnego lub próbnego. Wszystkie moduły znają wszystkie fałszywe obiekty, w ten sposób nie ma duplikatów;
  4. Zmniejszenie zakresu testu urządzenia;

Pierwsza opcja jest dość prosta i ma wiele wad, ktoś musi ją odtworzyć raz na jakiś czas, gdy testy jednostkowe "zepsuć", jeśli są zmiany w module danych, ktoś musi wprowadzić odpowiednie zmiany w danych testowych, dużo nakładów na konserwację. Nie znaczy to, że generowanie tych danych z pierwszej ręki może być trudne. Zobacz drugą opcję aslo.

Po drugie, piszesz swój kod testowy, który przed testowaniem wywołuje niektóre z "podstawowych" metod biznesowych, które tworzą twoją encję. Idealnie, kod testowy powinien być niezależny od kodu produkcyjnego, ale w takim przypadku otrzymasz podwójny kod, który powinieneś obsługiwać dwa razy. Czasami dobrze jest podzielić swoją metodę biznesową produkcji, aby mieć punkt wejścia do testu jednostki (takie metody są prywatne i używam programu Reflection do ich wywoływania, potrzebna jest też uwaga dotycząca metody, refaktoryzacja jest teraz nieco trudna) . Główną wadą jest to, że jeśli musisz zmienić swoje "podstawowe" metody biznesowe, nagle wpływa to na twój test jednostki i nie możesz go przetestować. Dlatego programiści powinni być tego świadomi i nie czynić częściowych zobowiązań wobec "podstawowych" metod biznesowych, chyba że działają. Ponadto, przy każdej zmianie w tym obszarze, powinieneś pamiętać "w jaki sposób wpłynie to na mój test jednostki". Czasami niemożliwe jest również dynamiczne odtworzenie wszystkich wymaganych danych (zwykle z powodu interfejsu API innej firmy, na przykład wywołujesz inną aplikację z własną bazą danych, z której musisz korzystać z niektórych kluczy. powiązane dane) jest tworzony ręcznie za pośrednictwem aplikacji innej firmy.W takim przypadku dane te i tylko te dane powinny być tworzone statycznie.Na przykład utworzone klucze 10000 począwszy od 300000.

Trzecia opcja powinna być dobra Opcje a) i d) brzmi dla mnie całkiem nieźle. W przypadku obiektu obojętnego można użyć makiety lub nie można jej użyć. Mock Framework jest tutaj tylko po to, aby ci pomóc. Nie widzę problemu, że cała twoja jednostka zna wszystkie twoje istoty.

Czwarta opcja oznacza, że ​​na nowo definiujesz, co oznacza "jednostka" w teście jednostki.Kiedy masz kilka modułów ze współzależnością, to może być trudno przetestować każdy moduł w izolacji. Podejście to mówi, że to, co pierwotnie testowaliśmy, to test integracji , a nie jednostka testowa. Tak więc dzielimy nasze metody, wyodrębniamy małe "jednostki prac", które odbierają wszystkie jego współzależności do innych modułów jako parametry. Parametry te można (miejmy nadzieję) łatwo kpić. Główną wadą tego podejścia jest to, że nie testuje się całego kodu, ale jedynie "ogniskowanie". Musisz wykonać test integracji osobno (zwykle przez zespół ds. Kontroli jakości).

+0

Hej! Dzięki za wejście. Szczególnie 4 opcja była interesująca. Masz rację, to, co robimy, nie jest testowaniem jednostkowym, ale testowaniem integracyjnym, ponieważ wywołujemy funkcje menedżera z niższych modułów, aby tworzyć obiekty potrzebne w teście jednostkowym w wyższym module, a więc domyślnie testujemy również niektóre funkcje niższego modułu. Rozumiem, że to sprawia, że ​​testy nie są całkowicie niezależne. Myślę, że pomyśleliśmy: no cóż, to tylko zwiększy gęstość testów dla wszystkich modułów. Myślę, że powinienem przeczytać na temat przydatności testów jednostkowych w porównaniu z testami integracji ... – Pete

+1

@ Proszę, właśnie o to chodzi. Musisz upewnić się, że możesz przetestować swoje funkcje w izolacji. Wyśmiewa każdą zależność i używa Buildera do generowania danych testowych. Upewnij się także, że masz na uwadze Prawo Demeter (nie przekazując całego Klienta, gdy potrzebujesz tylko adresu), ponieważ spowoduje to, że Twój kod będzie znacznie łatwiejszy do sprawdzenia. –

Powiązane problemy