TDD

индекс
107,68

TDD для начинающих. Ответы на популярные вопросы

Исходники проекта написанного с помощью TDD. Visual Studio 2008/C#
Для написания тестов использована библиотека xUnit, для создания mock-объектов – Moq.




На очередном собеседовании, спрашивая о TDD, я пришел к выводу, что даже основные идеи разработки через тесты не поняты большинством разработчиков. Я считаю, что незнание этой темы – большое упущение для любого программиста.

Мне задают много вопросов про TDD. Из этих вопрсов я выбрал ключевые и написал на них ответы. Сами вопросы вы можете найти в тексте, они выделены курсивом.

В качестве отправной точки мы будем решать бизнес-задачу:

Задача состоит из нескольких подзадач: 1) написать консольное приложение, которое отправляет отчеты. 2) Каждый второй сформированный отчет надо отправлять ещё и аудиторам. 3) Если ни одного отчета не сформировано, то отправляем сообщение руководству о том, что отчетов нет. 4) После отправки всех отчётов, нужно вывести в консоль количество отправленных.

Вопрос: С чего начать писать код при TDD?

Начинаем с того, что будем решать одну небольшую задачу. Мы будем описывать бизнес-требование в коде с помощью теста.


Итак, я создал новый проект консольного приложения (финальный код можно скачать из SVN). Сейчас в нём нет ни одной строчки кода. Надо придумать, как вообще будет работать моё приложение. Внимание, начало! Создаем тест, который описывает 4-ое требование:

public class ReporterTests
{
  [Fact]
  public void ReturnNumberOfSentReports()
  {
    var reporter = new Reporter();

    var reportCount = reporter.SendReports();

    Assert.Equal(2, reportCount);
  }
}


* This source code was highlighted with Source Code Highlighter.


Класс Assert проверяет равно ли количество отосланных отчетов 2. Тест запускается консольной утилитой xUnit, либо каким-нибудь плагином к Visual Studio.

Только что мы спроектировали API нашего приложения. Мы будем использовать объект Reporter с функцией SendReports. Функция SendReports возвращает количество отправленных отчетов, это показывает тест с помощью утверждения Assert.Equal. Если переменная reportCount не будет равена 2, то тест не пройдет.

На этом первый этап проектирования закончился, переходим к кодированию. Напишем минимум кода, чтобы этот тест сработал.

Вопрос: Сначала нам надо написать много тестов, а потом исправлять их один за другим?
Нет, идем последовательно. Не будем распылять свою усилия. В каждый момент времени только один неработающий тест. Напишем код, который этот тест починит и можем писать следующий тест.


В нашем проекте на данный момент есть только один класс ReporterTests. Пора создать тестируемый класс Reporter. Добавляем в проект объект Reporter и создаем у него пустую функцию SendReports. Для того, чтобы тест прошёл, функция SendReports должна вернуть цифру 2. Пока не понятно, как задать начальные условия в объекте Reporter, чтобы функция SendReports вернула цифру 2.

Возвращаемся к проектированию. Я думаю, что у меня будет отдельный класс для создания отчётов, и класс для отправки отчётов. Сам объект Reporter будет управлять логикой взаимодействия этих классов. Назовем первый объект IReportBuilder, а второй – IReportSender. Попроектировали, пора написать код:

[Fact]
public void ReturnNumberOfSentReports()
{
  IReportBuilder reportBuilder;
  IReportSender reportSender;

  var reporter = new Reporter(reportBuilder, reportSender);

  var reportCount = reporter.SendReports();

  Assert.Equal(2, reportCount);
}


* This source code was highlighted with Source Code Highlighter.


Вопрос: есть ли правила для именования тестовых методов?
Да, есть. Желательно, чтобы название тестового метода показывало, что проверяет тест и какого результата мы ожидаем. В данном случае название говорит нам: «Возвращается количество отправленных отчётов».


Как будут работать классы, реализующие эти интерфейсы, сейчас не имеет значения. Главное, что мы можем сформировать IReportBuilder'ом все отчёты и отправить их с помощью IReportSender'а.

