Тестирование состояния / взаимодействия и путаница при их смешивании (или злоупотреблении) - PullRequest
6 голосов
/ 19 августа 2009

Я думаю, что понимаю определение тестирования на основе состояния / взаимодействия (читайте статью Фаулера и т. Д.). Я обнаружил, что начал основываться на состоянии, но в большей степени основывался на взаимодействии, и я немного запутался в том, как тестировать определенные вещи.

У меня есть контроллер в MVC, и действие вызывает службу для отказа в пакете:

public ActionResult Deny(int id)
{
    service.DenyPackage(id);

    return RedirectToAction("List");
}

Мне кажется, это понятно. Предоставить фиктивный сервис, убедиться, что он был правильно вызван, выполнено.

Теперь у меня есть действие для представления, которое позволяет пользователю связать сертификат с пакетом:

public ActionResult Upload(int id)
{
    var package = packageRepository.GetPackage(id);
    var certificates = certificateRepository.GetAllCertificates();

    var view = new PackageUploadViewModel(package, certificates);

    return View(view);
}

На этот раз я немного озадачен. Я делаю тесты в стиле Spec (возможно, неправильно), поэтому для тестирования этого метода у меня есть класс, а затем два теста: убедитесь, что был вызван репозиторий пакетов, убедитесь, что был вызван репозиторий сертификатов. Я на самом деле хочу третий тест, чтобы убедиться, что конструктор был вызван, но я не знаю, как это сделать! У меня такое впечатление, что это совершенно неправильно.

Так что для тестирования на основе состояния я бы передавал идентификатор и затем проверял представление ActionResult. Хорошо, это имеет смысл. Но разве у меня не будет теста на конструктор PackageUploadViewModel? Поэтому, если у меня есть тест на конструкторе, то часть меня просто захочет убедиться, что я вызываю конструктор и что возвращаемое действие соответствует тому, что возвращает конструктор.

Теперь еще одна опция, о которой я могу подумать, это то, что у меня есть PackageUploadViewModelBuilder (или что-то с таким же тупым именем), который зависит от двух репозиториев, а затем я просто передаю идентификатор методу CreateViewModel или чему-то другому. Тогда я мог бы высмеять этот объект, проверить все и быть счастливым. Но ... ну ... это кажется экстравагантным. Я делаю что-то простое ... не простое. Кроме того, controller.action (id), возвращающий builder.create (id), выглядит как добавление слоя без причины (контроллер отвечает за построение моделей представлений .. верно?)

Я не знаю ... Я думаю, что необходимо больше тестирования на основе состояния, но я боюсь, что если я начну тестировать возвращаемые значения, то если метод А может быть вызван в 8 различных контекстах, у меня будет тестовый взрыв с большим количеством повторений. Я использовал тестирование на основе взаимодействия, чтобы передать некоторые из этих контекстов в метод B, чтобы все, что мне нужно было сделать, это проверить метод A, называемый методом B, и у меня был протестирован метод B, чтобы метод A мог просто доверять обработке этих контекстов. Таким образом, основанное на взаимодействии тестирование строит эту иерархию тестов, но тестирование на основе состояния собирается сгладить ее.

Понятия не имею, имеет ли это смысл.

Ух ты, это долго ...

Ответы [ 2 ]

5 голосов
/ 19 августа 2009

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

Что наиболее важно, так это то, что ваш API выполняет то, что вы хотите, и , что - это то, что вам нужно для тестирования. Если вы протестируете механику того, как он достигает того, что ему нужно, вы, скорее всего, в конечном итоге получите тесты с перенастроенными данными, которые укусят вас, когда дело доходит до ремонтопригодности.

В большинстве случаев вы можете спроектировать свой API так, чтобы тестирование на основе состояния было естественным выбором, потому что это намного проще.

Чтобы проверить пример загрузки: имеет ли значение, что были вызваны GetPackage и GetAllCertificates? Это действительно ожидаемый результат метода загрузки?

Я бы не догадался. Я предполагаю, что цель метода Upload - это и есть причина существования - состоит в том, чтобы заполнить и обслуживать правильный View.

Таким образом, тестирование на основе состояния проверяет возвращенный ViewResult и его ViewModel и проверяет, что оно имеет все правильные значения.

Конечно, поскольку код стоит прямо сейчас, вам потребуется предоставить Test Doubles для packageRepository и certificateRepository, потому что в противном случае будут генерироваться исключения, но не похоже, что само по себе важно, чтобы вызывались методы репозитория. .

Если вы используете Stubs вместо Mocks для своих репозиториев, ваши тесты больше не привязаны к внутренним деталям реализации. Если позже вы решите изменить реализацию метода Upload для использования кэшированных экземпляров пакетов (или чего-либо еще), заглушка не будет вызываться, но это нормально, поскольку в любом случае это не важно - что важно что возвращаемое представление содержит ожидаемые данные.

Это гораздо предпочтительнее, чем тестовый перерыв, даже если все возвращенные данные в порядке.

Интересно, что ваш пример Deny выглядит как основной пример, где основанное на взаимодействии тестирование все еще оправдано, потому что только проверяя косвенные выходы, вы можете проверить, что метод выполнил правильное действие (метод DenyPackage возвращает void).

Все это и многое другое очень хорошо объясняется в превосходной книге xUnit Test Patterns .

1 голос
/ 13 сентября 2009

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

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

Вот почему у нас есть эвристика "Stub Queries, Mock Actions" в http://www.mockobjects.com/book

...