31 октября 2008

LINQ to SQL: обновление модели из базы данных

Если вы используете LINQ to SQL в паре с его дизайнером вы наверняка в курсе его неприятного ограничения - невозможно обновить модель из базы данных.

Как ни странно с того момента, как я последний раз изучал эту ситуацию - решение появилось:

Huagati DBML/EDMX Tools для Visual Studio 2008 / .net 3.5

Вот в кратце возможности:

  • LINQ to SQL
    • Стандартизация имен классов и членов (удаление префиксов tbl_, int_, str_, приведение регистра символов)
    • Обновление диаграмы дизайнера из базы данных
    • Обновление документации из базы данных
    • Сравнение диаграмы дизайнера и базы данных
  • ADO.NET Entity Framework
    • Стандартизация имен классов и членов (удаление префиксов tbl_, int_, str_, приведение регистра символов)
UPDATE: к сожалению, выяснилось, что данная утилита платная - будем искать лекарство

Unit Testing. Что это и чем это нам грозит?

Благодаря тлетворному влиянию прекрасного разработчика, моего коллеги, Никиты Говорова я начал изучать Best Practices. Первой из этих практик естественно стало модульное тестирование, к тому же в тот момент подоспел очередной preview ASP.NET MVC, а в месте с ним серия скринкастов MVC Storefront, с которыми я рекомендую ознакомиться, даже если вы не связаны с веб-разработкой.

Итак, модульное тестирование. Концепцию удобнее показать на примере.

Предположим, что перед нами стоит простая задача - написать целочисленный калькулятор. Как ни трудно догадаться основой его будет класс Calculator, который мы с вами и будем писать. Для удобства создадим интерфейс ICalculator, который покажет какие методы нам нужны:

public interface ICalculator
{
   int Add(int first, int second);
   int Substract(int first, int second);
   int Multiply(int first, int second);
   int Divide(int first, int second);
}

Далее нам нужно реализовать этот интерфейс в классе Calculator. Мы будем делать это поэтапно для каждого необходимого нам метода.

Этап 1: Написание теста

Да, как это парадоксально не звучит - сначала пишется тест, потом то, что тестируется. Это основное "неудобство" данной практики для новичков, так как очень сложно заставить писать тест еще до того, как написан сам код.

Для написания теста нам нужно добавить в наш Solution новый проект типа Test Project(в данной статье мы будем использовать встроенные тесты Visual Studio 2008).После его создания у нас появится класс UnitTest1 с атрибутом [TestClass], содержащий в себе метод TestMethod1 с атрибутом [TestMethod].

Для начала переименуем метод во что-нибудь более удобоваримое, например в Calculator_Add_MustAddFirstIntToSecondAndReturnTheResult(). Не пугайтесь такого длинного названия - вызывать напрямую этот метод вам нигде не придется, но это добавит ясность к тесту в том случае, когда их у вас в проекте станет несколько сотен. Основная идея - назвать метод так, чтобы по названию стало ясно, что он делает(тестирует).

Небольшая ремарка: Мой коллега Никита Говоров недавно предложил воспользоваться возможностью C# и писать название тестовых методов на русском языке. Это спорное с моей точки зрения решение, так как может вызвать труднонаходимые опечатки из-за переключения раскладки, но если у вас есть сложности с английским языком, то попробуйте - возможно это то, что вам нужно.

Теперь собственно напишем сам тест, который будет тестировать работу метода Add.

[TestMethod]
public void Calculator_Add_MustAddFirstIntToSecondAndReturnTheResult()
{
   Calculator calculator = new Calculator();

   int result = calculator.Add(1, 2);

   Assert.AreEqual(3, result);
}

Как видите - тут все просто. Мы создаем экземпляр класса Calculator, и вызываем у него метод Add, записывая результат. Последняя инструкция - собственно проверка равен ли первый аргумент(тот, результат, который мы ожидаем) второму(результат, который мы получили). Если это не так - метод Assert.AreEqual выбросит исключение. Тест считается пройденным(Passed), если не было выброшено ни одного исключения.

Этап 2: Провалим тест

На этом этапе мы должны убедится, что то что мы собираемся проверять - действительно это проверяет. Так как мы еще не реализовали сам метод Add запуск теста приведет к провалу(Failed), что нам и нужно. Запуск теста осуществляется кликом по телу метода и вызовом команды контекстного меню Run Tests.

Это может показаться излишним, но действительно довольно часто встречаются ситуации, когда тестовый проходит(Passed), хотя никаких условий этому не обеспечено. Например вы можете ошибиться и проверять вместо длины коллекции возвращенных объектов методом Assert.AreNotEqual(0, collection.Length) ее неравность null методом Assert.IsNotNull(collection). При количестве элементов равном нулю ваш тест пройдет, хотя должен быть Failed.

Этап 3: Реализуем метод

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

[TestMethod]
public void Calculator_Divide_MustDivideFirstIntBySecondAndReturnTheResult()
{
   Calculator calculator = new Calculator();

   int result = calculator.Divide(4, 2);

   Assert.AreEqual(2, result);
}

В момент реализации самого метода вы можете подумать: "Черт, а ведь на ноль делить нельзя - надо добавить проверку". И будете не правы, так как ваш тест не учитывает этого функционала. Для написания такого функционала нужно написать еще один тест:

[TestMethod]
public void Calculator_Divide_MustCheckSecondArgumentOnZeroAndThrowExceptionIfTrue()
{
   Calculator calculator = new Calculator();

   bool catched = false;

   try
   {
       int result = calculator.Divide(4, 0);
   }
   catch (Exception ex)
   {
       Assert.IsInstanceOfType(ex, typeof (ArgumentException));
       catched = true;
   }
   Assert.IsTrue(catched, "No exception throwed - something wrong");
}

И только после этого реализовывайте соответствующий функционал.

Этап 4: Проходим тест

Последний этап самый простой - убедится что тест прошел.

Заключение

Как видите использование данной практики связано с большим расходом времени(нормальное соотношение строк кода к тестам - 1:2) и очень большой силой самоконтроля - непросто заставить себя идти ровно по шагам не забегая вперед тестов. Но - это того стоит. Во время написания тестов находится много потенциальных логических ошибок, которые в противном случае всплыли бы на этапе выполнения.

В этой статье мы использовали Unit Test Framework, который идет в комплекте с Visual Studio. В следующей статье мы рассмотрим альтернативный - MbUnit.

До новых встреч.

Стимул

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

И так бы ничего и не изменилось, если бы не одно событие, показавшее мне, что это все-таки не зря - вести блог. Ходя по форуму sql.ru в перерыве на работе я заметил топик про уже описаный мною продукт - UrlRewriting.NET. И, как это было для меня неожидано, увидел в нем ссылку, ведущую на блог, который вы сейчас читаете.

Вроде мелочь, скажете вы, и я с вами соглашусь. Но эта мелочь - тот стимул, которого мне не хватало.

Этот пост - всего-лишь вступление, попытка вспомнить клавиатуру и разговорный язык - на ближайшее время я наметил пару статей по юнит-тестированию в .NET и целом и в ASP.NET MVC в частности. Также, вероятно, мы с вами коснемся различных паттернов проектирования, как то Repository, IoC и, возможно, других, если я найду их для себя интересными и применимыми в своей реальной жизни.

До скорых встреч, коллеги!