Как стать автором
Обновить

Комментарии 65

Первый вопрос — как по феншую делать запуск юнит тестов? Это должен быть отдельный проект (и запускаемый .exe файл в результате), или же проект с юнит тестами должен запускаться каким-то определенным образом (с ключем командной строки?) чтобы выполнить тесты? Или волшебная интеграция в среду разработки?
Если идёт командная разработка, перед выкладкой нового кода разработчик собирает билд и прогоняет тесты через IDE либо отдельное приложение. После этого код заливается в сорс контрол. На сервере по евентам происходит сборка нового билда и прогон тестов под управлением continuous integration софта, например cruise control. Это довольно распространённый сценарий.
И то, и другое и третье. Для различных IDE есть кучи плагинов для запуска самых разных unit-test-фреймворков. Для этих же фреймворков, для каждого, существуют свои GUI.

С точки зрения организации — в частности, в .NET — это делается отдельным проектом/сборкой, от которой основной код не зависит.

Кроме этого, должны быть настроены инструменты автоматического билда/прогона тестов на общем сервере рабочей команды (см. Continuous Integration, CI), который происходит на определенных условиях (по расписанию, вручную или по мере обновления репозитория контроля версий).
Спасибо за вопрос. Я частично коснулся его "… Тест запускается консольной утилитой xUnit, либо каким-нибудь плагином к Visual Studio."
Вообще, тесты находятся в отдельной библиотеке и компилируются в .dll файл. Эти тесты можно запускать разными способами:
1. У xUnit-а есть консоль для запуска тестов и GUI. Оба приложения на вход принимают .dll с тестами.
2. Плагин к Visual Studio. Например, очень удобно использовать ReSharper.
Ага, плюс для VS есть еще TestDriven.NET
И еще вопрос по code coverage. Я правильно понимаю, что на данный момент есть всего два способа его получения внутри visual studio (в текстовом редакторе) — встроенные средства Team Edition и платный ReSharper?
Можешь прояснить: "… его получения внутри visual studio".
Просто есть средства проверки покрытия кода, такие как PartCover, но видимо это не «внутри visual studio».
«Внутри visual studio» — имеется в виду текстовый редактор, встроенный в visual studo. Потому как если coverage показывается внешней тулзой, то значительное время тратится на переход от внешней тулзы к редактору и обратно.
Интересно, а зачем так часто смотреть покрытие тестами? Я думаю хватает один раз в день снимать эту метрику.
Совершенно верно. Более того, и метрика «покрытия кода по результатам дня» зачастую практического применения не находит, а используется для всего проекта в целом (за весь период) для поддержания уровня покрытия и для принятия стратегических решений по разработке.
Какбы удобный паттерн использования — написал тесты, написал код, посмотрел покрытие, дописал тесты, опять посмотрел покрытие, опять дописал тесты, удовлетворился покрытием и пополз дальше O_O.

Тут на самом деле некая беда с покрытием контроля ошибок. Если проверять контроль ошибок в юните, то тесты начинают сильно завязываться на реализации, что не есть хорошо. Например, метод два раза выделяет память. Соответственно, надо проверить коректность работы если обломилось первое выделение и второе. Это можно сделать спецтестом, но тогда тест будет завязан на внутреннее устройство объекта, на знании того, что он два раза выделяет какой-то ресурс. К сожалению, моей квалификации на данный момент не хватает чтобы корректно разруливать такие ситуации :(. Может кто что посоветует? ^_^".
Уточните, о чем идет речь про «контроль ошибок»?

Что касается «завязанности» тестов на реализации — то в чем тут особый минус? Ведь так и должно быть. Есть код, есть тесты, которые именно этот код проверяют.

Вопрос в том, что в конкретный момент удобнее применять — тесты состояния (оценка результата) или тесты поведения (behaviour testing) с использованием mock-объектов (в этом случае тест действительно знает что-то о внутреннем устройстве и последовательности инструкций метода).

P.S. «Метод 2 раза выделяет память» — может стоит разделить метод на 2 метода? И отделить бизнес-логику от инфраструктуры (a.k.a. Aspect Oriented Programming)?
Уточните, о чем идет речь про «контроль ошибок»?


Я имею в виду, что штатное поведение юнита проверяется достаточно просто: по входным-выходным параметрам и сайд эффектам. Но при разработке ряда программного обеспечения, например библиотек или общих модулей, часто возникает необходимость проверять как юнит реагирует на нештатные ситуации: некорректное поведение других юнитов, недостаток системных ресурсов, ошибки вызова системных функций. Для проверки этой функциональности приходится в тестах закладываться на внутреннее устройство объекта.

«Метод 2 раза выделяет память» — может стоит разделить метод на 2 метода?


Меня интересуют как раз те случаи, когда юнит представляет собой законченную функциональную единицу и его метод является логически атомарным. Согласитесь, что логично иметь в юните метод Print(), а не PrintPrepareDevice(), PrintAllocMemory(), PrintPrint() :)
> «часто возникает необходимость проверять как юнит реагирует на нештатные ситуации: некорректное поведение других юнитов, недостаток системных ресурсов, ошибки вызова системных функций.»

