Юнит-тестирование БД-зависимых классов
Написание автоматизированных тестов для приложений работающих с БД часто становится источником многочисленных споров. Основная причина этих споров — подходы к тестированию. Можно выделить 3 основных подхода, которые при этом применяются:
- Абстрагирование от БД. Подход предполагает, что взаимодействия с какими бы то ни было БД в тестах не происходит. Для этого все обращения к БД оборачивают в вызовы, например, IRepository и в тестах эти обращения замещаются с помощью mock-фреймворков (Rhino.Mocks, Moq, etc.). Из плюсов подхода можно выделить популярность паттерна репозиторий, что ускоряет знакомство с методикой. Из минусов — приходится замещать все БД-обращения (иногда их бывает довольно много), а также тесты жестко связываются с интерфесом репозиториев (в случае изменения интерфесов тесты придется также изменять)
- Работа поверх тестовой БД. В тестах используется база, «похожая» на продакшн-базу, но с некоторыми тестовыми данными. Тесты, таким образом, работают в окружении, максимально близком к реальной среде, что, без сомнения, является большим плюсом. Из минусов можно выделить более низкую скорость выполнения тестов и проблемы поддержания тестовой БД в эталонном состоянии.
- Работа поверх временных БД. В тестах используются временные базы, чаще всего, способные хранить структуру в памяти (SQLite) или на локальном диске (SQL CE). Из плюсов — высокая скорость работы и «приближенность» к реальному окружению (хоть и менее близкая, чем во втором пункте).
Стоит отметить и общую часть всех трех подходов в «простых» случаях: участки кода, которые не требуют «активного» доступа к БД, а лишь модифицируют сущности доменной модели, тестируются во всех подходах одинаково и взаимодействия с БД просто не требуют.
В своей работе я не использую репозитории, потому что в большинстве случаев они являются ненужной прослойкой, которая лишь усложняет доступ к данным.
Из других подходов, я стараюсь использовать последний, поскольку он наиболее лёгок и гибок в применении. Он особенно удобен при использовании ORM, поскольку в этом случае сам ORM обычно способен создать схему базы в соответствии с определенными маппингами и соглашениями.
Ниже я приведу пример базового тестового класса, при наследовании от которого я получаю временную sqlite базу со структурой, соответствующей текущему проекту, и 3 NHibernate-сессии открытые поверх этой БД. Несколько сессий я использую для удобства проверки коммитов: обычно настройку начальных данных я провожу в sessionArrange, в тестируемый код передается sessionAct и проверки осуществляются через sessionAssert, таким образом все «несохраненные» в тестируемом коде данные из sessionAssert будут просто не видны.
public class InMemoryDb : IDisposable
{
private static Configuration Configuration;
private static ISessionFactory SessionFactory;
protected ISession _sessionAssert;
protected ISession _sessionArrange;
protected ISession _sessionAct;
[SetUp]
public void _Setup()
{
Init();
}
public void Init()
{
if (Configuration == null)
{
var conf = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory)
.Mappings(m => m.AutoMappings.Add(AutomappingGenerator.CreateAutomappings));
//AutomappingGenerator.CreateAutomappings - статичная функция, возвращающая AutoPersistenceModel - конфигурацию NHibernate в текущем проекте.
SessionFactory = conf.BuildSessionFactory();
Configuration = conf.BuildConfiguration();
}
_sessionAct = SessionFactory.OpenSession();
_sessionArrange = SessionFactory.OpenSession(_sessionAct.Connection);
_sessionAssert = SessionFactory.OpenSession(_sessionAct.Connection);
_sessionAct.BeginTransaction();
_sessionArrange.BeginTransaction();
_sessionAssert.BeginTransaction();
var nullWriter = new StringWriter();
new SchemaExport(Configuration).Execute(false, true, false, _sessionAct.Connection, nullWriter);
}
public void Dispose()
{
_sessionAct.Dispose();
_sessionArrange.Dispose();
_sessionAssert.Dispose();
}
}
Второй подход я также использую в случаях, когда воспользоваться третьим не получается :) В этом случае проблему «очистки» БД я решаю путем выполнения всех операций внутри теста в рамках одной транзакции и отката этой транзакции по окончании теста. Базовый класс в этом случае выглядит совсем просто, но более специфично, поэтому, пожалуй, от выкладывания его я воздержусь :)
Проблема one-click publish и MvcBuildViews в asp.net MVC
При использовании one-click publish я как-то раз столкнулся с подобной ошибкой:
It is an error to use section registered as allowDefinition=’MachineToApplication’ beyond application level. This error can be caused by a virtual directory not being configured as an application in IIS.
Происходит она при попытке компиляции проекта после первого же паблиша (на удаленный сервер или даже в локальную папку).
Столкнувшись сегодня с ней во второй раз и осознав, что я не помню, «что это было» и как это лечится, решил написать заметку.
Read the rest of this entry »
Хитрость байндинга булевых полей в asp.net mvc
Сегодня мне вдруг стало любопытно, как происходит байндинг булевых полей в MVC.
Причину любопытства можно пояснить на примере:
Допустим, у нас есть модель:
public class TestModel {
public string Name {get;set;}
public bool IsDeleted {get;set;}
}
И есть две хтмл-формы для редактирования этой модели, в первой из которых присутствует флаг IsDeleted, а во второй — нет:
Форма 1:
@using (Html.BeginForm) {
@Html.TextboxFor(x => x.Name)
@Html.CheckboxFor(x => x.IsDeleted)
<input type="submit" value="OK" />
}
Форма 2:
@using (Html.BeginForm) {
@Html.TextboxFor(x => x.Name)
<input type="submit" value="OK" />
}
При этом серверный обработчик этой формы выглядит вполне типично:
public ActionResult UpdateModel(int id) {
var model = Db.Load<TestModel>(id);
TryUpdateModel(model);
return RedirectToAction();
}
Предположим, что в базе у TestModel IsDeleted == true. Что произойдет, если
- мы отсабмитим Форму1 со снятым флагом?
- мы отсабмитим Форму2, в которой флага IsDeleted просто нет?
Как и ожидалось, в обоих случаях MVC отработал отлично, и в случае 1 в базе у сущности TestModel поле IsDeleted стало false, а в случае 2 — осталась в true.
В чем же причина любопытства? Мне было любопытно, как же именно это работает.
В «классическом html» чекбоксы обычно представлены в виде <input name=’IsDeleted’ type=’checkbox’ />. А при сабмите формы на сервер это отправляется в виде:
- http://localhost/?name=zcx&IsDeleted=on — если флажок проставлен
- http://localhost/?name=zcx — если флажок не стоит
Как видно, в «классическом случае» ситуация отсутствия чекбокса в форме как такового и ситуация, когда он есть, но «галочка не стоит» абсолютно одинаковы, и выполнить корректный байндинг не представляется возможным.
Именно поэтому мне было интересно заглянуть «под капот» и узнать, как же это реализовано в mvc3.
Решение оказалось простым: на каждое булево поле mvc генерит такой html:
<input type="checkbox" name="IsDeleted" value="true" /><input type="hidden" name="IsDeleted" value="false" />
Вот и весь секрет :)
Возможно, это вполне стандартный прием у веб-программистов, но я о нём почему-то не знал, потому восполнить пробел было очень любопытно, и, уверен, этот приём мне еще не раз пригодится на практике.
Варианты реализации WebServices в .net — WCF, REST, OData, WebAPI и другие умные слова — Part I
Недавний релиз ASP.Net MVC4 Beta заставил меня внимательно посмотреть на новые возможности и без того замечательного фреймворка. Среди этих возможностей наиболее любопытной мне показалась Web API, которая предоставляет удобный интерфейс создания открытых API для сайтов/сервисов, для использования их из различных клиентов (javascript, мобильные приложения, b2b-сервисы, а то и вовсе desktop-клиенты :)).
Заодно я решил слегка обновить свои знания о вариантах создания веб-сервисов на .net в принципе, и вспомнить, что нам на сегодняшний день предлагает WCF. Оказалось, что нового весьма немало.
Read the rest of this entry »
Настройка Web-Deploy на IIS7
Эта заметка из разряда «напоминалок чтобы не забыть».
Установить web-deploy проще всего через Web Platform Installer (если уже установлен — запустить из меню Пуск->Web Platform Installer).
В Web Platform Installer поиском по слову «deploy» найти «Web Deploy 3.0″ или «Web Deployment Tool 2.1 blablabla».
Установить. Радоваться :)
В настройках one-click publish в VS прописать адрес сервера и имя сайта, в который будет идти деплой.
Обработчик web-deploy на стороне сервера будет работать по адресу http://xxxxxxx/MSDEPLOYAGENTSERVICE, на 80-м порту.
Деплоить на этот адрес смогут администраторы сервера. При получении ошибки 401 Unauthorized добавить ключ в реестр: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System (DWORD: LocalAccountTokenFilterPolicy = 1)
Это решит проблему, если пользователь, от имени которого идет деплой не «админ», но состоит в группе «Администраторы».
Настройка деплоя под неадминистративным пользователем требует доп. настроек прав доступа в IIS->Управление(Management)->Делегирование службы управления(Management Service Delegation). Требуется добавить правило «Развертывания приложений» для нужных пользователей. Детали хорошо описаны по ссылке.
В проблеме 401 Unauthorized может также помочь ссылка.
Плагин к VS для создания веток в TFS
Мои профессиональные обязанности связаны с разработкой CMS на ASP.Net. У нас уже сейчас более десятка клиентов, и все они время от времени хотят получать обновления :). Жестких релизов и «цикла поддержки» у нас нет, поэтому у каждого клиента может оказаться фактически произвольная версия.
Особенность такого цикла разработки всплывает в тех случаях, когда у клиента, последнее обновление которого было месяца 3-4 назад всплывает мелкий баг и клиент очень хочет его пофиксить. Однако обновиться до последней версии часто непросто, потому что система развивается очень быстро, и полноценное обновление скорее всего потребует комплексное обновление БД и 1С, что может быть довольно дорого по времени/усилиям/деньгам :)
Выход из этой ситуации довольно прост — при деплое сайта на каждого клиента необходимо создавать ветку, из которой при необходимости и проводить «горячие фиксы» клиента.
Здесь всплывает и чисто человеческий фактор, что «забыть» сделать ветку при деплое в спешке очень и очень легко, особенно если таких деплоев за день несколько :)
Дабы не страдать от подобной забывчивости я и решил автоматизировать процесс создания веток, а заодно и ознакомиться с возможностями расширения Visual Studio.
Read the rest of this entry »
T4MVCJS отрефакторен и выложен на codeplex
Недавно дошли руки до выкладывания T4MVCJS в opensource. Распространение исходников в зип-архиве показалось слегка устаревшей методикой и мы переехали на codeplex :)
Попутно было слегка отрефакторено использование T4MVC, вместо простой «копипасты» теперь используется оригинальный исходник с вырезанными из него строками, отвечающими за генерацию T4MVC-хэлперов. Таким образом легко и просто используется весь парсинг, осуществляемый T4MVC, и обновление до новых версий будет представлять куда меньше проблем (скопипастить файл, выкинуть 400 подряд идущих строк — вуа-ля :)).
Помимо эстетического удовлетворения это позволило с лёгкостью обрабатывать MVC Area (предыдущая версия, этого не умела, за репорт этого бага спасибо Брайану Бетти).
Заодно я задумался о проблеме существования двух экшенов с одинаковыми именами — в Javascript перегрузка функций, к сожалению, недоступна. В результате на свет появляются экшены Edit, Edit1, Edit2, etc. :) Если кто-нибудь предложит более адекватное решение проблемы — я бы с удовольствием его обсудил :)
P.S. в качестве системы контроля версий T4MVCJS используется Mercurial, так что при желании внести изменения — форкайте с удовольствием :)
P.P.S. На момент изначальной публикации поста ареи-таки не работали. Начиная с версии 1.0.10 всё ок.
T4MvcJs — строготипизированный яваскрипт-хелпер для URL
Не так давно я уже писал о решении проблемы «магических строк» в яваскрипте, примером таких строк могут служить url экшенов («/Home/Index?name=John&lastname=Doe»)
В разор-вьюшках проблему написания урлов «напрямую» можно решить с помощью T4Mvc: @Url.Action(MVC.Actions.Home.Index(«John», «Doe)). Проблема в том, что этот код — серверный, и написать что-то подобное в script.js — не получится.
В предыдущем посте я уже предлагал решение проблемы, однако(об этом я также писал) в нём была существенная недоработка. Новая версия T4MvcJs эти недоработки устраняет, и в результате мы, как и прежде, спокойно сможем писать в яваскрипт файлах что-то вроде: MvcActions.Home.Index(«John», «Doe»), и это будет полностью клиентский код.
Read the rest of this entry »
Nuget пакет для JsValidator’a
Наконец-то создан nuget-пакет для JsValidator‘a, о котором я не так давно писал.
Теперь интеграция проверки яваскрипта в любой проект займет минимум времени: Install-Package JsValidator в nuget-консоли — это всё, что нужно. Название пакета, как нетрудно догадаться — JsValidator.
Пакет делает всё то, что раньше приходилось делать руками: скачивает бинарники валидатора, создает тестовый конфиг, прописывает себя в post-build-events.
При удалении пакета всё, где пакет наследил, аккуратненько удаляется.
По факту окончания разработки JsValidator’a написан анонс проекта на Хабрахабре. Желающие приглашаются к обсуждению в любом из источников :)
Валидация JavaScript в ASP.Net MVC проекте — еще один велосипед или jsvalidator.codeplex.com
В предыдущей заметке я рассматривал существующие решения «псевдо-компиляции» яваскрипта и интеграции этих решений в asp.net проекты.
В итоге не найдя ничего идеального и решив, что собственный велосипед здесь не помешает, я и написал утилиту под названием jsvalidator. Найти её можно на codeplex, там же есть и краткое описание установки и конфигурирования. Данная заметка, по сути, будет переводом «официальной документации» собственного же сочинения :)
Если коротко, то jsvalidator это build-step с json-like конфигурационным файлом, проверяющая яваскрипт с помощью java-библиотеки google closure. Из этого следует и первое требование — для работы утилиты необходима установленная на компьютере java (с java.exe добавленным в системные пути (system PATH)).
После интеграции утилиты на каждый билд в вашей Visual Studio будет нечто вроде:
(в данном случае утилита сообщает нам о необъявленной переменной asd, по двойному клику на ошибке откроется js-файл на строчке с ошибкой, как мы и привыкли).
Read the rest of this entry »
