Jak zastosować TDD do funkcji odczytu / zapisu?

10

Wygląda na problem z kurczakiem i jajkami.

Możesz zmusić funkcję zapisu do zapisu w jakimś magazynie danych, ale nigdy nie wiesz, że zapisałeś ją poprawnie bez przetestowanej funkcji odczytu.

Możesz zrobić funkcję odczytu z magazynu danych, ale jak umieścić rzeczy w tym magazynie danych, aby można je było czytać bez przetestowanej funkcji zapisu?

EDYTOWAĆ:

Łączę się z bazą danych SQL i dokonuję transakcji w celu zapisywania i ładowania obiektów do użycia. Nie ma sensu testować funkcji dostępu udostępnianych przez DB, ale pakuję takie funkcje DB, aby serializować / deserializować obiekty. Chcę mieć pewność, że poprawnie piszę i czytam odpowiednie dane do iz bazy danych.

To nie jest jak dodawanie / usuwanie, jak wspomina @snowman. Chcę wiedzieć, że treść, którą napisałem, jest poprawna, ale wymaga dobrze przetestowanej funkcji odczytu. Kiedy czytam, chcę mieć pewność, że mój odczyt poprawnie utworzył obiekt równy temu, co zostało napisane; ale to wymaga dobrze przetestowanej funkcji zapisu.

użytkownik2738698
źródło
Czy piszesz własny magazyn danych, czy używasz już istniejącego? Jeśli używasz już istniejącego, załóż, że już działa. Jeśli piszesz własne, działa to w taki sam sposób, jak każde inne oprogramowanie korzystające z TDD.
Robert Harvey
1
Chociaż ściśle ze sobą powiązane, nie sądzę, że jest to duplikat tego konkretnego pytania. Dupe target mówi o dodawaniu / usuwaniu, ten jest do odczytu / zapisu. Różnica polega na tym, że zależność odczytu / zapisu prawdopodobnie zależy od zawartości obiektów odczytywanych / zapisywanych, podczas gdy prosty test dodawania / usuwania byłby prawdopodobnie znacznie prostszy: czy obiekt istnieje, czy nie.
2
Możesz ustawić bazę danych z danymi i bez funkcji zapisu, aby przetestować funkcję odczytu.
JeffO,

Odpowiedzi:

7

Zacznij od funkcji odczytu.

  • W konfiguracji testowej : utwórz bazę danych i dodaj dane testowe. za pośrednictwem skryptów migracji lub z kopii zapasowej. Ponieważ to nie jest twój kod, nie wymaga testu w TDD

  • W teście : zaimplementuj swoje repozytorium, wskaż na testową bazę danych i wywołaj metodę Read. Sprawdź, czy dane testowe zostały zwrócone.

Teraz masz w pełni przetestowaną funkcję odczytu, możesz przejść do funkcji zapisu, która może użyć istniejącego odczytu do zweryfikowania własnych wyników

Ewan
źródło
Myślę, że możesz utworzyć DB w pamięci, aby przyspieszyć, ale to może być zbyt skomplikowane. Dlaczego nie użyć próbnych zamiast testów jednostkowych?
BЈовић
1
Trochę Adwokata Diabła tutaj, ale jak sprawdzić, czy baza danych została poprawnie utworzona? Jak stwierdzono w OP, kurczak i jajko.
user949300,
1
@Ewan - Absolutnie zgadzam się, że nie powinieneś testować ich kodu DB. Ale skąd wiesz, że Twój kod konfiguracji DB nie zapomniał gdzieś WSTAWIĆ lub nie umieścił niewłaściwej wartości w kolumnie?
user949300,
1
z czystego podejścia TDD test JEST wymaganiem. więc logicznie nie mogę się mylić. obvs w prawdziwym świecie musisz to zobaczyć
Ewan
1
Quis custodiet ipsos custodes? Lub „Kto testuje testy?” :-) Zgadzam się z tobą, że w purystycznym świecie TDD byłby to strasznie żmudny i podatny na błędy (szczególnie jeśli byłaby to skomplikowana struktura wielu tabel z 8 JOINS) sposobem na zrobienie tego. Pozytywne.
user949300,
6

Często po prostu piszę, a potem czytam. np. (pseudokod)

Foo foo1 = setup some object to write
File tempfile = create a tempfile, possibly in memory 
writeFoo(foo1, tempfile) 
Foo foo2 = readFoo(tempfile) 
assertEquals(foo1, foo2); 
clean-up goes here

Dodano później