Ну ведь unit-testing фреймворки позволяют ловить исключения — можно механизм Exception-ов для этого и использовать. Все возможные сайд-эффекты таким образом и тестировать.

По поводу атомарности юнита я полностью согласен, точнее даже именно об этом я и говорил. Может мы немного о разных вещах? :)
Исключения — это да. Тоесть проверить что юнит корректно обработал ошибку — это не проблема. Проблема эту ошибку создать. Как уже говорилось, если в методе есть два выделения памяти — то сложно сделать такой юнит тест, чтобы он сперва проверил корректную обработку если первое выделение памяти не прошло, а потому проверил корректную обработку что второе выделение памяти не прошло. Как создать такие условия, при котором пройдет первое выделение памяти, но не пройдет второе? Маркировать их? :)
Ставьте TestDriven.net аддон к студии. Для персонального использования он бесплатен. Помимо того, что им можно гонять тесты nUnit прямо из студии, к нему в добавок идет бесплатная версия coverage tool — NCover.
НЛО прилетело и опубликовало эту надпись здесь
Да, я сам учился на таких проектах. Можете посмотреть практически любую популярную программу, которая лежит в OpenSource. Лично мое изучение TDD началось с исходников NAnt. Сейчас очень стремительно развивается NHibernate, могу вам посоветовать последить за его развитием, а возможно и поучаствовать.
тесты должны быть простыми. если код плохо тестируется либо тесты очень сложные и хрупкие, то это первый сигнал к тому, что что-то с проектированием не так.
с этой колокольни — нужно перечитывать литературу по проектированию скорее, чем по практикам тестирования. хотя — перечитывать полезно и те, и другие :-)
Zope, например: svn.zope.org/
Отличная статья для новичков.
Единственно только предложение по реализации последнего метода: после того как вы проверили, что if (reports.Count == 0) можно или сразу сделать return 0; либо обход списка с отчетами поместить в тело else.
Как считаете?
Тут уже на любителя, но я бы не стал делать дополнительную точку выхода из функции и писать там константу 0. Кол-во отчетов определяется свойством reports.Count, вот пусть только он и знает про эту цифру.
хотя, в принципе, код ведь ещё не готов (нет ребования №2) — поэтому и рефакторить его ещё рано.
предложение отзываю: цикл «тест-код-тест-код» ещё не закончен, чтобы приступать к последнему этапу.
Точно!
по поводу реализации последнего требования, можно я не кодом а словами? (практики программирования на c# мало, поэтому боюсь банально наделать ошибок. да и тут важен смысл, а не реализация)

тест будет выглядеть так:

билдер и сендер моки
мок билдера по CreateRegularReports() возвращает список с X = «целое от» (rand() * 100) число отчётов

reporter.SendReports();

проверяем, что send был вызван X раз
проверяем, что sendToAuditors был вызван (int)(X / 2) раз

реализация тестируемого метода усложнилась бы добавлением локального счётчика i
+ в теле foreach добавили бы if (i % 2) { reportSender.SendToAuditors(report); }
Да, отлично!
А этот псевдо-язык мне даже нравится ;)
Рекомендую книгу по теме: Roy Osherove — The Art of Unit Testing with Examples in .NET. Книга относительно свежая (июнь 2009) и как раз для начинающих в юнит-тестировании. Все изложено доступным и понятным языком даже мне, php-программисту :)
Для тестирования работы приложений с базами данных пишутся точно такие же тесты. Обычно перед каждым тестом тестовая база данных возвращается к исходному эталонному состоянию и делаются соответствующие запросы. Результаты сравниваются с эталонными.

