Я просто не понимаю модульное тестирование TDD (проект Asp.Net MVC)? - PullRequest
16 голосов
/ 19 мая 2010

Я пытаюсь понять, как правильно и эффективно выполнить юнит-тестирование моего проекта Asp.net MVC. Когда я начал этот проект, я купил Pro ASP.Net MVC, и с этой книгой я узнал о TDD и модульном тестировании. После ознакомления с примерами и того факта, что я работаю инженером-программистом в QA в моей нынешней компании, я был поражен тем, насколько крутым оказался TDD. Поэтому я начал работать над своим проектом и занялся написанием юнит-тестов для уровня своей базы данных, уровня бизнеса и контроллеров. Все прошло модульное тестирование до реализации. Сначала я думал, что это было круто, но потом дела пошли вниз.

Вот проблемы, с которыми я начал сталкиваться:

  • В итоге я написал код приложения, чтобы сделать возможным выполнение модульных тестов. Я не имею в виду это хорошим способом, так как мой код был сломан, и мне пришлось это исправить, чтобы пройти тестовый модуль. Я имею в виду, что абстрагирование базы данных от фиктивной базы данных невозможно из-за использования linq для извлечения данных (с использованием общего шаблона репозитория).

    Причина в том, что с linq-> sql или linq-> сущностями вы можете делать объединения, просто выполнив:

    var objs = select p from _container.Projects select p.Objects;
    

    Однако, если вы макетируете слой базы данных, чтобы этот linq прошел модульный тест, вы должны изменить linq на

    var objs = select p from _container.Projects
           join o in _container.Objects on o.ProjectId equals p.Id
           select o;
    

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

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

    Таким образом, я закончил тем, что бросил тестирование моей базы данных.

  • Написание модульных тестов для контроллеров было легко, пока я возвращал представления. Тем не менее, основная часть моего приложения (и та, которая больше всего выиграет от модульного тестирования) - это сложное ajax-приложение. По разным причинам я решил изменить приложение с возврата представлений на JSON с нужными мне данными. После того, как это произошло, мои модульные тесты стали чрезвычайно болезненными для написания, поскольку я не нашел хорошего способа написания модульных тестов для нетривиального json.

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

  • Итак, в конце концов я остался с тестированием сервисного уровня (BLL). Прямо сейчас я использую EF4, однако у меня также была эта проблема с linq-> sql. Я решил использовать EF4-ориентированный на модель подход, потому что для меня имеет смысл сделать это таким образом (определите мои бизнес-объекты и дайте фреймворку понять, как его преобразовать в SQL-сервер). Это было хорошо в начале, но теперь это становится громоздким из-за отношений.

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

    User usr = new User { Name = "Me" };
    _userService.SaveUser(usr);
    
    Project prj = new Project { Name = "Test Project", Owner = usr };
    _projectService.SaveProject(prj);
    
    Object obj = new Object { Name = "Test Object" };
    _objectService.SaveObject(obj);
    
    // Perform verifications
    

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

    • Для начала, если я добавлю новую зависимость, например, все проекты должны принадлежать к категории, я должен пойти на КАЖДЫЙ одиночный модульный тест, который ссылается на проект, добавить код для сохранения категории, затем добавить код для добавления категории в проект. Это может быть ОГРОМНОЕ усилие в будущем для очень простого изменения бизнес-логики, и все же почти ни один из модульных тестов, которые я буду модифицировать для этого требования, на самом деле не предназначен для тестирования этой функции / требования.
    • Если я затем добавлю проверки в свой метод SaveProject, чтобы проекты не могли быть сохранены, если у них нет имени, по крайней мере, с 5 символами, мне нужно будет пройти каждый модульный тест объекта и проекта, чтобы убедиться, что новое требование не выполняется. не делать никаких несобственных юнит-тестов.
    • Если есть проблема в методе UserService.SaveUser(), это приведет к сбою всех тестов проекта и объектных модулей, и эта причина не будет сразу заметна без необходимости разбираться с исключениями.

Таким образом, я удалил все юнит-тесты уровня сервиса из моего проекта.

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

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

Повсюду в Интернете я вижу людей, говорящих о том, насколько хороши модульные тесты TDD, и я говорю не только о фанатичных людях. Те немногие, кто отвергает тесты TDD / Unit, приводят неверные аргументы, утверждая, что они более эффективны при ручной отладке через IDE, или что их навыки кодирования удивительны тем, что им это не нужно. Я признаю, что оба эти аргумента являются явными ошибками, особенно для проекта, который должен обслуживаться несколькими разработчиками, но любые допустимые опровержения TDD, кажется, немногочисленны.

