Pull to refresh

Comments 21

А вот фреймворк Moq (http://code.google.com/p/moq/) какие-то похожие возможности не предоставляет? Вроде, что-то общее в примерах проглядывается.
В природе существует предостаточное количество Isolation Frameworks с более-менее похожими возможностями, при этом каждый фреймворк имеет свои особенности. К примеру я не нашел в Moq возможности редиректить вызовы статических и приватных членов классов — а вот Moles умеет это делать.
В конце концов, каждый волен выбирать то, что ему подходит больше.
Ага, видимо как раз для статических и приватных и приходится генерировать отдельные классы.
Единственный Isolation Framework который умеет мокать статичные, невиртуальные и приватные методы — это Typemock Isolation. Но он весьма не дешевый — хотя и очень функциональный
Moles делает то же самое даром.
Moles is also available separately for commercial use without requiring an MSDN subscription.
Тесты на моках — зло. Данный пример с вызовами внешних сервисов более или менее оправдан. Но используя такой подход, вы завязываете реализацию своих тестов на внутренней структуре объектов, а не на интерфейсе объектов, иначе говоря, тесты будут ломаться, когда код будет фактически работоспособным.

Наиболее эффективными (но и трудоемкими в написании) являются тесты, основанные на использовании тестовых реализаций (тестовые дубликаты). Т.е. что-то вроде InMemoryFileManager и InMemoryFileStorage, которые по честному вычисляют хеш-код и так далее.
используя такой подход, вы завязываете реализацию своих тестов на внутренней структуре объектов, а не на интерфейсе объектов, иначе говоря, тесты будут ломаться, когда код будет фактически работоспособным.

Упавшие тесты не составит особого труда пофиксить, а что вам даст уверенность что после изменения внутренней структуры класса, вы не поломали его логику?
Я для того и упомянул об особенностях юнит тестирования в обзоре, — обратите внимание на пункт Для чего нужно юнит тестирование.
При адекватном использовании моков, такие неоправданные падения тестов будут скорее исключением чем правилом.
Во вторых, упавшие тесты заставляют держать руку на пульсе — человек который их фиксит, волей-неволей должен вникнуть в логику работы класса который он изменил(а юнит тесты как раз и документируют логику классов). Это особенно важно в коллективной разработке, когда над одним проектом трудятся десяток и более девелоперов.

Наиболее эффективными (но и трудоемкими в написании) являются тесты, основанные на использовании тестовых реализаций

Вам не кажется что это те же самые моки, только реализованные вручную? Какие преимущества с точки зрения юнит-тестирования дает такой подход(кроме увеличения скорости работы тестов и простоты набивки тестовых данных)?
Давайте подискутируем=)

>Упавшие тесты не составит особого труда пофиксить, а что вам даст уверенность что после изменения внутренней структуры класса, вы не поломали его логику?

Вы пытаетесь решить не ту проблему. Уверенность дает хороший тест написанный для API тестируемого модуля (а не реализации, как в случае мока). Если тест написаный для «черного ящика» не ломается при сломанном коде, это говорит о плохой архитектуре модуля, либо плохом тесте. Тут рефакторить нужно, а не моки лепить. «Проверяй результат, не проверяй реализацию» — для меня это одно из главных правил при написании тестов.

>обратите внимание на пункт Для чего нужно юнит тестирование.

>>Документирование логики юнитов – для того чтоб понять как функционирует юнит — достаточно прочесть код его тестов.

Т.е. по вашей логике, если человек не понял что делает модуль по его коду, то он поймет это по не слишком читаемой «полотне» кода моков? Опять же проблема в плохом коде. Под документированием логики юнитов я понимаю рабочий пример использования API в виде теста.

> человек который их фиксит, волей-неволей должен вникнуть в логику работы класса который он изменил

Если человек лезет куда попало, заставить его вникнуть путем дублирования логики в тесте тоже заход «не с того конца».

>Вам не кажется что это те же самые моки, только реализованные вручную?

Конечно, дело вкуса. Но фейковые реализации читабельнее моков. И их использование не подразумевает, что будет проверяться вздор вроде сколько раз метод А вызывал метод Б. Проблема в том, что не всегда возможно написать нормальный дубликат. Для таких случаев как раз и нужны моки. Написать дублитак не получилось, хоть замочим.

>Какие преимущества с точки зрения юнит-тестирования дает такой подход

Вычищаем архитектуру и API, пишем более компактные и легко поддерживаемые тесты. Минус — мир не иделаен, что-то все равно приходится мочить.

«Проверяй результат, не проверяй реализацию»

приведу цитату из очень хорошей статьи:
Unit-тестирование условно делится на два подхода:
* state-based testing, в котором мы тестируем состояние объекта после прохождения unit-теста
* interaction (behavioral) testing, в котором мы тестируем взаимодействие между объектами, поведение тестируемого метода, последовательность вызовов методов и их параметры и т.д.
… Иногда просто удобнее проверить состояние объекта, а иногда – его взаимодействие с другими объектами. Поэтому эти два подхода прекрасно уживаются вместе, когда вы понимаете, о чем идет речь, и что именно вы хотите сейчас проверить.


В целом согласен, но все же behavioral testing это не от хорошей жизни. В идеале такие тесты должны проверять бизнес-поведение, на практике достаточно высоких абстракций не бывает, в основном «сервисы», которые таскают между собой данные без поведения. Так что в книгах/статьях одно, в жизни — другое.
behavioral testing это не от хорошей жизни

по сути, само юнит-тестирование не от хорошей жизни… а behavioral testing это всего лишь один из подходов, ни больше ни меньше. А говорить что какой то из подходов хуже/лучше бессмысленно, нужно просто правильно их использовать, и да будет счастье.
Оно, конечно, круто и мощно. Но ведь для тестирования 15-строчного кода понадобилось написать 55 строк теста. Не многовато ли?
Рефакторинг тестов никто не отменял. Учитывая что тестовых ситуаций могут быть десятки, можно инициализацию тестовой среды вынести в отдельные методы и использовать их повторно с передачей нужных параметров.
А если тестовая ситуация одна, то без использования моков вам понадобится создавать свои реализации fake обьектов, или обращаться к реальным обьектам, перед забив их тестовыми данными — на это тоже потребуется немало усилий по реализации и сопровождению. Плюс вы таким образом уходите в сторону от концепции юнит тестирования.
Интересно. Сам использую Typemock, но надо будет глянуть на всякие там Pex, Chess, Moles.
Спасибо за обзор. Первое впечатление о Moles — громоздко и многословно. Но, если разобраться и не злоупотреблять, т. е. не добавлять лишней логики и ограничиться возвращением заранее подготовленных значений, то получается все довольно аккуратно. Почти как в Moq.
что за параметр dataMapper — инстанс класса FileManager?

PS. Вообще, спасибо. Познавательно.
Извиняюсь, — исправил на fileManager. Забыл переименовать переменную после переименования класса с FileDataMapper на FileManager.
"# Реализация теста должна быть максимально простой и не требовать инициализации тестового окружения вне теста(например подготовки тестовых данных в БД).
[...]
# Прохождение теста не должно зависеть от внешних систем(сервера баз данных, веб-сервера, файловая система, контроллеры внешних устройств), если он не предназначен для тестирования взаимодействия с этими системами."
Одно противоречит другому. Таки если я написал юнит-тест для тестирования взаимодействия с этой системой, как мне ее оттестировать, не инициализируя эту систему?

Ну и да. Идея о том, что юнит-тест должен работать без тестового окружения (например, БД) приводит к тому, что нам приходится писать два разных теста — сначала юнит-тест, который проверит, что на моке все работает, а потом юнит-тест, который проверит, что БД работает так же, как мок. А вы пробовали когда-нибудь найти все особенности поведения крупного DAL? Когда на моке, который возвращает IQueryable, собранный из массива данных в памяти, все работает (потому что работает LINQ to objects), а на боевому запуске, где IQueryable из датаридера, все падает (потому что работает, скажем, LINQ to SQL, в котором нет поддержки нужного метода).
# Прохождение теста не должно зависеть от внешних систем(сервера баз данных, веб-сервера, файловая система, контроллеры внешних устройств), если он не предназначен для тестирования взаимодействия с этими системами."
Одно противоречит другому. Таки если я написал юнит-тест для тестирования взаимодействия с этой системой, как мне ее оттестировать, не инициализируя эту систему?

Если вы написали автоматический тест для тестирования взаимодействия вашего модуля со сторонней системой, то это уже не юнит-тест а integration test. Само по себе понятие «юнит-тест» предполагает тестирование логики реализованной в вашем модуле(группе модулей).
Ну и да. Идея о том, что юнит-тест должен работать без тестового окружения (например, БД) приводит к тому, что нам приходится писать два разных теста — сначала юнит-тест, который проверит, что на моке все работает, а потом юнит-тест, который проверит, что БД работает так же, как мок. А вы пробовали когда-нибудь найти все особенности поведения крупного DAL? Когда на моке, который возвращает IQueryable, собранный из массива данных в памяти, все работает (потому что работает LINQ to objects), а на боевому запуске, где IQueryable из датаридера, все падает (потому что работает, скажем, LINQ to SQL, в котором нет поддержки нужного метода).

Правильный подход в данном случае — написание тестов для:
1 — тестирования логики которая формирует IQueryable на соотвествие формирования запросов.
2 — тестирования логики LINQ to SQL на наличие поддержки необходимых методов.
Для большого проекта вторую группу тестов вам понадобится реализовать всего один раз.

«Правильный подход в данном случае — написание тестов для:
1 — тестирования логики которая формирует IQueryable на соотвествие формирования запросов.
2 — тестирования логики LINQ to SQL на наличие поддержки необходимых методов.»
Вы самолично пробовали это сделать? Хотя бы раз?

Вообще, сама идея, что мне надо писать юнит-тесты на Linq2Sql, меня очень веселит.

Про что, собственно, и речь — что идея «давайте напишем изолированный тест с моками» приводит к тому, что сложность написания мока превосходит сложность тестируемого кода. Честное слово, инициализировать базу, учитывая наличие соответствующих систем, иногда проще.
Sign up to leave a comment.

Articles