Udostępnij za pośrednictwem


Юнит-тесты: Как протестировать то, что не тестируется

Есть один замечательный вопрос, который возникает в любой дискуссии связанной с юнит-тестированием. «Надо ли создавать тесты для юнит тестов». Ответом на этот вопрос, как правило, служит технология Code Coverage. Действительно, если вы хотите убедиться в том, что юнит тест подготовлен правильно, вам нужно только проверить вызываются ли все ветвления в коде. Достигается это простым методом – надо подать на вход проверяемой функции все комбинации данных, которые позволят обойти эти ветвления. И академические примеры из документации это показывают.

Но подвох в том, что реальный мир сложнее. Функции приложения могут учитывать условия не только подаваемые на вход. Как быть в этом случае?

Еще раз про Code Coverage.

Представим, что вам надо проверить код осуществляющий покупку товара:

Если вы при этом создадите юнит тест, то очевидно, что вам необходимо предусмотреть вызов этой функции с различными параметрами. Для случая itemID=0 и ItemID<>0. В таком варианте будут вызваны все ветки кода, и этот юнит тест будет иметь значение Code Coverage 100%.

Усложняем задачу

Более сложный код, приближенный к реальности может иметь следующий вид:

Как быть в таком случае? Внутри функции используется вызов CLR Date.Time, и мы на возврат данных этой функции никакого влияния не имеем. Как бы мы не пытались усложнить логику юнит-теста, работа функции зависит от внешних условий. Что, теперь запускать юнит тесты по расписанию два раза в день, днем и вечером, чтобы проверить все ветки кода?

Можно конечно воспользоваться паттерном Visitor. При создании класса с методом PurchaseItem мы будем передавать некий интерфейс IGetCurrentTime который при работе программы будет иметь одну имплементацию, а при вызове юнит теста будет осуществляться подмена. Тем самым мы добьемся изоляции от внешних условий. Это неплохой вариант, который в общем то всеми рекомендуем. Но есть ли какие то другие варианты?

Изолируемся от внешнего мира с помощью Moles

Для Visual Studio 2010 существует замечательное дополнение Moles. Это так называемый Isolation Framework который и может помочь нам в таких случаях:

Установить вы его можете из Extension Manager, как говорится, не выходя из Visual Studio. Изучать его возможности можно начать тут research.microsoft.com/en-us/projects/pex/documentation.aspx, а так же на Хабре есть несколько интересных и подробных статей.

Возможности этого дополнения достаточно широки и в том числе позволяют вам создавать «перехватчики» функций вашего приложения или любой другой внешней функции. При этом у вас отпадает надобность в изменении исходного кода вашей программы.

Перехватываем DateTime.Now

Итак, что же надо сделать чтобы наш юнит тест не зависил от возвращаемых значений DateTime.Now?

Первый шаг – добавляем в наш тестовый проект Moled Assembly – специальным образом подготовленную заглушку для сборки MSCorLib в которой содержится метод DateTime.Now

Второй шаг – объявляем, что мы действительно собираемся перехватывать метод DateTime.Now:

Третий шаг – собственно переделываем наши юнит тесты:

Некоторые пояснения: атрибут HostType необходим для того чтобы указать подсистеме Moles Runtime что в данном юнит тесте будет осуществляться перехват внешних функций. Далее в коде указывается что метод DateTime.Now должен на самом деле браться из лямбды. В начале было указано что Moles сгенерировал для mscore специальную заглушку. Типы в этой заглушке имеют префиксы и суффиксы. Например, для типа DateTime.Now заглушка будет System.Moles.MDateTIme.NowGet. Запустив этои юнит тесты мы опять получим значение Code Coverage 100% вне зависимости от того, какое на самом деле текущее время.

Заключение

Конечно, приведенный пример очень прост, и Moles способен на более сложные варианты использования. Реальное применение этой технологии может быть удобно для изоляции вызовов баз данных, файловой системы, сложного API связанного с железом, веб-сервисами внешних поставщиков, и многих других сценариев. Но тем не менее, этот пример поможет вам быть уверенными, что к любой даже самой сложной функции все-же можно написать юнит тест.

Кстати, завтра в офисе Microsoft на Крылатских холмах пройдет мероприятие Microsoft Quality Assurance Day www.microsoft.com/ru-ru/events/msqadays/index.html на котором будут обсуждаться вопросы обеспечения качества ПО. В моем докладе будет, упомянут Moles и я покажу «живьем» как эти механизмы работают. Если вас заинтересовала эта технология, обязательно посмотрите трансляцию.

Comments

  • Anonymous
    March 07, 2011
    >Можно конечно воспользоваться паттерном Visitor Уверен, что к паттерну Visitor описанный прием не имеет отношения.

  • Anonymous
    March 08, 2011
    Почему? Распространенный метод решения подобных задач - создать и интерфейс который будет подаваться на вход функции (visitor), его поведение и имплементация будут разными для настоящего и тестового запуска.