Итак, смысл этого поста в том, чтобы спросить, не понимаю ли я, как использовать TDD и автоматические юнит-тесты?

Ответы [ 3 ]

6 голосов
/ 19 мая 2010

Вы можете взглянуть на пример структуры проекта ASP.NET MVC 2.0 , который я написал. В нем представлены некоторые концепции, которые могут помочь вам начать модульное тестирование логики контроллера и базы данных. Что касается тестирования базы данных, то это уже не модульные тесты, а интеграционные тесты. Как вы увидите из моего примера, я использую NHibernate, который позволяет мне легко переключаться на образец базы данных SQLite, которая создается заново для каждого тестового устройства.

Наконец, выполнение модульных тестов в ASP.NET MVC может быть проблематичным без надлежащего разделения задач и абстракций, а использование поддельных структур и структур, таких как MVCContrib.TestHelper , может сделать вашу жизнь проще.

Вот предварительный просмотр того, как может выглядеть тест вашего контроллера.


UPDATE:

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

Я мог бы дать вам совет: если всю концепцию TDD трудно понять, и вы не видите в этом никакой выгоды, тогда это нормально. Никто не может доказать, что TDD полезен во всех ситуациях. Просто попытайтесь спроектировать свое приложение таким образом, чтобы каждый класс нес единую ответственность. Если вы обнаружите, что делаете проверку ввода, доступа к данным SQL и обработки исключений в одном и том же классе, то вы делаете это неправильно. Как только вы достигнете этого разделения, вы увидите, что модульное тестирование становится намного проще, и вы даже можете прийти к этапу, когда модульные тесты будут стимулировать вашу разработку:

Что касается юнит-тестирования JsonResult с MvcContrib.TestHelper, это конкретный вопрос, на который я даю конкретный ответ:

public class MyModel
{
    public string MyProperty { get; set; }
}

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return Json(new MyModel { MyProperty = "value" });
    }
}

и тест:

[TestMethod]
public void HomeController_Index_Action_Should_Return_Json_Representation_Of_MyModel()
{
    // arrange
    var sut = new HomeController();

    // act
    var actual = sut.Index();

    // assert
    actual
        .AssertResultIs<JsonResult>()
        .Data
        .ShouldBe<MyModel>("")
        .MyProperty
        .ShouldBe("value");
}
3 голосов
/ 20 мая 2010

Похоже, проблема не в модульном тестировании, а в вашем инструменте / платформе. Я пришел из Java, поэтому мне не нужно переписывать свои запросы только для удовлетворения юнит-тестов.

Тестирование Json - это боль, которую вы знаете, что. Если вы не хотите проверять это, не делайте;) Тестирование не покрывает 100%. Это тестирование того, что действительно нужно проверить. У вас гораздо больше шансов получить ошибку внутри сложного выражения, чем добавить что-либо на карту, а затем преобразовать ее в json.

Если вы проверяете, что карта создается правильно ... есть очень хороший шанс, что json также верен. Конечно, это не всегда будет, но вы будете знать, что, как только вы запустите его, и он заработает. Или нет. Это действительно так просто.

Хотя я могу дать реальный комментарий по третьей проблеме. Вы действительно не хотите создавать массивные графы объектов. Но есть несколько способов сделать это.

Создать синглтон с именем ObjectMother. Есть методы, такие как ObjectMother.createProject (). Внутри вы создаете фиктивный экземпляр проекта. Таким образом, 12 тестов могут использовать этот метод, и тогда вам нужно всего лишь изменить его в одном месте. Помните, что тестовый код необходимо реорганизовать!

Кроме того, вы можете посмотреть что-то вроде dbUnit. Ну, это то, что называется в мире Java. Идея состоит в том, что перед каждым тестом база данных переводится в известное состояние. И это происходит автоматически при настройке / разборке ваших тестов. Это означает, что ваш тестовый код может немедленно начать тестирование. Фактические фиктивные данные теста находятся в скрипте или файле XML.

Надеюсь, это поможет.

1 голос
/ 02 августа 2010

Что касается тестирования ваших представлений AJAXified, я бы посоветовал вам попробовать такую ​​среду тестирования, как WatiN .

С этим вы можете сделать следующее (пример с их сайта).

[Test]
public void SearchForWatiNOnGoogle()
{
 using (var browser = new IE("http://www.google.com"))
 {
  browser.TextField(Find.ByName("q")).TypeText("WatiN");
  browser.Button(Find.ByName("btnG")).Click();

  Assert.IsTrue(browser.ContainsText("WatiN"));
 }
}
...