Czasami przedmioty muszą być ściśle połączone. Na przykład CsvFile
klasa prawdopodobnie będzie musiała ściśle współpracować z CsvRecord
klasą (lub ICsvRecord
interfejsem).
Jednak z tego, czego nauczyłem się w przeszłości, jedną z głównych zasad rozwoju opartego na testach jest: „Nigdy nie testuj więcej niż jednej klasy na raz”. Oznacza to, że powinieneś używać ICsvRecord
próbnych lub kodów pośredniczących zamiast rzeczywistych instancji CsvRecord
.
Jednak po wypróbowaniu tego podejścia zauważyłem, że wyśmiewanie się z CsvRecord
klasy może być trochę owłosione. Co prowadzi mnie do jednego z dwóch wniosków:
- Trudno jest pisać testy jednostkowe! To zapach kodu! Refaktoryzacja!
- Wyśmiewanie każdej zależności jest po prostu nieuzasadnione.
Kiedy zastąpiłem moje kpiny rzeczywistymi CsvRecord
instancjami, sprawy potoczyły się znacznie sprawniej. Rozglądając się za myślami innych ludzi, natknąłem się na ten post na blogu , który wydaje się popierać nr 2 powyżej. W przypadku obiektów, które są naturalnie ściśle powiązane, nie powinniśmy się tak bardzo martwić o wyśmiewanie.
Czy jestem na dobrej drodze? Czy są jakieś wady powyższego założenia nr 2? Czy powinienem naprawdę myśleć o refaktoryzacji mojego projektu?
Odpowiedzi:
Jeśli naprawdę potrzebujesz koordynacji między tymi dwiema klasami, napisz
CsvCoordinator
klasę, która zawiera dwie klasy i przetestuj to.Kwestionuję jednak pojęcie, które
CsvRecord
nie może być niezależnie przetestowane.CsvRecord
jest w zasadzie klasą DTO , prawda? To tylko zbiór pól z kilkoma metodami pomocniczymi. ICsvRecord
może być używany także w innych kontekstachCsvFile
; możesz na przykład mieć kolekcję lub tablicęCsvRecord
s.CsvRecord
Najpierw przetestuj . Upewnij się, że pomyślnie przeszedł wszystkie testy. Następnie śmiało i używajCsvRecord
zCsvFile
klasą podczas testu. Użyj go jako wstępnie przetestowanego kodu pośredniczącego / próbnego; wypełnij je odpowiednimi danymi testowymi, przekaż jeCsvFile
i napisz na nim swoje przypadki testowe.źródło
CsvRecord
zepsuje, to oczywiście sięCsvData
nie powiedzie; ale to jest OK, ponieważ najpierw testujeszCsvRecord
, a jeśli to się nie powiedzie, twojeCsvFile
testy są bez znaczenia. Nadal można rozróżnić błędy wCsvRecord
i wCsvFile
.Powodem testowania jednej klasy na raz jest to, że nie chcesz, aby testy dla jednej klasy były zależne od zachowania drugiej klasy. Oznacza to, że jeśli twój test na klasę A ćwiczy dowolną funkcjonalność klasy B, powinieneś wyśmiewać klasę B, aby usunąć zależność od określonej funkcjonalności w klasie B.
CsvRecord
Wydaje mi się, że klasa taka służy głównie do przechowywania danych - nie jest to klasa o zbyt dużej funkcjonalności. Oznacza to, że może mieć konstruktorów, funkcje pobierające, ustawiające, ale nie ma metod z prawdziwą logiką. Oczywiście zgaduję tutaj - może napisałeś klasę o nazwie,CsvRecord
która wykonuje wiele skomplikowanych obliczeń.Ale jeśli
CsvRecord
nie ma własnej logiki, nie można nic zyskać, kpiąc z niej. To tak naprawdę tylko stara maksyma - „nie kpij z obiektów o wartości” .Rozważając więc, czy wyśmiewać daną klasę (na test innej klasy), powinieneś wziąć pod uwagę, ile jej własnej logiki ma ta klasa i ile tej logiki zostanie wykonane w trakcie testu.
źródło
Nie. 2 jest w porządku. Rzeczy mogą być i powinny być ściśle powiązane, jeśli ich koncepcje są ściśle powiązane. Powinno to być rzadkie i na ogół unikać, ale w podanym przykładzie ma to sens.
źródło
Klasy „sprzężone” są od siebie wzajemnie zależne. Nie powinno tak być w tym, co opisujesz - CsvRecord nie powinien tak naprawdę dbać o to, że plik CsvFile go zawiera, więc zależność przebiega tylko w jedną stronę. To dobrze i nie jest ciasne połączenie.
W końcu, jeśli klasa zawiera zmienną String, nie twierdziłbyś, że jest ściśle sprzężona z String, prawda?
Więc przetestuj CsvRecord pod kątem pożądanego zachowania.
Następnie użyj kpiny (Mockito jest świetny), aby sprawdzić, czy twoja jednostka wchodzi w interakcje z obiektami, od których zależy poprawnie. Naprawdę zachowanie, które chcesz przetestować - to, że CsvFile obsługuje CsvRcords w oczekiwany sposób. Wewnętrzne funkcjonowanie CvsRecord nie powinno mieć znaczenia - tak właśnie komunikuje się z nim CvsFile.
Wreszcie TDD to nie tylko testy jednostkowe. Z pewnością możesz (i powinieneś) zacząć od testów funkcjonalnych, które sprawdzają funkcjonalne zachowanie się większych komponentów - czyli historii użytkownika lub scenariusza. Twoje testy jednostkowe ustalają oczekiwania i weryfikują elementy, testy funkcjonalne robią to samo dla całości.
źródło
CsvFile
jest ściśle powiązanyCsvRecord
(ale nie na odwrót). PO pyta, czy jest to dobry pomysł do testuCsvFile
przez odłączenie go odCsvRecord
pośrednictwemICsvRecord
, a nie odwrotnie.CsvFile
zależy od tego, jak bardzo zależy od wewnętrznego działaniaCsvRecord
, to znaczy od liczby założeń pliku dotyczących rekordu. Interfejsy pomagają dokumentować i egzekwować takie założenia (a raczej brak innych założeń), ale ilość sprzężeń pozostaje taka sama, z tym wyjątkiem, że za pomocą interfejsu można podłączyć inną klasę rekordówCsvFile
. Wprowadzenie interfejsu tylko po to, by powiedzieć, że masz zmniejszone sprzężenie, jest głupie.Są tutaj naprawdę dwa pytania. Pierwszy dotyczy sytuacji, w których drwiny z obiektu są niewskazane. To niewątpliwie prawda, jak pokazują inne doskonałe odpowiedzi. Drugie pytanie dotyczy tego, czy konkretny przypadek jest jedną z takich sytuacji. W tej kwestii nie jestem przekonany.
Prawdopodobnie najczęstszym powodem, aby nie kpić z klasy, jest to, że jest to klasa wartości. Musisz jednak spojrzeć na przyczynę tej reguły. Nie dlatego, że wyśmiewana klasa będzie jakoś zła, ale dlatego, że będzie zasadniczo identyczna z oryginałem. Gdyby tak było, testowanie jednostek nie byłoby łatwiejsze przy użyciu oryginalnej klasy.
Może się zdarzyć, że Twój kod jest jednym z rzadkich wyjątków, w których refaktoryzacja nie pomogłaby, ale powinieneś go zadeklarować tylko wtedy, gdy staranne refaktoryzacje nie zadziałały. Nawet doświadczeni programiści mogą mieć problemy ze znalezieniem alternatyw dla własnego projektu. Jeśli nie możesz wymyślić żadnego możliwego sposobu na jego ulepszenie, poproś kogoś z doświadczeniem, aby dał mu drugie spojrzenie.
Wydaje się, że większość ludzi zakłada, że Twoja
CsvRecord
klasa jest wartością. Spróbuj to zrobić. Niech to będzie niezmienne, jeśli możesz. Jeśli masz dwa obiekty ze wskaźnikami do siebie, usuń jeden z nich i wymyśl, jak to zrobić. Poszukaj miejsc do podziału klas i funkcji. Najlepszym miejscem do podziału klasy nie zawsze jest dopasowanie do fizycznego układu pliku. Spróbuj odwrócić relacje rodzic / dziecko klas. Być może potrzebujesz osobnej klasy do odczytu i zapisu plików csv. Być może potrzebujesz osobnych klas do obsługi pliku I / O i interfejsu do wyższych warstw. Jest wiele rzeczy, które należy wypróbować, zanim uznamy, że jest to bezrefrakcyjne.źródło