Powinieneś rozbić klasę, o której mowa.
Każda klasa powinna wykonać proste zadanie. Jeśli zadanie jest zbyt skomplikowane do przetestowania, zadanie, które wykonuje klasa, jest zbyt duże.
Ignorując głupotę tego projektu:
class NewYork
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class Miami
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class MyProfit
{
MyProfit(NewYork new_york, Miami miami);
boolean bothProfitable();
}
AKTUALIZACJA
Problem z metodami stubowania w klasie polega na tym, że naruszasz enkapsulację. Twój test powinien sprawdzić, czy zewnętrzne zachowanie obiektu odpowiada specyfikacjom. Cokolwiek dzieje się wewnątrz obiektu, nie jest jego sprawą.
Fakt, że FullName używa FirstName i LastName jest szczegółem implementacji. Nic poza klasą nie powinno dbać o to, że to prawda. Wyśmiewając publiczne metody w celu przetestowania obiektu, przyjmujesz założenie, że ten obiekt został zaimplementowany.
W pewnym momencie w przyszłości założenie to może przestać być poprawne. Być może cała logika nazwy zostanie przeniesiona do obiektu Name, który Osoba po prostu wywołuje. Być może FullName będzie bezpośrednio uzyskiwać dostęp do zmiennych członków first_name i last_name zamiast wywoływać FirstName i LastName.
Drugie pytanie dotyczy tego, dlaczego czujesz taką potrzebę. W końcu Twoja klasa osobowa może zostać przetestowana w następujący sposób:
Person person = new Person("John", "Doe");
Test.AssertEquals(person.FullName(), "John Doe");
W tym przykładzie nie powinieneś odczuwać potrzeby usuwania niczego. Jeśli to zrobisz, jesteś bardzo szczęśliwy i dobrze ... przestań! Wyśmiewanie metod nie ma żadnej korzyści, ponieważ i tak masz kontrolę nad tym, co w nich jest.
Jedynym przypadkiem, w którym wydaje się sensowne, aby metody stosowane przez FullName były wyśmiewane, jest to, że FirstName () i LastName () były operacjami nietrywialnymi. Być może piszesz jeden z tych generatorów losowych nazw lub Imię i Nazwisko wyszukuje w bazie danych odpowiedź lub coś takiego. Ale jeśli tak się dzieje, sugeruje, że obiekt robi coś, co nie należy do klasy Person.
Innymi słowy, naśmiewanie się z metod polega na zabraniu obiektu i rozbiciu go na dwie części. Jeden kawałek jest wyśmiewany, podczas gdy drugi jest testowany. To, co robisz, to w zasadzie doraźne rozbicie obiektu. W takim przypadku wystarczy rozbić obiekt.
Jeśli twoja klasa jest prosta, nie powinieneś odczuwać potrzeby kpienia z niej podczas testu. Jeśli twoja klasa jest na tyle złożona, że czujesz potrzebę kpienia, powinieneś podzielić ją na prostsze części.
PONOWNIE AKTUALIZACJA
Z mojego punktu widzenia obiekt ma zachowanie zewnętrzne i wewnętrzne. Zachowanie zewnętrzne obejmuje zwracanie wartości wywołań do innych obiektów itp. Oczywiście wszystko w tej kategorii powinno zostać przetestowane. (inaczej co byś przetestował?) Ale zachowanie wewnętrzne nie powinno być tak naprawdę testowane.
Teraz testowane jest zachowanie wewnętrzne, ponieważ właśnie to powoduje zachowanie zewnętrzne. Ale nie piszę testów bezpośrednio na temat zachowania wewnętrznego, tylko pośrednio poprzez zachowanie zewnętrzne.
Jeśli chcę coś przetestować, uważam, że należy go przenieść, aby stał się zachowaniem zewnętrznym. Dlatego myślę, że jeśli chcesz coś wyśmiewać, powinieneś podzielić obiekt tak, aby rzecz, którą chcesz wyśmiewać, teraz zachowuje się na zewnątrz obiektów, o których mowa.
Ale jaka to różnica? Jeśli FirstName () i LastName () są członkami innego obiektu, czy to naprawdę zmienia problem FullName ()? Czy jeśli zdecydujemy, że konieczne jest kpiny z FirstName, a LastName naprawdę pomaga im znajdować się na innym obiekcie?
Myślę, że jeśli zastosujesz swoje kpiące podejście, wtedy utworzysz szew w obiekcie. Masz funkcje takie jak FirstName () i LastName (), które bezpośrednio komunikują się z zewnętrznym źródłem danych. Masz również FullName (), która nie ma. Ale ponieważ wszyscy należą do tej samej klasy, nie jest to oczywiste. Niektóre elementy nie powinny mieć bezpośredniego dostępu do źródła danych, a inne są. Twój kod będzie wyraźniejszy, jeśli tylko rozbijesz te dwie grupy.
EDYTOWAĆ
Cofnijmy się i zapytajmy: dlaczego kpimy z obiektów podczas testowania?
- Spraw, aby testy działały spójnie (unikaj dostępu do rzeczy, które zmieniają się z uruchomienia na uruchomienie)
- Unikaj dostępu do drogich zasobów (nie korzystaj z usług stron trzecich itp.)
- Uprość testowany system
- Ułatw testowanie wszystkich możliwych scenariuszy (np. Symulowanie awarii itp.)
- Unikaj polegania na szczegółach innych fragmentów kodu, aby zmiany w tych innych fragmentach kodu nie przerwały tego testu.
Myślę, że przyczyny 1-4 nie dotyczą tego scenariusza. Wyśmiewanie zewnętrznego źródła podczas testowania pełnej nazwy rozwiązuje wszystkie te powody wyśmiewania. Jedyną nieobsługiwaną częścią jest prostota, ale wydaje się, że obiekt jest wystarczająco prosty, co nie stanowi problemu.
Myślę, że twoja obawa jest powodem numer 5. Obawa polega na tym, że w pewnym momencie zmiana implementacji FirstName i LastName przerwie test. W przyszłości FirstName i LastName mogą uzyskać nazwy z innej lokalizacji lub źródła. Ale FullName prawdopodobnie zawsze będzie FirstName() + " " + LastName()
. Dlatego chcesz przetestować FullName, wyśmiewając FirstName i LastName.
To, co masz, to jakiś podzbiór obiektu osoby, który z większym prawdopodobieństwem zmieni się niż inne. Reszta obiektu używa tego podzbioru. Ten podzbiór obecnie pobiera dane przy użyciu jednego źródła, ale może później pobrać te dane w zupełnie inny sposób. Ale dla mnie wygląda na to, że ten podzbiór jest odrębnym przedmiotem próbującym się wydostać.
Wydaje mi się, że jeśli wyśmiewasz metodę obiektu, dzielisz obiekt. Ale robisz to w sposób doraźny. Twój kod nie wyjaśnia, że wewnątrz obiektu Person znajdują się dwa różne elementy. Po prostu podziel ten obiekt na rzeczywisty kod, aby było jasne odczytywanie kodu, co się dzieje. Wybierz rzeczywisty podział obiektu, który ma sens, i nie próbuj dzielić obiektu w inny sposób dla każdego testu.
Podejrzewam, że możesz sprzeciwić się podziałowi swojego przedmiotu, ale dlaczego?
EDYTOWAĆ
Myliłem się.
Powinieneś dzielić obiekty, a nie wprowadzać podziały ad-hoc, wyśmiewając poszczególne metody. Jednak nadmiernie skupiłem się na jednej metodzie podziału obiektów. Jednak OO zapewnia wiele metod podziału obiektu.
Co bym zaproponował:
class PersonBase
{
abstract sring FirstName();
abstract string LastName();
string FullName()
{
return FirstName() + " " + LastName();
}
}
class Person extends PersonBase
{
string FirstName();
string LastName();
}
class FakePerson extends PersonBase
{
void setFirstName(string);
void setLastName(string);
string getFirstName();
string getLastName();
}
Może to właśnie robiłeś przez cały czas. Ale nie sądzę, aby ta metoda miała problemy, które widziałem w przypadku metod kpienia, ponieważ wyraźnie nakreśliliśmy, po której stronie jest każda metoda. Używając dziedziczenia, unikamy niezręczności, która pojawiłaby się, gdybyśmy użyli dodatkowego obiektu opakowania.
Wprowadza to pewną złożoność i dla tylko kilku funkcji użyteczności prawdopodobnie po prostu przetestuję je, kpiąc z podstawowego źródła zewnętrznego. Oczywiście są narażeni na większe ryzyko pęknięcia, ale nie warto tego zmieniać. Jeśli masz wystarczająco skomplikowany obiekt, który musisz podzielić, to myślę, że coś takiego jest dobrym pomysłem.