Показаны сообщения с ярлыком TDD. Показать все сообщения
Показаны сообщения с ярлыком TDD. Показать все сообщения

четверг, 11 марта 2010 г.

WatiN и ASP.NET integration tests

Не так давно пробовал библиотеку WatiN в работе, впечатления очень положительные.

На мой взгляд эту библиотеку очень выгодно отличает ее API. Он очень domain specific и его удобно использовать и читать.

Так же очень логично использовать так называемый "Page-based API", когда каждая страница в сценарии инкапсулирована в отдельный класс, унаследованный от класса WatiN.Core.Page. В этом классе описывается мэппинг контролов страницы на определенные свойста класса с помощью атрибутов, а так же реализуется логика взаимодействия со страницей в методах класса.

Реализованные классы страниц могут быть повторно использованы во множестве тестов с разными входными данными и с разными наборами страниц.
Таким образом, при чтении теста тестировщик или разработчик концентрируется не на полях ввода и кнопках, а на бизнес-логике и логике взаимодействия с веб приложением.

Классический API можно увидеть на главной странице сайта WatiN, я же приведу пример "Page-based API":
using (var ie = new IE("http://www.google.com"))
{
var page = ie.Page<GoogleSearchPage>();
page.SearchFor("WatiN");
}
...

[Page(UrlRegex="www.google.*")]
public class GoogleSearchPage : Page
{
[FindBy(Name="btnG")]
public Button SearchButton;

[FindBy(Name="q")]
public TextField SearchCriteria;

public void SearchFor(string searchCriteria)
{
this.SearchCriteria.TypeText(searchCriteria);
this.SearchButton.Click();
}
}

Надеюсь что весь приведенный код не нуждается в комментариях. Если вы его сразу же поняли, значит этот API можно считать действительно удачным.

Пример реального кода на WatiN:
browser = new IE(fedExHomePageUrl);
browser.Page<FedExLoginPage>().LogIn(login, password);
browser.GoTo(fedExSearchPageUrl);
browser.Page<FedExSearchPage>().Search(dateFrom, dateTo);
browser.Page<FedExSearchResultPage>().SaveAs(reportPath);
browser.Page<FedExDownloadPage>().Download(browser, reportPath);
...

В коде выше описан верхний уровень сценария взаимодействия с сайтом FedEx. Глядя на код можно получить четкое представление о том, что нужно сделать пользователю для того чтобы скачать некий репорт с сайта. При этом все ньюансы разметки страницы и ввода данных сгруппированы и изолированы :)


WatiN даже умеет взаимодействовать со скрытым окном браузера, что позволяет запускать integration тесты на локальной машине прямо во время работы.


Стоит отметить, что в работе со скрытым окном браузера есть несколько ньюансов:

1. Если понадобится сделать скриншот страницы, то окно браузера необходимо сделать видимым, но не активным, иначе вместо скриншота у нас будет снимок черного экрана. Окно можно делать не активным, чтобы оно не мешало работе. После снятия скриншота окно можно будет сразу же спрятать:
browser.ShowWindow(NativeMethods.WindowShowStyle.ShowNormalNoActivate);
browser.CaptureWebPageToFile(debugImagePath);
browser.ShowWindow(NativeMethods.WindowShowStyle.Hide);

2. Обработка диалоговых окон часто завершается неудачей без видимых на то причин. К сожалению у меня не было достаточно времени чтобы четко вычислить закономерность поведения, поэтому я проблему решил приведением окна браузера в видимый + активный режим. Понятно что побочным эффектом будет периодические появления диалоговых окон в самый неудачный для вас момент, но как правило таких ситуаций не так уж и много. Буду очень признателен, если кто-нибудь подскажет решение данной проблемы.

3. На моем копьютере WatiN отказывается работать в режиме Windows Service без доступа к рабочему столу, несмотря на то что на официальном сайте утверждается обратное.

...случайно заметил что в IE8 все вставки кода выглядят просто ужасно. Что интересно, FF и iPhone показывают правильно.

понедельник, 16 марта 2009 г.

Необычный подход к написанию юнит тестов

Обнаружил довольно необычный подход к написанию юнит тестов у автора вот этого поста:
Test Driven Design и Test First Development -- в чем разница?

Меня привлекло то, что тест класс представляет из себя наследник тестируемого класса.

Приведу пример классического и альтернативного подхода (пример из документации NUnit).

