TDD с использованием SharpDevelop на C#
Вторник, 8 мая 2007 (16:31:19)
В этой статье вашему вниманию предлагается курс молодого бойца, который позволит на простых примерах ознакомиться с принципами разработки основанной на тестировании. Нет разницы какой язык программирования вы будете использовать, и какую среду разработки выберете.
Я выбрал бесплатную IDE для .NET SharpDevelop лишь потому, что кроме интегрированной поддержки Subversion и NDoc, он включает в себя также интегрированную поддержку NUnit и NCover, а так же Microsoft FXCop.
Немного теории
Смысл разработки основанной на тестировании (TDD, test-driven development) состоит в том, чтобы сформулировать требования к будущему коду при помощи набора тестов. Набор тестов для отдельно взятого фрагмента кода должен быть написан раньше, чем сам код. Рекомендую для разнообразия также ознакомиться с отличным примером использования TTD на языке Perl.
Постановка задачи
Для начала сформулируем простую задачу. Допустим, у нас имеется класс Email такого вида:
{
private string email;
public string Value
{
get {return email;}
set {email = value;}
}
}
Вам необходимо написать для него функцию isValid(), которая проверяет правильность адреса электронной почты, и в зависимости от результата проверки, возвращает true или false.
Итерация №1: Болванка
Напишем тест, который будет проверять работу этой функции:
public void ValidEmailTest()
{
Email email = new Email();
email.Value = "s_tretyak-1979@yandex.ru";
Assert.AreEqual(true,email.isValid());
}
Этот тест проверяет возвращает ли функция isValid() логическую истину, если объект класса Email содержит правильно сформированный адрес s_tretyak-1979@yandex.ru. Естественно, тест не только не будет пройден, но и проект не будет собран, потому что функция isValid() отсутствует в классе Email. Добавим эту функцию в исходник класса, таким образом, чтобы она удовлетворяла условиям тестового случая:
{
return true;
}
Теперь, если вы запустите тестирование, то увидите, что случай ValidEmailTest() будет пройден.
Итерация №2: Вечерний Дели
Пришло время проверить заведомо неправильный адрес электронной почты. Создадим тестовый случай в котором будет отражено это условие:
public void InvalidEmailTest()
{
Email email = new Email();
email.Value = "v_petrov-1969.yandex.ru";
Assert.AreEqual(false,email.isValid());
}
Функция isValid() должна вернуть логическую ложь, поскольку адрес электронной почты сформирован неправильно. В данном случае, в нём отсутствует @. Естественно, этот тест будет провален:
expected: <False> but was: <True>
На этом этапе мы должны модифицировать isValid() таким образом, чтобы она удовлетворяла тестовому случаю, описанному в InvalidEmailTest(). Собственно говоря, поэтому эта техника называется TTD. Решаем задачу «в лоб»:
{
if(email.LastIndexOf('@') != -1)
return true;
else return false;
}
Итерация №3: Оно шевелится!
Два теста пройдены, и мы получили некрасивый, но вполне работоспособный код в духе индийского экстремального программирования. Однако, очевидно, что если адрес будет содержать, например, два знака @, то функция isValid() вернёт неверное значение. Для проверки создадим третий тестовый случай:
public void InvalidEmailDoubleAtTest()
{
Email email = new Email();
email.Value = "i_ivanov-1985@li@ru";
Assert.AreEqual(false,email.isValid());
}
Который будет успешно провален, и натолкнёт нас на мысль об использовании регулярных выражений. Перепишем isValid() следующим образом:
{
Regex myReg = new Regex("^[\\w-\\.]+@\\w+\\.\\w+$");
return myReg.IsMatch(email);
}
Вместо заключения
По-научному наши телодвижения с функцией isValid() называются «рефакторинг». Смысл в этом такой: как бы мы не изменяли реализацию функции, тесты должны работать. Естественно, нет предела совершенству, и мы можем написать тестовый случай в котором будет ещё проверяться принадлежит ли домен хоста к допустимому TLD и т.п.
Также, если мы запустим тесты с проверкой покрытия кода,

мы увидим те места в нашем коде, которые не покрыты тестами:

а так же цифры, которые показывают как покрыт код тестами:

Это и неудивительно, ведь в наших тестовых примерах нигде не используется метод get свойства Value, возможно его покроют в других тестах, а возможно он не будет использован совсем. В таком случае его можно будет смело удалять из кода.