среда, 13 июля 2011 г.

понедельник, 6 июня 2011 г.

Стабить или не стабить репозитории?

Для тестировании кода, который использует репозитории, есть два основных подхода:

1. Стабить и мокать вызовы репозиториев
2. Использовать реальные реализации репозиториев (а значит работать с базой)

Какой из вариантов выбрать - большой вопрос и не меньший повод для холивара.

Возьмем для примера следующий сервис:

public class ParametersService : IParametersService
{
private readonly IParametersRepository _parametersRepository;

public ParametersService(IParametersRepository parametersRepository)
{
_parametersRepository = parametersRepository;
}

public Parameter GetParameterOrAddNew(string parameterName)
{
var parameter = _parametersRepository.GetParameterOrDefault(parameterName);
if (parameter == null)
parameter = _parametersRepository.Add(parameterName);
return parameter;
}
}


А теперь напишем тест (возьмем для примера NUnit и RhinoMocks) для первого:


private IParametersService _parametersService;

public void SetUp(IParametersRepository parametersRepository)
{
_parametersService = new ParametersService(parametersRepository);
}

[Test]
public void GetParameterOrDefault_NoParameter_ParameterIsAdded()
{
//создаем Stub + Mock
var parametersRepository = MocksRepository.GenerateMock<IParametersRepository>();
//Эмулируем отсутствие параметра
parametersRepository.Stub(pr => pr.GetParameterOrDefault("testParameterName")).Return(null);

SetUp(parametersRepository);

var parameter = _parametersService.GetParameterOrAddNew("testParameterName");

//Проверяем, что метод Add был вызван
parametersRepository.AssertWasCalled(pr => pr.Add("testParameterName"));
}

и второго:


private IParametersService _parametersService = new ParametersService(new ParametersRepository);

[Test]
public void GetParameterOrDefault_NoParameter_ParameterIsAdded()
{
var parameter = _parametersRepository.GetParameterOrDefault("testParameterName");
//Проверяем, что такого параметра нет
Assert.IsNull(parameter);

var parameter = _parametersService.GetParameterOrAddNew("testParameterName");

Assert.IsNotNull(parameter);
Assert.AreEqual("testParameterName", parameter.Name);
}


Какую из реализаций выбрать?

Первая реализация более "чистая": мы тестируем непосредственно логику сервиса, не отвлекаясь на сторонние зависимости - классический unit-тест.

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

В .NET распространены два подхода к реализации паттерна репозиторий:
1 (классический). Инкапсуляция запроса в рамках метода репозитория. Методы репозитория возвращают сущность/коллекцию сущностей.
2. Легковесный репозиторий, который возвращает IQueryable, который, в свою очередь, уже вне репозитория обрастает фильтрами, сортировками, пейджингом и т. д. посредством паттерна Specification

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

Вторая же реализация больше подходит ко второму подходу к тестированию. Во-первых, раз мы возвращаем IQueryable, то находимся в зоне действия не LINQ To Objects, а LINQ To Entities, LINQ To NHibernate и т. д. Если же мы застабим репозиторий, то будем работать с LINQ To Objects, а значит наш код будет тестировать не то поведение, которое будет в продакшене. Во-вторых, ввиду того, что репозиторий не возвращает конечный результат выполнения запроса (как в первой реализации), такую реализацию репозитория не очень удобно стабить.

Нельзя не отметить, что производительность второго подхода гораздо ниже. Но давайте не будем забывать, что это тесты, а не продакшен. Говоря о производительности, мы должны ответить всего на один вопрос: достаточно ли высока производительность для того, чтобы не тормозить работу программиста при прогонке тестов? Вопреки распространенному мнению, зачастую ответ на этот вопрос будет положительным. Ведь локально мы прогоняем тесты лишь для модуля/модулей, над которым непосредственно в данный момент работаем. А сделать так, чтобы тесты всех модулей не тормозили на build-сервере, - задача решаемая (ведь build-сервер - это мощный сервер с современным железом, а не старый списанный десктоп, да?). Если производительность второго подхода вас не устраивает, но использовать первый подход не хочется, можно попробовать использовать in-memory базы данных: они практически выравнивают производительность двух подходов.

