Byłoby to dynamiczne, a nie statyczne. Pisanie kaczką wykona wtedy tę samą pracę, co interfejsy w statycznych językach. Ponadto jego klasy można modyfikować w czasie wykonywania, aby środowisko testowe mogło z łatwością wprowadzać lub wyszydzać metody z istniejących klas. Ruby jest jednym z takich języków; rspec to najlepsza platforma testowa dla TDD.
Jak dynamiczne pisanie pomaga testować
Dzięki dynamicznemu pisaniu możesz tworzyć fałszywe obiekty, po prostu tworząc klasę, która ma ten sam interfejs (podpisy metod), obiekt współpracujący, którego potrzebujesz wyśmiewać. Załóżmy na przykład, że masz klasę, która wysłała wiadomości:
class MessageSender
def send
# Do something with a side effect
end
end
Powiedzmy, że mamy MessageSenderUser, który korzysta z instancji MessageSender:
class MessageSenderUser
def initialize(message_sender)
@message_sender = message_sender
end
def do_stuff
...
@message_sender.send
...
@message_sender.send
...
end
end
Zwróć uwagę na zastosowanie zastrzyku zależności , podstawowego zestawu testów jednostkowych. Wrócimy do tego.
Chcesz sprawdzić, czy MessageSenderUser#do_stuff
połączenia wysyłane są dwukrotnie. Podobnie jak w przypadku języka o typie statycznym, możesz utworzyć próbny MessageSender, który zlicza liczbę wywołań send
. Jednak w przeciwieństwie do języka o typie statycznym nie potrzebujesz klasy interfejsu. Po prostu idź i stwórz go:
class MockMessageSender
attr_accessor :send_count
def initialize
@send_count = 0
end
def send
@send_count += 1
end
end
I użyj go w swoim teście:
mock_sender = MockMessageSender.new
MessageSenderUser.new(mock_sender).do_stuff
assert_equal(mock_sender.send_count, 2)
Samo „pisanie kaczką” w języku dynamicznie wpisywanym nie dodaje zbyt wiele do testowania w porównaniu do języka statycznego. Ale co, jeśli klasy nie są zamknięte, ale można je modyfikować w czasie wykonywania? To zmieniacz gier. Zobaczmy jak.
Co się stanie, jeśli nie będziesz musiał używać wstrzykiwania zależności, aby przetestować klasę?
Załóżmy, że MessageSenderUser będzie kiedykolwiek używał MessageSender tylko do wysyłania wiadomości, a Ty nie musisz zezwalać na zastępowanie MessageSender przez inną klasę. W jednym programie często tak jest. Przepiszmy MessageSenderUser, aby po prostu tworzył i używał MessageSender, bez wstrzykiwania zależności.
class MessageSenderUser
def initialize
@message_sender = MessageSender.new
end
def do_stuff
...
@message_sender.send
...
@message_sender.send
...
end
end
MessageSenderUser jest teraz prostszy w użyciu: nikt go tworzący nie musi tworzyć MessageSender, aby mógł z niego korzystać. W tym prostym przykładzie nie wygląda to na duże ulepszenie, ale teraz wyobraź sobie, że MessageSenderUser jest tworzony w więcej niż jednym miejscu lub że ma trzy zależności. Teraz system ma wiele instancji, aby uszczęśliwić testy jednostkowe, nie dlatego, że w ogóle poprawia projekt.
Klasy otwarte umożliwiają testowanie bez wstrzykiwania zależności
Struktura testowa w języku z dynamicznym pisaniem i otwartymi klasami może uczynić TDD całkiem niezłym. Oto fragment kodu z testu rspec dla MessageSenderUser:
mock_message_sender = mock MessageSender
MessageSender.should_receive(:new).and_return(mock_message_sender)
mock_message_sender.should_receive(:send).twice.with(no_arguments)
MessageSenderUser.new.do_stuff
To cały test. Jeśli MessageSenderUser#do_stuff
nie wywoła MessageSender#send
dokładnie dwa razy, test nie powiedzie się. Prawdziwa klasa MessageSender nigdy nie jest wywoływana: powiedzieliśmy testowi, że ilekroć ktoś próbuje utworzyć MessageSender, powinien zamiast tego uzyskać nasz pozorowany MessageSender. Nie jest konieczne wstrzykiwanie zależności.
Miło jest robić tyle w tak prostszym teście. Coraz przyjemniej jest nie stosować zastrzyku zależności, chyba że ma to sens dla twojego projektu.
Ale co to ma wspólnego z otwartymi zajęciami? Zanotuj połączenie z MessageSender.should_receive
. Nie pisaliśmy #should_receive, kiedy pisaliśmy MessageSender, więc kto to zrobił? Odpowiedź jest taka, że struktura testowa, dokonująca pewnych ostrożnych modyfikacji klas systemowych, może sprawić, że będzie wyglądać tak, jakby #should_receive było zdefiniowane dla każdego obiektu. Jeśli uważasz, że modyfikowanie takich klas systemowych wymaga pewnej ostrożności, masz rację. Ale jest to idealna rzecz do tego, co robi biblioteka testowa, a otwarte klasy umożliwiają to.