Очень много ошибок, которые можно устранить тестированием, содержатся в коде для работы с базой данных.
А тесты пишутся до SQL-кода или после?
Дело вкуса, ИМХО. Я сначала мокаю middlelayer (объект, через который общаюсь с базой данных), пишу все тест кейсы. Затем, после того как тесты проходятся, моки с middlelayer снимаются и делается база, удовлетворяющая этим тестам. При таком подходе удобно параллелить разработку — имея тесты, один программист делает удовлетворяющую им логику, а второй — удовлетворяющую им базу. Соответственно, первый убирает моки для логики, второй убирает моки для базы.
А при разработке приложения используется какая-то ORM?
Если используется ORM, то ее объекты и результаты работы всегда можно заменить mock/stub-объектами. Для целей тестирования.
Зависит от проекта. Где-то это просто C++ класс который по вызову метода делает SQL запрос. А где-то это сложный C# код, который автоматически делает ORM и сам преобразует конструкции вида root.projects[ «фигня» ].users[ «вася» ].age в соответствующий SQL запрос. Проектов, где SQL запросы были бы прямо в коде лично я не видел ^_^.
Лучший способ — «отделение котлет от мух». То есть, каждый уровень: BLL, DAL, View, итд — тестировать и создавать отдельно. Это приводит нас к размышлениям о правильной архитектуре и в свою очередь обязательно приведет к DDD (Domain Driven Design).
Про Arrange-Act-Assert (AAA): при практическом написании теста лучше всё-таки начинать с Assert. Тогда тесты помогут вам определить архитектуру — в данном случае вы начнете с того, что будете думать, как клиентский код будет использовать метод.
После написания Assert, надо создать сам метод (если его нет) и так далее, то есть цепочка ААА разворачивается с конца.
Да, спасибо за уточнение. Именно так я и пишу.
Я обычно разворачиваю таким образом. Assert — Arrange — Act.
Что хочу получить — Что для этого надо иметь — Что для этого надо сделать.
На самом деле, третий тест проходим, когда закомментируем строку:
reportSender.Verify(m => m.Send(It.IsAny()), Times.Never());
Да, проходит, возможно это даже лишняя проверка, что ни один обычный отчет не был отправлен.
Неа, тест действительно не срабатывает, так как SpecialReport явственно наследуется от Report и поэтому одна отправка Report происходит — администратору. ;-) (P.S. Я пользовался Moq 4.0.812.4 версии)
Скажите, а почему при написании юнит-тестов никто не делает также тесты производительности? Например, сгененрировать миллион отчетов за секунду? Сгенерировать 10-мегабайтный отчет за секунду?

Из-за такого подхода чертовы индусы пишут код как попало, лишь бы тесты проходил, что мы и видим на примере многих програмных продуктов.
Очень просто, потому что они не для этого. Нагрузочное тестирование делается не модульными тестами.
А чем пример в книге Бека с разными валютами не подходит для разъяснения основ TDD? На мой взгляд он ничуть не сложнее. Написан, правда на Java, но это же не принципиально).

Очень здорово, что вы рассмотрели вопрос о тестировании приватных методов: он пожалуй самый часто задаваемый и является для меня, например, лакмусовой бумажкой понимания человеком TDD.
Пример с валютой очень даже ничего. Реализовать же можно всё, что угодно, правда? Я просто выбрал отсылку отчётов, как наиболее распространенное задание. Такой вот субъективный выбор :)
А как четвёртое требование («вывести в консоль») проверяется первым тестом, который проверяет результат вызова какого-то метода на равенство двум?
Тест проверяет не сам вызов, а возврат правильного количества отправленных отчетов.
Предполагается, что из функции консольного приложения Main будет сделан вызов:
var reportCount = reporter.SendReports()
Console.WriteLine("Отправлено {0}", reportCount);