Вопрос: почему стоит использовать интерфейсы IReportBuilder и IReportSender, а не создать конкретные классы?
Реализовать объект для создания отчётов и объект для отправки отчётов можно по-разному. Сейчас удобнее скрыть будущие реализации этих классов за интерфейсами.

Вопрос: Как задать поведение объектов, с которыми взаимодействует наш тестируемый класс?
Вместо реальных объектов, с которыми взаимодействует наш тестируемый класс удобнее всего использовать заглушки или mock-объекты. В текущем приложении мы будем создавать mock-объекты с помощью библиотеки Moq.


[Fact]
public void ReturnNumberOfSentReports()
{
  var reportBuilder = new Mock<IReportBuilder>();
  var reportSender = new Mock<IReportSender>();

  // задаем поведение для интерфейса IReportBuilder
  // Здесь говорится: "При вызове функции CreateReports вернуть List<Report> состоящий из 2х объектов"
  reportBuilder.Setup(m => m.CreateRegularReports())
    .Returns(new List<Report> {new Report(), new Report()});

  var reporter = new Reporter(reportBuilder.Object, reportSender.Object);

  var reportCount = reporter.SendReports();

  Assert.Equal(2, reportCount);
}


* This source code was highlighted with Source Code Highlighter.


Запускаем тест – он не проходит, потому что мы не реализовали функцию SendReports. Программируем самую простую из возможных реализаций:

public class Reporter
{
  private readonly IReportBuilder reportBuilder;
  private readonly IReportSender reportSender;

  public Reporter(IReportBuilder reportBuilder, IReportSender reportSender)
  {
    this.reportBuilder = reportBuilder;
    this.reportSender = reportSender;
  }

  public int SendReports()
  {
    return reportBuilder.CreateRegularReports().Count;
  }
}


* This source code was highlighted with Source Code Highlighter.


Запускаем тест и он проходит. Мы реализовали 4-ое требование. При этом записали его в виде теста. Таким образом, мы составляем документацию нашей системы. Как показала практика – эта документация самая актуальная в любой момент времени и никогда не устаревает. Идем дальше.

Вопрос: Есть ли стандартный шаблон для написания теста?
Да. Он называется Arrange-Act-Assert (AAA). Т.е. тест состоит из трех частей. Arrange (Устанавливаем) – производим настройку входных данных для теста. Act (Действуем) – выполняем действие, результаты которого тестируем. Assert (Проверяем) – проверяем результаты выполнения. Я подпишу соответствующие этапы в следующем тесте.


Теперь займёмся первым требованием – отправлением отчётов. Тест будет проверять, что все созданные отчёты отправлены:

[Fact]
public void SendAllReports()
{
  // arrange
  var reportBuilder = new Mock<IReportBuilder>();
  var reportSender = new Mock<IReportSender>();

  reportBuilder.Setup(m => m.CreateRegularReports())
    .Returns(new List<Report> {new Report(), new Report()});

  var reporter = new Reporter(reportBuilder.Object, reportSender.Object);

  // act
  reporter.SendReports();

  // assert
  reportSender.Verify(m => m.Send(It.IsAny<Report>()), Times.Exactly(2));
}


* This source code was highlighted with Source Code Highlighter.


Вопрос: Надо ли писать тесты для всех объектов приложения в одном тестовом классе?
Очень нежелательно. В этом случае тестовый класс разрастется до огромных размеров. Лучше всего на каждый тестируемый класс создать отдельный файл с тестами.


Запускаем тест, он не проходит, потому что мы не реализовали отправку отчётов в функции SendReports. На этом, как обычно мы проектировать заканчиваем и переходим к кодированию:

public int SendReports()
{
  IList<Report> reports = reportBuilder.CreateRegularReports();

  foreach (Report report in reports)
  {
    reportSender.Send(report);
  }

  return reports.Count;
}


* This source code was highlighted with Source Code Highlighter.


Запускаем тесты – оба проходят. Мы реализовали ещё одно бизнес-требование. К тому же, запустив оба теста мы убедились, что не сломали функциональность, которую делали 5 минут назад.