Oprócz tego, że to rozwiązanie jest „prgamatyczne” i „wystarczająco dobre”, można argumentować, że inne rozwiązania testują niewłaściwe działanie . Testowanie, czy łańcuchy lub instrukcje SQL pasują do siebie, nie jest strasznym pomysłem, sam to zrobiłem, ale testuję efekt uboczny i jest delikatny. Co się stanie, jeśli zmienisz wielkie litery, dodasz pole lub zaktualizujesz numer wersji w swoich danych? Co się stanie, jeśli sterownik SQL zmieni kolejność wywołań wydajności lub zaktualizowany serializator XML doda dodatkowe miejsce lub zmieni wersję schematu?

Teraz, jeśli musisz ściśle przestrzegać jakiejś oficjalnej specyfikacji, zgadzam się, że sprawdzenie drobnych szczegółów jest właściwe.

użytkownik949300
źródło
1
Ponieważ to 90% naprawdę gęsty pseudokod? Niepewny. Może wyróżnij tekst i spraw, aby kod był mniej hałaśliwy?
RubberDuck,
1
Tak @Ewan. Zelota skrzywiłby się na to, jednak pragmatyczny programista powiedziałby „wystarczająco dobry” i ruszył dalej.
RubberDuck,
1
Trochę czytam pytanie jako… „zakładając, że podążam za TDD jak gorliwy ...”
Ewan,
1
moja interpretacja OP i TDD jest taka, że ​​twój test powinien być napisany jako pierwszy i nie należy używać zarówno odczytu, jak i zapisu, chyba że testowany jest również w innym miejscu.
Ewan,
2
testujesz, „odczyt powinien zwracać to, co piszę”, ale wymagania są następujące: „odczyt powinien zwracać dane z bazy danych”, a „zapis powinien zapisywać dane do bazy danych”
Ewan,
4

Nie rób Nie testuj We / Wy urządzenia. To strata czasu.

Logika testu jednostkowego. Jeśli jest dużo logiki, którą chcesz przetestować w kodzie I / O, powinieneś przeformułować swój kod, aby oddzielić logikę tego, jak robisz I / O i co robisz I / O od faktycznej działalności wykonywania I / O (co jest prawie niemożliwe do przetestowania).

Aby nieco rozwinąć, jeśli chcesz przetestować serwer HTTP, powinieneś to zrobić poprzez dwa rodzaje testów: testy integracyjne i testy jednostkowe. Testy jednostkowe w ogóle nie powinny wchodzić w interakcje z I / O. Jest to powolne i wprowadza wiele błędów, które nie mają nic wspólnego z poprawnością kodu. Testy jednostkowe nie powinny zależeć od stanu twojej sieci!

Twój kod powinien się rozdzielić:

  • Logika określania, jakie informacje wysłać
  • Logika określania, które bajty wysłać, aby wysłać określony bit informacji (jak zakodować odpowiedź itp. W nieprzetworzonych bajtach), oraz
  • Mechanizm zapisywania tych bajtów w gnieździe.

Pierwsze dwa obejmują logikę i decyzje i wymagają testów jednostkowych. Ostatni nie wymaga podejmowania wielu, jeśli w ogóle, decyzji i można go cudownie przetestować za pomocą testów integracyjnych.

Właściwie jest to po prostu dobry projekt, ale jednym z powodów jest to, że ułatwia testowanie.


Oto kilka przykładów:

  • Jeśli piszesz kod, który pobiera dane z relacyjnej bazy danych, możesz przetestować jednostkowo, w jaki sposób mapujesz dane zwrócone z zapytań relacyjnych do modelu aplikacji.
  • Jeśli piszesz kod, który zapisuje dane do relacyjnej bazy danych, możesz testować jednostkowo, które fragmenty danych chcesz zapisać w bazie danych, bez testowania konkretnych zapytań SQL, których używasz. Na przykład możesz zachować dwie kopie stanu aplikacji w pamięci: kopię reprezentującą wygląd bazy danych i kopię roboczą. Jeśli chcesz zsynchronizować bazę danych, musisz je różnicować i zapisać różnice w bazie danych. Możesz dość łatwo testować jednostkowo ten kod różnicowy.
  • Jeśli piszesz kod, który odczytuje coś z pliku konfiguracyjnego, chcesz przetestować parser formatu pliku konfiguracyjnego, ale z łańcuchami z testowego pliku źródłowego zamiast ciągów z dysku.
Miles Rout
źródło
2

Nie wiem, czy to standardowa praktyka, czy nie, ale działa mi dobrze.