Производительность - не единственная проблема второго подхода: необходимо решить вопрос очистки базы данных после каждого теста. Для этого можно и накатывать после каждого теста базу (медленно решение), и откатываться к некоторому snapshot'у (работает быстрее, но поддерживается не всеми СУБД), и использовать транзакций без их последующего коммита. Я предпочитаю последний вариант за его универсальность и удовлетворяющую моим требованиям произвидительность. К тому же, благодаря TransactionScope реализовать этот подход очень просто. Правда, как я отмечал в своей недавней статье про TransactionScope, этот подход не работает, если в коде используется TransactionScope в режиме RequiresNew или Suppress. Заинтересовавшимся вопросом восстановления состояния базы данных, рекомендую серию постов на английском языке: раз, два, три

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

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

PP. S. Добавил кросспост на Хабр: http://habrahabr.ru/blogs/net/120755/

четверг, 2 июня 2011 г.

Опишите TFS одним предложением

На работе переходим на TFS по не зависящим от нас причинам. Я пока еще формирую свое мнение насчет TFS, но вот натолкнулся на статью, в которой был очень интересный комментарий:

It is like someone took the 5 worst tools they could find for source control (VSS excluded, of course,) testing, issue tracking, automated build and code analysis and put them all in one product

А как бы вы описали TFS?

воскресенье, 29 мая 2011 г.

Впечатления от конференции DevCon

На этой неделе проходила, пожалуй, самая крупная Microsoft-oriented российская конференция - DevCon.

Два дня - четверг и пятница - получились для меня очень насыщенными. Четверг начался с 4-х часового полета из Томска в Москву. Затем от Внуково мы с коллегой еще несколько часов добирались на такси до дома отдыха, где проходила конференция. И лишь благодаря часовым поясам (-3) успели к началу.

Организаторы (Microsoft Россия) постарались на славу. Было все: приглашенные звезды из Америки, отличные выступления, концерт вечером первого дня, отличное проживание и питание и т. д. Вообще программа была очень насыщенной и вечером первого дня (особенно на фоне перелета, переезда, да еще и болезни) голова просто раскалывалась.

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

Например, был показан CSS3 тест, который в IE10 Preview 1 работал быстрее, чем в текущей версии Google Chrome (в котором, вероятно, была отключена GPU Acceleration). Непонятно, зачем такие дешевые приемчики обыгрывать на профессиональной аудитории.

Или еще одна сценка. Вызывают на сцену парня. Он в течение двух минут рассказывает, что их компания занимается разработкой игр, причем в IE 9 их игры работают в 4 раза быстрее, чем в Google Chrome - и уходит. Опять какая-то желтизна. Я бы понял, если бы это был пресс-релиз для журналистов, но на профессиональной конференции разработчиков подобные вещи вызывают недоумение.

К счастью все эти бестолковые выступления уместились в первые два пленарных заседания. К сожалению, это были самые длинные заседания общей длительностью в 3 часа. И если бы не ноутбук и желание дописать код, который я не закончил в самолете, я бы, наверное, уснул, как и несколько людей рядом со мной в зале.

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

Правда, были и выступления, которые разочаровали. В основном причина тому была низкий уровень сложности. Например, придя на выступление о системе контроля версий в TFS (мы как раз на работе переходим на TFS, поэтому это было кстати), пришлось в течение часа слушать об элементарных операциях, которых одинаковы во всех нераспределенных системах контроля версий. Наверное, стоило рядом с каждым выступлением приписывать уровень сложности, чтобы избежать таких недоразумений. Хотя, на мой взгляд, подобных выступлений (а их было несколько) на конференции профессиональных разработчиков не должно быть в принципе.

Из интересного еще были стенды разных компаний. Из присутствовавших стендов меня заинтересовал лишь стенд компании DevExpress, но зато я смог вдоволь наговориться с ребятами из этой чудной (ударение поставьте на свой вкус :) ) компании.

Обратно добирался до Внуково на общественном транспорте с тремя пересадками. В Томске в те редкие случаи, когда мне приходится пользоваться общественным транспортом, все просто: я сажусь и доезжаю до нужного места (за 6 лет проживания в Томске ни разу не приходилось ездить с пересадками). На этом контрасте мой топографический кретинизм не мог нарадоваться московским приключениям.

В целом, я остался доволен, а негативные моменты спишем на мою вредность и придирчивость.

По результатам конференции, вероятность того, что после окончания учебы (защищаюсь через 2 недели) я уеду из Томска, повысилась до 90 процентов.