Тестируемый класс:
namespace bank
{
public class Account
{
private float balance;
public void Deposit(float amount)
{
balance+=amount;
}

public void Withdraw(float amount)
{
balance-=amount;
}

public void TransferFunds(Account destination, float amount)
{
}

public float Balance
{
get{ return balance;}
}
}
}
Тест, написаный при помощи "классического" подхода:
namespace bank
{
using NUnit.Framework;

[TestFixture]
public class AccountTest
{
[Test]
public void TransferFunds()
{
Account source = new Account();
source.Deposit(200.00F);
Account destination = new Account();
destination.Deposit(150.00F);

source.TransferFunds(destination, 100.00F);
Assert.AreEqual(250.00F, destination.Balance);
Assert.AreEqual(100.00F, source.Balance);

}
}
}
Тест, написанный с помощью альтернативного подхода с наследованием:
namespace bank
{
using NUnit.Framework;

[TestFixture]
public class AccountTest : Account
{
[Test]
public void TestTransferFunds()
{
this.Deposit(200.00F);
Account destination = new Account();
destination.Deposit(150.00F);

this.TransferFunds(destination, 100.00F);
Assert.AreEqual(250.00F, destination.Balance);
Assert.AreEqual(100.00F, this.Balance);
}
}
}

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


Лично у меня возникает вопрос. А стоит ли вообще так писать тесты?

У меня сходу возникли следующие мысли насчет альтернативного подхода:
+ Наследование позволяет протестировать protected методы
+ Я могу написать helper-методы в тест классе, который будет активно работать с protected методами и свойствами
- Наследование дает бОльшую связность между тестовым и тестируемым классом
- Наследование скорее всего завяжет мне руки в SetUp/TearDown методах
- Если класс sealed, то о подходе с наследованием можно забыть
- Когда я пишу классический тест-метод, то я получаю лаконичную документацию к своему коду в виде маленьких примеров
- Когда я пишу классический тест-метод, то я использую класс точно так же как это делает production-код
- У класса Account будет аж два клиента - production code and unit tests, что как правило будет положительно отражаться на его интерфейсе


Очень интересны ваши мнения на этот счет.

воскресенье, 9 ноября 2008 г.

XML в качестве DataSource для юнит тестов

Что-то в последнее время я вижу просто невообразимое обилие постов на тему TDD, что не может не радовать :)

Регулярно читая посты одного моего уважаемого коллеги по имени Mike Chaliy обнаружил заметку CSV у якості DataSource.

Вначале хотел оставить комментарий, но потом решил сделать это в виде отдельного поста.

Итак, Mike предпочитает CSV. Он достаточно подробно описал причины его решения, поэтому я решил немного пропиарить формат XML :)

Естественно что для табличных данных XML дает достаточно неслабый overhead. Для того чтобы заполнить файл нужно потратить дополнительное время чтобы описать XML-структуру для новых данных вместо того, чтобы просто их перечислять.

На мой взгляд, у CSV формата в качестве источника данных для юнит тестов так же есть несколько недостатков, которые я привожу ниже:

1. Изменение структур данных. Добавляется новое либо удаляется старое поле. Приходится менять CSV документ. Причем придется чуть-чуть поколдовать над загрузкой документа в тот же Excel для того чтобы представить его в табличном виде. После чего нужно отредактировать эту таблицу и опять сохранить в CSV.

В случае с XML можно обойтись банальным Find/Replace в текстовом редакторе. Хотя это конечно дело вкуса. В любом случае нужно помнить об этом, поскольку для кого-то это может быть проблемой.

2. Обработка ошибок чтения. В .NET достаточно бедные средства для того чтобы работать с CSV форматом. С другой стороны, связка XML-файл + XSD-схема + класс-контейнер полученный от XSD.exe + чтение файла с валидацией по схеме выглядит очень привлекательно.

Предположим другой разработчик, меняет функциональность подсистемы. Для этого он должен дополнить CSV-файл новыми данными. Он обновляет документ, и тут бац! Оказывается что он ошибся, поэтому документ не читается. Очень часто потребуется немного поднапрячь свой мозг чтобы понять что же там не так.

Если бы он обновлял XML документ он бы получил довольно внятное объяснение почему документ не может быть загружен.

3. Строгая типизация. Если использовать класс-контейнер, то сериализатор сам будет делать все приведения типов. В случае с CSV тебе придется этот код писать самому.

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


Таким образом, для меня выбор между CSV и XML практически эквивалентен с очень небольшим перевесом в пользу XML ;)