Как я могу проверить ожидаемое исключение с помощью определенного сообщения об исключении из файла ресурсов в Visual Studio Test?
-
02-07-2019 - |
Вопрос
Visual Studio Test может проверять наличие ожидаемых исключений с помощью атрибута ExpectedException.Вы можете передать исключение следующим образом:
[TestMethod]
[ExpectedException(typeof(CriticalException))]
public void GetOrganisation_MultipleOrganisations_ThrowsException()
Вы также можете проверить сообщение, содержащееся в ExpectedException, следующим образом:
[TestMethod]
[ExpectedException(typeof(CriticalException), "An error occured")]
public void GetOrganisation_MultipleOrganisations_ThrowsException()
Но при тестировании приложений I18N я бы использовал файл ресурсов, чтобы получить это сообщение об ошибке (любой может даже решить протестировать различные локализации сообщения об ошибке, если я захочу, но Visual Studio не позволит мне сделать это:
[TestMethod]
[ExpectedException(typeof(CriticalException), MyRes.MultipleOrganisationsNotAllowed)]
public void GetOrganisation_MultipleOrganisations_ThrowsException()
Компилятор выдаст следующую ошибку:
Аргумент атрибута должен быть постоянным выражением, выражением типа или выражением создания массива атрибута
Кто-нибудь знает, как проверить исключение, содержащее сообщение из файла ресурсов?
Один из вариантов, который я рассмотрел, — это использование пользовательских классов исключений, но он основан на часто встречающихся советах, таких как:
«Создайте и добавляйте пользовательские исключения, если у вас есть условие ошибки, с которым можно программно обрабатывать иначе, чем любое другое существующее исключение.В противном случае бросьте одно из существующих исключений ». Источник
Я не ожидаю, что в нормальном потоке исключения будут обрабатываться по-другому (это критическое исключение, поэтому я все равно перехожу в режим паники), и я не думаю, что создание исключения для каждого тестового примера - это правильно.Есть мнения?
Решение
Только мнение, но я бы сказал текст ошибки:
- является частью теста, и в этом случае получение его из ресурса будет «неправильным» (в противном случае вы можете получить постоянно искажаемый ресурс), поэтому просто обновляйте тест при изменении ресурса (или тест не пройден)
- не является частью теста, и вам следует заботиться только о том, чтобы оно вызывало исключение.
Обратите внимание, что первый вариант должен позволить вам тестировать несколько языков, учитывая возможность запуска с локалью.
Что касается множественных исключений, я из страны C++, где создание множества исключений (вплоть до одного на оператор «throw»!) в больших наследствах приемлемо (если не распространено), но система метаданных .Net, вероятно, этого не делает. Мне это не нравится, отсюда и такой совет.
Другие советы
Я бы рекомендовал использовать вспомогательный метод вместо атрибута.Что-то вроде этого:
public static class ExceptionAssert
{
public static T Throws<T>(Action action) where T : Exception
{
try
{
action();
}
catch (T ex)
{
return ex;
}
Assert.Fail("Exception of type {0} should be thrown.", typeof(T));
// The compiler doesn't know that Assert.Fail
// will always throw an exception
return null;
}
}
Затем вы можете написать свой тест примерно так:
[TestMethod]
public void GetOrganisation_MultipleOrganisations_ThrowsException()
{
OrganizationList organizations = new Organizations();
organizations.Add(new Organization());
organizations.Add(new Organization());
var ex = ExceptionAssert.Throws<CriticalException>(
() => organizations.GetOrganization());
Assert.AreEqual(MyRes.MultipleOrganisationsNotAllowed, ex.Message);
}
Преимущество этого также заключается в том, что он проверяет, что исключение было выброшено именно в той строке, в которой вы ожидали его создания, а не где-либо в вашем тестовом методе.
Аргумент сообщения ExpectedException не соответствует сообщению об исключении.Скорее, это сообщение, которое выводится в результатах теста, если ожидаемое исключение на самом деле не произошло.
Я думаю, вы можете просто выполнить явную попытку перехвата в своем тестовом коде вместо того, чтобы полагаться на атрибут ExpectedException, который сделает это за вас.Затем вы можете придумать какой-нибудь вспомогательный метод, который будет читать файл ресурсов и сравнивать сообщение об ошибке с сообщением об исключении, которое было перехвачено.(конечно, если исключения не было, то тестовый пример следует считать неудачным)
Если вы переключитесь на использование очень приятного xUnit.Net тестовую библиотеку, вы можете заменить [ExpectedException] чем-то вроде этого:
[Fact]
public void TestException()
{
Exception ex = Record.Exception(() => myClass.DoSomethingExceptional());
// Assert whatever you like about the exception here.
}
Интересно, движется ли NUnit по пути от простоты...но вот.
Новые улучшения (2.4.3 и выше?) атрибута ExpectedException позволяют лучше контролировать проверки, выполняемые для ожидаемого исключения с помощью метода Handler..Более подробная информация о официальная страница документации NUnit..ближе к концу страницы.
[ExpectedException( Handler="HandlerMethod" )]
public void TestMethod()
{
...
}
public void HandlerMethod( System.Exception ex )
{
...
}
Примечание:Что-то здесь не так..Почему ваши сообщения об исключениях интернационализированы?Используете ли вы исключения для вещей, которые необходимо обработать или уведомить пользователя?Если только у вас нет группы разработчиков с разными культурами, исправляющих ошибки..тебе это не должно понадобиться.Исключений на английском или общепринятом языке будет достаточно.Но на случай, если вам это понадобится..возможно :)
Я столкнулся с этим вопросом, пытаясь решить подобную проблему самостоятельно.(Я подробно опишу решение, которое я выбрал ниже.)
Я должен согласиться с комментариями Гишу о том, что интернационализация сообщений об исключениях является запахом кода.
Первоначально я сделал это в своем собственном проекте, чтобы обеспечить согласованность сообщений об ошибках, выдаваемых моим приложением, и моих модульных тестов.т. е. чтобы определить сообщения об исключениях только в одном месте и в то время, файл ресурсов казался разумным местом для этого, поскольку я уже использовал его для различных меток и строк (и поскольку имело смысл добавить ссылку к нему в моем тестовом коде, чтобы убедиться, что те же метки отображаются в соответствующих местах).
В какой-то момент я рассматривал (и тестировал) использование блоков try/catch, чтобы избежать требования константы атрибутом ExpectedException, но казалось, что это приведет к довольно большому количеству дополнительного кода, если применить его в больших масштабах.
В конце концов, решение, которое я выбрал, заключалось в том, чтобы создать статический класс в моей библиотеке ресурсов и хранить в нем сообщения об исключениях.Таким образом, нет необходимости интернационализировать их (что, я согласен, не имеет смысла), и они становятся доступными в любое время, когда строка ресурса будет доступна, поскольку они находятся в одном пространстве имен.(Это соответствует моему желанию не усложнять проверку текста исключения.)
Тогда мой тестовый код просто сводится к (простите за искажения...):
[Test,
ExpectedException(typeof(System.ArgumentException),
ExpectedException=ProductExceptionMessages.DuplicateProductName)]
public void TestCreateDuplicateProduct()
{
_repository.CreateProduct("TestCreateDuplicateProduct");
_repository.CreateProduct("TestCreateDuplicateProduct");
}