* This source code was highlighted with Source Code Highlighter.
Ага, не проверяется… грустно.

Ок, ещё вопрос — как значение 2 соответствует требованиям первой («отправлять отчёт»), второй («иногда отправлять отчёт дважды») — и треьей («отправлять иногда не отчёт, а уведомление о его отсутствии») подзадач?
а можно пояснить подробнее про:
Вопрос: Как протестировать приватные методы?
Если вы дочитали до этого момента, то уже понимаете, что раз сначала пишутся тесты, а уже потом код, значит весь код внутри класса будет по-умолчанию протестирован.

я честно говоря никак не могу въехать…
Если мы пишем сначала тест, а потом добавляем код, который делает этот тест рабочим (зеленым), то не может появится кода (никакого, даже приватного внутри класса) без теста на него.

Другими словами, откуда берутся приватные методы? Сначала ведь их нет, как например в моем примере. Но вот если мы возьмем и вынесем условие reports.Count == 0 в приватный метод IsReportListEmpty()? Функциональность системы не измениться, при этом все тесты буду проходить, как и до создания метода IsReportListEmpty. При этом приватный метод будет протестирован.
Протестирован будет не метод, а то, что в него положили. И оно наверняка перестанет быть протестированным через пару-тройку итераций рефакторинга, если тот его затронет.
Мда, даже не знаю как еще сказать. Может кто поможет? :)

Просто пару наводящих вопросов. Когда рефакторите вы добавляете или удаляете функциональность? Проходят ли тесты после рефакторинга? И наконец, участвует ли код приватного метода при проходе теста?
При чистом рефакторинге — ни то ни другое. А по жизни — всяко бывает… Тесты, видимо, проходят в итоге, но это ведь уже априори тесты не этого приватного метода, а чего-то публичного, более верхнего уровня. А там уже возможны эффекты типа интерференции или взаимопогашения ошибок, сокращение вариантов использования и т.п…

Хотя, может быть это и мелочи, которыми действительно в реале допустимо пренебрегать. Не знаю, концепцией TDD пока не проникся.
Вот тут-то на помощь и присохит code coverage :)
по определению — рефакторинг это модификация реализации без модификации интерфейсов и поведения. поэтому рефакторинг никак не может повлиять на работоспособность тестов.
ошибки способны рождаться при любой правке, даже при рефакторинге.
совершенно верно — а тесты это гарант того, что поведение кода не изменилось.
т.о. дополню свою мысль: тесты обязаны помочь в утверждении того, что всё осталось так же, как и до рефакторингов.
Спасибо! Интересная статья.
А что скажете про тестирование GUI?
Я могу поделиться нашим опытом. Оба текущих проекта — это веб-сайты. Тестируем с помощью Selenium. Тесты также написаны на xUnit. Тесты GUI запускаются каждую ночь при сборке релиза с помощью TeamCity.
да но Selenium не годится для асинхронных веб-приложений
Приведите сценарий, когда у вас не получается применить Selenium
Кроме того, что автор сказал «есть проблемы с тестированием асинхронных приложений», я не увидел примера, который невозможно протестировать с помощью Selenium.

да но Selenium не годится для асинхронных веб-приложений

Я на всякий случай уточнил у тестировщиков, что мы успешно тестируем асинхронные запросы. У нас довольно много ajax-запросов.

Можете все-таки описать сценарий, когда невозможно написать тестовый сценарий с помощью Selenium?
Следует ли писать тесты к спагетти коду? Для того, чтобы подготовить проект для дальнейшего рефакторинга итерациями, но при этом ничего не сломать.
Перед рефакторингом надо написать модульные тесты на все части системы, которые вы планируете изменять, но не более. Если код слишком запутан и есть много взаимосвязей с внешними источниками, то напишите интеграционные тесты.

По этому поводу есть хорошая книжка www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

Может быть вам поможет вебинар, где мы развлекались со сложным кодом одного из моих студентов blog.byndyu.ru/2011/12/refactoring-legacy-code.html
Спасибо. На плюс кармы не хватает )
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории