Да, на моделе данных, но без их выборки из базы данных. От этого будет зависить эффективность теста. Более подробно я написал здесь blog.byndyu.ru/2009/04/blog-post.html
Я согласен, что надо тестировать также и с взаимодействием с базой данных, но это уже интеграционные тесты.
Возможно, я не сильно углублялся в терминологию по тестированию. Судя по всему, я пишу интеграционные тесты.
А TDD вещь клёвая, но на практике всё никак не получается писать сначала тесты, потом проект.
Проблема (главная), — что менеджмент нашей компании старается всё время ускорить процесс написания приложения. Что приводит даже к тому, что тесты можем и не писать вовсе.
Пожалуй, это единственная причина…
Да, еще опыта такого не было, вот вторая проблема )
Вот вторая проблема — главная. TDD — такая вещь, преимущества которой обязательно должно понимать именно руководство.
Ровно как и то, что TDD — дисциплина, дающая отличный результат _на дистанции_. То есть, может быть «срочный кусок кода» вы до завтра и не напишете, но целое приложение будет написано быстрее (благодаря отсутствию необходимости проводить часы с дебаггером) и будет в разы проще поддерживаться.
Честно говоря все рассказы о TDD и тестировании- это хорошо. Звучит красиво. Даже не плохо получается на тестовых примерах. Но есть хорошая фраза:
«Реальность- это жестокое убийство прекрасной теории, бандой мерзких фактов.»
У всех ГИбких методологий и всему, что с ней связано(TDD, BDD и прочие интересные сокращений) есть один большой недостаток. Они гибки до определенного предела.
Когда у тебя за день 2 раза меняется ТЗ на компонент(класс, функция, метод, сборка), который ты пишешь у меня даже времени на поддержку тестов нету. Пока задача не меняется, все хорошо. На все модули есть тест, весь код используется. НО Как только меняется задача, то все тесты оказываются устаревшими, а задачу как всегда надо сделать через 30 минут. В итоге код перелопачивается сразу(не какие планирования архитектуры тут не помогут, слишком жесткое изменения)
А вот переписать эти тесты просто нету времени. Предел гибкости гибкой методологии. Во время спринта меняется все уже
(у меня так каждый приход на работу. Придешь и тебе говорят что теперь все надо поменять. Что теперь у объекта не права локального администратора, а инспектора… и все… весь код перелопатить)
Может это только у меня такое чумное руководство, но думаю я не один такой.
Большое кол-во тестов действительно может стать массой, которую сложно поддерживать. Для написания хороших тестов, которые помогают, а не отягощают, надо набить руку и… шишки.
Тесты не должны быть обременяющими — если они таковы, то TDD ведется неправильно. Наборы тестов поддерживать значительно проще, чем многостраничные толмуды ТЗ.
«Весь код перелопатить» — тут проблема отнюдь не в тестах, а в архитектуре. Если она правильно выстроена, то один модуль (unit) вообще должен работать независимо от другого. А тесты в каждый момент времени показывают, где изменения в одном модуле затронули работу другого.
В общем, народ приводит массу доводов, чтобы НЕ использовать TDD, но тут, как говорится, не попробуешь — не почувствуешь. Недаром Google, Microsoft, 37 Signals и прочие немалоизвестные компании используют эти подходы.
«Реальность- это жестокое убийство прекрасной теории, бандой мерзких фактов.»
Когда меняешь внутреннюю реализацию функции проблем нет, TDD помогает. Входные\выходные значения известны, следовательно не важно что там внутри(черный ящик.)
А теперь ситуация:
Пришел утром. Задача: класс должен принимать из бд то-то и то-то. высчитывать следующее. Отдавать туда сразу же.
К обеду сделал.
Пришел после обеда. Задача: класс должен наследовать от вот этого объекта, и реализовывать вот такой функционал. Данные берутся из экселя, высчитываются совсем другие метрики. А затем держатся внутри объекта, пока не потребуется забрать от туда.
В общем хорошо, когда все по шаблону, по книжке. Жаль что мы не в книжке…
Во всем есть свои нюансы, в том числе в тестировании.
Сам процесс разработки должен быть выстроен немного по-другому (на мой взгляд, более организованно).
Например.
«Задача: класс должен принимать из бд то-то и то-то. высчитывать следующее. Отдавать туда сразу же.»
1. Принимать из БД — это уже не бизнес-логика, а инфраструктура. Запись/чтение из БД — отдельная задача, не имеющая прямого отношения к работе бизнес-правила в коде. Если стоит задача проверить прецеденты взаимодействия с реальной БД — необходимы интеграционные тесты.
2. «Высчитывать следующее» — вот это собственно бизнес-логика. Она тестируется отдельно, вообще вне контекста «БД».
3. «Отдавать туда сразу же» — что куда отдавать? После написания и проверки кода на машине разработчика, код должен поступить в центральный репозиторий контроля версий (или его ветку) на сервере, где по определенным условиям запущена общая сборка и необходимый набор общих тестов (Постоянная интеграция a.k.a. Continuous Integration, CI). Это дает гарантию, что все (даже небольшие) изменения полностью «уживаются» со всем проектом.
4. «Задача: класс должен наследовать от вот этого объекта, и реализовывать вот такой функционал.»
Берется актуальный «снимок» нужной части проекта из репозитория. Пишутся тесты для новых бизнес-правил. Вносятся новые элементы бизнес-логики (для которых и писались тесты).
5. «Данные берутся из экселя, высчитываются совсем другие метрики.»
Неважно откуда берутся данные. Связь и работа с источником данных — это инфраструктура и здесь применяется интеграционное тестирование. А вот подсчет тех или иных метрик — бизнес-логика. Она тестируется независимо от источника данных (так как у нас задача проверить механизм, логику, а связь с источником данных мы, как уже было сказано, проверяем с помощью интеграционных тестов).
6. «А затем держатся внутри объекта, пока не потребуется забрать от туда.»
Это все уже бизнес-логика (логика работы объектов предметной области a.k.a. Domain Objects) и тестируется соответственно (unit-тестами вне зависимости от источников данных).
Все автоматизировано, на все есть определенные процедуры, как действовать в том или ином случае. TDD — это не шаблон, это просто другой метод, как раз-таки призванный добавить (а не убавить) гибкости процессу разработки.
Такое экстремальное программирование в прямом смысле этого слова не должно быть нормальным рабочим процессом. Обычно новая срочная задача вечером в пятницу — признак кривого менеджерства и сигнал о необходимости искать новое место.
Его цель — проверка бизнес-правила, проверка того, что это правило верно описано в коде, а не того, как это физически взаимодействует с базой. Откуда берутся данные с точки зрения работы правила не имеет значения.
Я не знаком с C#, поэтому у меня следующий вопрос: правильно ли я понимаю что в C# можно на лету (в runtime) создавать Mock-объекты вокруг интерфейса? То есть нет нужды создавать Mock-объекты классическим способом — фейковой реализацией интерфейса? Например в Actionscript 3 на лету создать нельзя в связи с ограничением возможностей языка.
И еще такой вопрос… Представим ситуацию когда в системе (приложении) есть интерфейс IProduct, и в этой же системе есть много ( > 1 ) реализаций этого интерфейса. Как разруливать ситуацию когда в разных местах должна ижектитится разная реализация интерфейса IProduct?
ЗЫ. Спасибо за отличный скринкаст, очень эффективно, по сравнению с чтением и пониманием данной информации.
Для создания mock-объектов я использовать библиотеку Moq code.google.com/p/moq
Она дает не создавать stub-объекты (class StupProductRepository: IProductRepository), а оборачивать их new Mock(). Это удобно тем, что нет лишнего кода для создания stub-объектов и возможно задавать поведение mockRepository.Setup(r => r.GetLastProduct()).Returns(new Product())
Как разруливать ситуацию когда в разных местах должна ижектитится разная реализация интерфейса IProduct?
Александр, спасибо! Отличный скринкаст, равно как и про TDD.
Практически всё из изложенного знал и понимал раньше, но — почти не использовал. Так что для меня подобные скринкасты являются скорее мотивационными, чем информационными. И это прекрасно! Прочитать про best practices — это одно, а посмотреть код, сопровождаемый живыми комментариями — куда нагляднее!
Управление зависимостями в коде