Це було б динамічно, а не статично типізовано. Введення качок робило б ту саму роботу, що і інтерфейси на мовах, що мають статичний тип. Крім того, його класи можуть бути модифіковані під час виконання, щоб тестовий фреймворк міг легко заглушити або знущатися над методами існуючих класів. Рубі - одна з таких мов; rspec - це її основна тестова основа для TDD.
Як динамічне тестування допоміжних засобів тестування
За допомогою динамічного введення тексту ви можете створювати макетні об’єкти, просто створивши клас, який має той самий інтерфейс (підписи методу) об'єкта-колаборатора, з якого потрібно знущатися. Наприклад, припустимо, у вас був клас, який надсилав повідомлення:
class MessageSender
def send
# Do something with a side effect
end
end
Скажімо, у нас є MessageSenderUser, який використовує екземпляр MessageSender:
class MessageSenderUser
def initialize(message_sender)
@message_sender = message_sender
end
def do_stuff
...
@message_sender.send
...
@message_sender.send
...
end
end
Зверніть увагу на використання тут ін'єкційних залежностей , які є основним тестуванням одиниць. Ми повернемось до цього.
Ви хочете перевірити, що MessageSenderUser#do_stuff
дзвінки надсилаються двічі. Так само, як і в статично набраній мові, ви можете створити макет MessageSender, який підраховує, скільки разів send
дзвонили. Але на відміну від статично набраної мови, вам не потрібен клас інтерфейсу. Ви просто продовжуйте створювати його:
class MockMessageSender
attr_accessor :send_count
def initialize
@send_count = 0
end
def send
@send_count += 1
end
end
І використовуйте його у своєму тесті:
mock_sender = MockMessageSender.new
MessageSenderUser.new(mock_sender).do_stuff
assert_equal(mock_sender.send_count, 2)
Само по собі "набирання качок" мови, що динамічно набирається, не надто додає тестування порівняно зі статично набраною мовою. Але що робити, якщо заняття не закриті, але можуть бути змінені під час виконання? Це зміна гри. Подивимося, як.
Що робити, якщо вам не довелося використовувати ін'єкцію залежності, щоб зробити клас перевіряючим?
Припустимо, що MessageSenderUser коли-небудь використовуватиме MessageSender для надсилання повідомлень, і вам не потрібно дозволяти замінювати MessageSender іншим класом. У рамках однієї програми це часто трапляється. Давайте перепишемо MessageSenderUser, щоб він просто створював та використовував MessageSender, без введення залежності.
class MessageSenderUser
def initialize
@message_sender = MessageSender.new
end
def do_stuff
...
@message_sender.send
...
@message_sender.send
...
end
end
MessageSenderUser тепер простіший у використанні: Ніхто не створює його, щоб створити MessageSender для його використання. Це не схоже на велике вдосконалення в цьому простому прикладі, але тепер уявіть, що MessageSenderUser створений не раз, або що він має три залежності. Зараз у системі є безліч прохідних примірників лише для того, щоб зробити щасливі тести приладів, а не тому, що це обов'язково покращує дизайн взагалі.
Відкриті заняття дозволяють пройти тестування без введення залежності
Тестова основа на мові з динамічним набором тексту та відкритими класами може зробити TDD дуже приємним. Ось фрагмент коду з тесту rspec для 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
Ось і весь тест. Якщо MessageSenderUser#do_stuff
не викликати MessageSender#send
рівно два рази, цей тест не вдасться. Справжній клас MessageSender ніколи не викликається. Ми сказали тесту, що коли хтось намагається створити MessageSender, він повинен отримати замість цього насмішку MessageSender. Не потрібно вводити залежність.
Приємно зробити так багато в такому більш простому тесті. Завжди приємніше не використовувати ін'єкцію залежності, якщо це насправді не має сенсу для вашого дизайну.
Але що це стосується відкритих занять? Зверніть увагу на дзвінок на MessageSender.should_receive
. Ми не визначали #should_receive, коли писали MessageSender, і хто це робив? Відповідь полягає в тому, що тестовий фреймворк, вносячи деякі ретельні модифікації системних класів, може зробити так, щоб через #should_receive було визначено кожен об'єкт. Якщо ви думаєте, що модифікація подібних системних класів вимагає певної обережності, ви маєте рацію. Але це ідеальна річ для того, що тут робить тестова бібліотека, і відкриті класи дозволяють це зробити.