W moich implementacjach odczytu i zapisu poza bazą danych używam własnych metod toString()i fromString()metod jako szczegółów implementacji.

Można je łatwo przetestować w izolacji:

 assertEquals("<xml><car type='porsche'>....", new Car("porsche").toString());

Dla rzeczywistych metod odczytu i zapisu mam jeden test integracji, który fizycznie odczytuje i pisze w jednym teście

Nawiasem mówiąc: czy jest coś złego w jednym teście, w którym test odczytuje / zapisuje razem?

k3b
źródło
Może nie czuć się dobrze lub „czysto”, ale jest to pragmatyczne rozwiązanie.
RubberDuck,
Ja też podoba mi się pomysł wspólnego testowania odczytu i zapisu. Twój toString () to miły, pragmatyczny kompromis.
user949300,
1

Znane dane muszą być sformatowane w znany sposób. Najłatwiejszym sposobem na wdrożenie tego jest użycie stałego ciągu i porównanie wyniku, jak opisano w @ k3b.

Nie jesteś jednak ograniczony do stałych. Może istnieć wiele właściwości zapisanych danych, które można wyodrębnić przy użyciu innego rodzaju analizatora składni, takich jak wyrażenia regularne, a nawet sondy ad hoc szukające cech danych.

Jeśli chodzi o odczytywanie lub zapisywanie danych, przydatne może być posiadanie systemu plików w pamięci, który umożliwia uruchamianie testów bez możliwości ingerencji innych części systemu. Jeśli nie masz dostępu do dobrego systemu plików w pamięci, użyj tymczasowego drzewa katalogów.

BobDalgleish
źródło
1

Użyj iniekcji zależności i kpiny.

Nie chcesz testować sterownika SQL i nie chcesz sprawdzać, czy baza danych SQL jest w trybie online i czy została poprawnie skonfigurowana. Byłoby to częścią testu integracyjnego lub systemowego. Chcesz przetestować, czy Twój kod wysyła instrukcje SQL, które powinien wysłać, i czy interpretuje odpowiedzi tak, jak powinien.

Więc jeśli masz metodę / klasę, która ma coś zrobić z bazą danych, nie każ jej uzyskać połączenia z bazą danych. Zmień go tak, aby obiekt reprezentujący połączenie z bazą danych był do niego przekazywany.

W kodzie produkcyjnym przekaż rzeczywisty obiekt bazy danych.

W testach jednostkowych przekaż pozorowany obiekt, który zachowuje się tak, jakby rzeczywista baza danych nie kontaktowała się z serwerem bazy danych. Wystarczy, że sprawdzi, czy odbiera instrukcje SQL, które ma otrzymać, a następnie odpowiada na stałe.

W ten sposób możesz przetestować warstwę abstrakcji bazy danych, nawet bez potrzeby posiadania rzeczywistej bazy danych.

Philipp
źródło
Adwokat diabła: Skąd wiesz, które instrukcje SQL „mają otrzymać”? Co się stanie, jeśli sterownik DB zoptymalizuje kolejność na podstawie tego, co pojawia się w kodzie?
user949300,
@ user949300 Próbny obiekt bazy danych zwykle zastępuje sterownik bazy danych.
Philipp
podczas testowania repozytorium nie ma sensu wstrzykiwać fałszywego klienta bazy danych. Musisz przetestować, czy Twój kod działa SQL, który działa na bazie danych. w przeciwnym razie kończysz testowanie swojego makiety
Ewan
@Ewan Nie o to chodzi w testach jednostkowych. Test jednostkowy testuje jedną jednostkę kodu, odizolowaną od reszty świata. Nie testujesz interakcji między komponentami, takimi jak kod i baza danych. Do tego służą testy integracyjne.
Filip
tak. im mówię, że nie ma jednostki testowania repozytorium db. test integracji jest jedyną rzeczą, którą warto zrobić
Ewan,
0

Jeśli używasz obiektu relacyjnego mapowania obiektów, zwykle istnieje powiązana biblioteka, której można użyć do przetestowania poprawności działania mapowań poprzez utworzenie agregacji, utrwalenie jej i ponowne załadowanie z nowej sesji, a następnie weryfikację stanu względem oryginalny obiekt.

NHibernate oferuje testy specyfikacji trwałości . Można go skonfigurować do pracy ze sklepem w pamięci w celu szybkich testów jednostkowych.

Jeśli zastosujesz najprostszą wersję wzorców repozytorium i jednostki pracy i przetestujesz wszystkie swoje odwzorowania, możesz liczyć na to, że wszystko będzie działać dobrze.

pnschofield
źródło