Вопрос: Как часто надо запускать все тесты?
Чем чаще, тем лучше. Любое изменение в коде может неожиданно для вас отразится на других частях системы. Особенно, если этот код писали не Вы. В идеале все тесты должны запускаться автоматически системой интеграции (Continuous Integration) при каждой сборке проекта.

Вопрос: Как протестировать приватные методы?
Если вы дочитали до этого момента, то уже понимаете, что раз сначала пишутся тесты, а уже потом код, значит весь код внутри класса будет по-умолчанию протестирован.


Пора подумать о том, как реализовывать третье требование. С чего начнем? Нарисуем UML-диаграммы или просто помедитируем сидя в кресле? Начнём с теста! Запишем 3-е бизнес-требование в коде:

[Fact]
public void SendSpecialReportToAdministratorIfNoReportsCreated()
{
  var reportBuilder = new Mock<IReportBuilder>();
  var reportSender = new Mock<IReportSender>();

  reportBuilder.Setup(m => m.CreateRegularReports()).Returns(new List<Report>());
  reportBuilder.Setup(m => m.CreateSpecialReport()).Returns(new SpecialReport());

  var reporter = new Reporter(reportBuilder.Object, reportSender.Object);

  reporter.SendReports();

  reportSender.Verify(m => m.Send(It.IsAny<Report>()), Times.Never());
  reportSender.Verify(m => m.Send(It.IsAny<SpecialReport>()), Times.Once());
}


* This source code was highlighted with Source Code Highlighter.


Запускаем и убеждаемся, что тест не проходит. Теперь наши усилия направлены на починку этого теста. Здесь проектирование как обычно заканчивается и мы возвращаемся к программированию:

public int SendReports()
{
  IList<Report> reports = reportBuilder.CreateRegularReports();

  if (reports.Count == 0)
  {
    reportSender.Send(reportBuilder.CreateSpecialReport());
  }

  foreach (Report report in reports)
  {
    reportSender.Send(report);
  }

  return reports.Count;
}


* This source code was highlighted with Source Code Highlighter.


Запускаем тесты – все 3 теста проходят. Мы реализовали новую функции и не сломали старые. Это не может не радовать!

Вопрос: Как узнать какой код уже протестирован?
Покрытие кода тестами можно проверить с помощью различных утилит. Для начала могу посоветовать PartCover.

Вопрос: Надо ли стремиться покрыть код тестами на 100%?
Нет. Это потебует слишком больших усилий на создание таких тестов и ещё больше на их поддержку. Нормальное прокрытие колеблется от 50 до 90%. Т.е. должна быть покрыта вся бизнес-логика без обращений к базе данных, внешним сервисам и файловой системе.


Второе требование я предлагаю реализовать вам самим и поделиться в комментариях финальной частью функции SendReports и вашего теста. Вы ведь сначала напишете тест, так?

Вопрос: Как же мне протестировать взаимодействие с базой данных, работу с SMTP-сервером или файловой системой?
Действительно, тестировать это нужно. Но это делается не модульными тестами. Потому что модульные тесты должны проходить быстро и не зависеть от внешних источников. Иначе вы будете запускать их раз в неделю. Более подробно об этом написано в статье «Эффективный модульный тест».

Вопрос: Когда я могу применять TDD?
TDD можно применять для создания любого приложения. Очень удобно его применять, если вы изучаете возможности новой библиотеки или языка программирования. Особых границ в применении нет. Возможны неудобства с тестированием многопоточных и других специфических приложений.


Заключение



Я желаю каждому разработчику попробовать эту практику. После этого можно решить, насколько TDD подходит для Вас лично и для проекта в целом.
+28
2 февраля 2010, 19:23
89

комментарии (62)

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

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

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

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

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

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

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


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

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


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

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

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

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

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

reporter.SendReports();

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

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

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

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

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


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

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

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

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

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

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

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

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

Можете все-таки описать сценарий, когда невозможно написать тестовый сценарий с помощью Selenium?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.