Ostatnio TDD stosowałem metodę fabryczną. Metoda polegała na utworzeniu zwykłego obiektu lub obiektu owiniętego dekoratorem. Udekorowany obiekt może być jednego z kilku typów rozszerzających StrategyClass.
W moim teście chciałem sprawdzić, czy klasa zwracanego obiektu jest zgodna z oczekiwaniami. To proste, gdy powróci zwykły obiekt, ale co zrobić, gdy jest zawinięty w dekoratorze?
Piszę w języku PHP, aby móc ext/Reflection
znaleźć klasę zawiniętego obiektu, ale wydawało mi się, że jest to zbyt skomplikowane i nieco przypomina zasady TDD.
Zamiast tego zdecydowałem się wprowadzić getClassName()
, która zwróci nazwę klasy obiektu, gdy zostanie wywołana ze StrategyClass. Jednak wywołany z dekoratora zwróci wartość zwróconą tą samą metodą w dekorowanym obiekcie.
Niektóre kody, aby to wyjaśnić:
interface StrategyInterface {
public function getClassName();
}
abstract class StrategyClass implements StrategyInterface {
public function getClassName() {
return \get_class($this);
}
}
abstract class StrategyDecorator implements StrategyInterface {
private $decorated;
public function __construct(StrategyClass $decorated) {
$this->decorated = $decorated;
}
public function getClassName() {
return $this->decorated->getClassName();
}
}
I test PHPUnit
/**
* @dataProvider providerForTestGetStrategy
* @param array $arguments
* @param string $expected
*/
public function testGetStrategy($arguments, $expected) {
$this->assertEquals(
__NAMESPACE__.'\\'.$expected,
$this->object->getStrategy($arguments)->getClassName()
)
}
//below there's another test to check if proper decorator is being used
Chodzi mi o to: czy można wprowadzić takie metody, które nie mają innego zastosowania niż ułatwienie testów jednostkowych? Jakoś mi to nie pasuje.
Odpowiedzi:
Moje myśli nie to, że nie należy niczego robić tylko dlatego, że jest potrzebny do sprawdzalności. Wiele decyzji podejmowanych przez ludzi ma korzystny wpływ na testowalność, a testowalność może nawet być główną korzyścią, ale powinna być dobrą decyzją projektową w oparciu o inne zalety. Oznacza to, że niektórych pożądanych właściwości nie można przetestować. Innym przykładem jest to, że musisz wiedzieć, jak skuteczna jest jakaś procedura, na przykład czy Hashmap używa równomiernie rozłożonego zakresu wartości skrótu - nic w interfejsie zewnętrznym nie byłoby w stanie tego powiedzieć.
Zamiast myśleć: „Czy dostaję odpowiednią klasę strategii”, „myślę” czy klasa, którą dostaję, wykonuje to, co ta specyfikacja próbuje przetestować? ” Fajnie jest, gdy możesz przetestować hydraulikę wewnętrzną, ale nie musisz, po prostu przetestuj pokrętło i sprawdź, czy dostajesz ciepłej lub zimnej wody.
źródło
Podejrzewam, że - czasami trzeba trochę przerobić kod źródłowy, aby był bardziej testowalny. Nie jest to idealne i nie powinno być usprawiedliwieniem do zaśmiecania interfejsu funkcjami, które mają być używane tylko do testowania, więc moderacja jest tu na ogół kluczem. Nie chcesz również znajdować się w sytuacji, w której użytkownicy twojego kodu nagle używają funkcji interfejsu testowego do normalnych interakcji z twoim obiektem.
Moim preferowanym sposobem na poradzenie sobie z tym (i przepraszam, że nie mogę pokazać, jak to zrobić w PHP, ponieważ głównie koduję w językach w stylu C), jest zapewnienie funkcji „testowych” w taki sposób, aby nie były wystawione na świat zewnętrzny przez sam obiekt, ale mogą być dostępne dla obiektów pochodnych. Dla celów testowych wyprowadziłbym klasę, która obsługiwałaby interakcję z obiektem, który faktycznie chcę przetestować, i aby test jednostkowy używał tego konkretnego obiektu. Przykład w C ++ mógłby wyglądać mniej więcej tak:
Klasa typu produkcji:
Klasa pomocnika testowego:
W ten sposób jesteś przynajmniej w pozycji, w której nie musisz ujawniać funkcji typu „test” w głównym obiekcie.
źródło
Kilka miesięcy temu, kiedy umieściłem nowo zakupioną zmywarkę, z jej węża wypłynęła duża ilość wody, zdałem sobie sprawę, że jest to prawdopodobnie spowodowane tym, że została właściwie przetestowana w fabryce, z której pochodzi. Nierzadko widać otwory montażowe i inne elementy maszyn, które są tam - tylko - w celu testowania na linii montażowej.
Testowanie jest ważne, w razie potrzeby po prostu dodaj coś do tego.
Ale spróbuj niektórych alternatyw. Twoja opcja oparta na refleksji nie jest wcale taka zła. Możesz mieć chronionego wirtualnego akcesorium do tego, czego potrzebujesz, i stworzyć klasę pochodną do testowania i potwierdzania. Być może możesz podzielić swoją klasę i przetestować bezpośrednio wynikową klasę liści. Ukrywanie metody testowej za pomocą zmiennej kompilatora w kodzie źródłowym jest również opcją (ledwo znam PHP, nie jestem pewien, czy jest to możliwe w PHP).
W twoim kontekście możesz zdecydować, że nie przetestujesz odpowiedniej kompozycji w dekoratorze, ale przetestuj oczekiwane zachowanie, jakie powinna mieć dekoracja. Być może bardziej skupiłoby się to na oczekiwanym zachowaniu systemu, a nie na specyfikacji technicznej (co kupuje wzór dekoratora z funkcjonalnej perspektywy?).
źródło
Jestem absolutnym nowicjuszem TDD, ale wydaje się, że zależy to od dodanej metody. Z tego, co rozumiem na temat TDD, twoje testy mają w pewnym stopniu „napędzać” tworzenie twojego API.
Kiedy jest OK:
Kiedy nie jest OK:
źródło