No zaraz. Testy jednostkowe polegają na testowaniu małych fragmentów. Dlatego chcę testować Add, żeby wiedzieć, że działa. Jeśli przetestowałbym MyClass.Operate i coś by było nie tak, to nie wiem, co jest nie tak. Czy nie działa Calculator, czy może JakisInnyObiekt, a może coś jeszcze innego.
Calculator to klasa przykładowa. Składnik konkretnego modułu. Więc czemu mam ją wywalić do innego modułu?
Są dwie możliwości:
-
Calculator
to odrębny byt, który nie musi być powiązany z MyClass
, bo operacja którą wykonuje jest uniwersalna i może być używana także przez inne klasy. (Np. dodawanie, zapis do pliku CSV, mnożenie macierzy). Wtedy taka klasa powinna być wydzielona do innego modułu i przetestowana oddzielnie.
-
Calculator
jest tak ściśle związany z MyClass
, i używany tylko przez nią. W tej sytuacji testy MyClass
pokryją też Calculator
.
Tu była ciekawa dyskusja na temat testowania, mockowania: Testy i mockowanie
Nie mam testów na MyClass.Operate. W zupełności wystarczają mi te dwa pozostałe.
No, ale jeśli MyClass
jest publiczna, a pozostałe klasy są wewnętrzne, to powinieneś jednak testować właśnie MyClass
.
Poza tym wszędzie czytam, że jeśli chodzi o pisanie testów, to DRY nie ma znaczenia. Test powinien być jak najprostszy, żeby zminimalizować możliwość wystąpienia błędu w teście. Więc jak to w końcu jest? Bo coraz częściej mam wrażenie, że każdy zupełnie inaczej postrzega testy i każdy ma do nich zupełnie inne podejście.
No jak to w przypadku każdego zagadnienia jest masa podejść i nieporozumień. Ogólnie są dwie szkoły: londyńska i chicagowska.
Ta pierwsza uczy testowania każdej klasy oddzielnie, mockowania wszystkich jej zależności i sprawdzania czy były wołane.
Ta druga każe skupiać się na tym, czy funkcja zwraca prawidłowy wynik dla określonego wejścia.
Ścisłe trzymanie się pierwszego podejścia sprawia, że w testach masz piekło mocków i masę kodu konfigurującego, ale nie to jest najgorsze. W razie jakiejkolwiek refaktoryzacji, np. przeniesienie kodu do nowej klasy masz masę testów do zmiany. Mimo, że wyniki pozostają takie same!
Dla mnie jest to bardzo niepragmatyczne, testy są po pierwsze po to, aby weryfikowały poprawność kodu, a po drugie po to, aby umożliwiały refaktoryzację. (Jeśli coś zepsujemy w jej trakcie, to testy nie przejdą. Jeśli przechodzą, to niczego nie zepsuliśmy.) Testy, w których testowana jest każda klasa samodzielnie, utrudniają refaktoryzację, po zmianie kodu biznesowego nie wiadomo czy go przypadkiem zepsuliśmy, czy to testy są teraz niepoprawne. W efekcie możemy naprawić nie to co trzeba i zostawić kod produkcyjny zepsuty.
Poza tym wszędzie czytam, że jeśli chodzi o pisanie testów, to DRY nie ma znaczenia.
Oczywiście, każdy przecież lubi po drobnych zmianach w kodzie produkcyjnym poprawiać kod w 30 różnych miejscach w testach. A z TDD to jeszcze lepiej współgra.
O ile dobrze rozumiem, to chodzi o DRY kodu testów, a nie logiki. A to już nie jest takie proste... bo o ile DRY jest oczywiście ważne, to jednak przesadne dążenie do DRY w kodzie testów może mieć dziwne konsekwencje. Np. kiedyś pisałem testy do buildera, więc potrzebowałem oczywiście zestawu spodziewanych obiektów do różnych przypadków testowych, zazwyczaj różniących się jedną-dwiema wartościami. Aby uniknąć kopiuj-wklej napisałem do nich kolejny builder... na szczęście w porę się opamiętałem. ;)