четверг, 12 апреля 2012 г.

Имитация в тестировании / Mock в Mockolate

Когда только начинал писать unittest-ы, меня сильно удручала необходимость создавать нормально работающий контекст, чтобы потом протестировать в нем всего одну функцию. Как же было приятно потом узнать, что для таких вещей есть свой подход - Mock-объекты.

Mock переводят как - пародийный, фиктивный, мнимый. или имитация, подделка. Но я предпочитаю использовать английское слово, чтобы избежать наслоения дополнительный оттенков.




Зачем нужны Mock-объекты

  • когда работа с объектом окружения медленная, а тесты хочется прогнать быстро;
  • объект представляющий неопределенное состояние (как текущее время);
  • обладает состоянием, которое сложно воспроизвести (хорошее или наоборот плохое соединение по сети, поломка оборудования);
  • отлично стирает за собой все следы жизнедеятельности;
По-сути образное представление mock-объекта - это глина, или пластилин, которые может принять любую форму. Мы запускам объект в конвейер тестов, а потом, по оттискам на поверхности понимаем, что произошло с Mock-объектом во время тестирования. Ну и конечно мы можем создать подделку не отличимую от оригинала. Почему-то для создателей библиотеки это шоколад :).

На Flash есть несколько реализаций этого удивительного Mock-объекта. Я использую Mockolate.

Возможности Mockolate

  • Из явных плюсов - использование fluent interface - такое уж веяние моды, но многие библиотеки переписывают под такой стиль API (взять к примеру RobotLegs).
  • Симулировать методы, свойства объекта, принимаемые события;
  • Запись поведения объектов;
Официальные источники: http://mockolate.org/ , https://github.com/drewbourne/mockolate.

Применение

Работа с Mock объектами состоит из фаз:
  1. подготовка;
  2. создание;
  3. настройка (не обязательно);
  4. использование;
  5. проверка;
Далее привожу отрывки кода из официальной документации.

Существуют два пути подключения объектов (ручное и через инъекции):

1 / Ручной Путь. Подготовка объекта

http://mockolate.org/preparing_and_creating.html

Необходимо дать Mock-объекту проинициализировать ваши типы (Flavour, DarkChocolate), по-хорошему они должны быть интерфейсами, хотя и с классами Mockolate отлично справляется:
[Before(async, timeout=5000)]
public function prepareMockolates():void
{
    Async.proceedOnEvent(this,
        prepare(Flavour, DarkChocolate),
        Event.COMPLETE);
}

2 / Ручной Путь. Создание объекта

1-ый способ nice. Если попытка эмуляции метода или свойства не удается, мы в ответ получаем false.
var flavour:Flavour = nice(Flavour);

2-ой способ strict более агрессивный, Если попытка эмуляции метода или свойства не удается, мы в ответ получаем исключение
var flavour:Flavour = strict(DarkChocolate);

1 и 2 / Путь инъекций. Подготовка и создание объекта

инъекция Mock-объектов в публичные свойства, отмеченные аннотацией
http://mockolate.org/preparing_and_creating_easier.html

Для чего необходимо подключить MockolateRule правило. А также добавить аннотации у публичных полей:

[Rule]
public var mocks:MockolateRule = new MockolateRule();
    
[Mock(type="strict")]
public var strictlyThanks:Example;
    
[Mock(inject="false")]
public var prepareButDontCreate:Example;

Уточню отличия:
use [Mock(inject="true")] подготовить и произвести инъекцию экземпляра Mock-объекта (используется по-умолчанию);
use [Mock(inject="false")] только подготовить, но не создавать экземпляр Mock-объекта;

так же в тестах можно убрать проверку (о проверке можно узнать тут):
[Test(verify="false")]

3 / Создание заглушки вместо метода или свойства 

http://mockolate.org/stubbing_and_mocking.html

С помощью заглушки можем проверять с подходящими ли параметрами был вызван метод или свойство;
var flavour:Flavour = nice(Flavour);
var yucky:Flavour = nice(Flavour);
var yummy:Flavour = nice(Flavour);

mock(flavour).method("combine").args(yummy);

flavour.combine(yucky);

verify(flavour);

После выполнения кода мы получим ошибку, т.к. пытались вместо yummy подсунуть yucky.

Так же можно навешивать поведение (вызов метода, события или исключения) по факту вызова метода или свойства.
stub(flavour).method("combine").args(anything())
    .returns(combinedFlavour)
    .dispatches(new FlavourEvent(FlavourEvent.TASTE_EXPLOSION));
Полный список возможностей лучше глянуть тут

Recording. Альтернативная нотация поведения объектов. 
http://mockolate.org/recording_and_replaying.html

начать записывание

record(flavour);

настроить объект

expect(flavour.name).returns("Butterscotch");

воспроизвести изменения

replay(flavour);

После этого передаем объект, как и обычный объект.

4 / Использование объекта

Тут ничего нового, созданный Mock-объект, как обычный объект передается заинтересованным функциям.

5 / Проверка

http://mockolate.org/verifying_and_test_spies.html

Для проверки того что все методы были вызваны надлежащим образом, есть необходимая инструкция:

verify(flavour);

Чего Mockolate не может

  • работать со свойствами методами, отмеченными как final;
  • классами в корневом пакете, т.е. с теми, что лежат прямо в src;
  • статическими методами и свойствами;
  • публичными переменными;
  • публичными константами;
  • с чем либо приватным;

Нюансы

Если в лоб подключить исходники Mockalate в код проекта, то получаем ошибку : 1120: Access of undefined property useFlexClasses.
Т.к. в исходниках проект включен параметр useFlexClasses….

проблема легко решается добавлением параметра компиляции (Project / Properties / ActionScript Compiler / Additional compiler arguments). Если вы не используете flex:
-define=CONFIG::useFlexClasses,false.
Если использовать swc таких проблем не возникнет.

Проблемы рефакторинга

Проблему можно назвать - козни рефакторинга.
Разыгралась следующая ситуация - в объекте, который имитировал Mock, был метод "apply".
Так выгладил код создания загрушки:

mock(handler1).method( "apply" ).args(EVENT_1, value1);  

После серии рефакторингов в оригинальном объекте метод стал называться "execute". Однако из-за специфики Flash Builder, настройка Mock объекта осталась прежней.

И как следствие последующий вызов:

verify(handler1);

стала выдавать ошибку:
Error: 1 unmet Expectation

и как по этому можно понять, что я ошибся в названии метода, осталось для меня загадкой, и лишь diff помог мне с этим справится?!
Но, к счастью, в Mockolate для решения этой проблемы можно использовать Recording (см. выше).

Когда Mock мешает

  1. имитатор использовать сложнее, чем имитируемый объект;
  2. при описании объекта во многом повторяется его функциональность. Высока вероятность получить проблему дублируемого кода. Т.е. код описания поведения Mock-объекта надо будет поддерживать после каждого рефакторинга оригинального объекта;

Альтернативы Mockolate

  • https://bitbucket.org/loomis/mockito-flex/ - Mockito - последний коммит был в конце 2010. Может библиотека достаточно зрелая? Выглядит похожим на Mocklate, и достаточно вкусным;
  • http://asmock.sourceforge.net/ - ASMock - для создания заглушек используется подход похожий на record у Mockolate.
  • http://code.google.com/p/mock4as/ - Mock4AS - похоже самый слабый вариант, т.к. необходимо самому создавать классы имитаторы;

Комментариев нет:

Отправить комментарий

Press Any Key...