Вопрос

У меня есть несколько юнит-тестов этого паттерна:

[TestMethod ()]
[ExpectedException (typeof (ArgumentNullException))]
public void DoStuffTest_Exception ()
{
    var foo = new Foo ();
    Foo.DoStuff (null);
}

Оказывается, что покрытие кода помечает бросающую строку как половинную, поэтому каждый раз я получаю 1 блок непокрытого кода.

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

public static void ExpectException<_T> (Action action) where _T: Exception
{
    try { action(); }
    catch (_T) { return; }
    Assert.Fail ("Expected " + _T);
}

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

Это правильный дизайн или я что-то пропустил?

Edit: Ugs ... похоже, что приведенный выше метод ExpectException оставляет мне и 1 непокрытый блок.

Это было полезно?

Решение

То, что вы предлагаете, действительно. Помимо вашей проблемы с покрытием кода, я бы сказал, что это лучше, чем использование атрибута ExpectedException , поскольку он явно показывает, какая строка теста может выдать исключение. Использование ExpectedException означает, что любая строка кода в тесте может выдать ожидаемый тип исключения, и тест все равно будет пройден. Если ошибка возникла из-за другого вызова, который не ожидался сгенерировать, это может скрыть тот факт, что тест должен быть неудачным, потому что строка, которая должна генерировать, не является.

Что было бы полезно, если бы вы предложили изменить возвращаемое исключение:

public static _T ExpectException<_T> (Action action) where _T: Exception
{
    try { action(); }
    catch (_T ex) { return ex; }
    Assert.Fail ("Expected " + typeof(_T));
    return null;
}

Это позволило бы тестовому коду дополнительно утверждать исключение, если оно требуется (т. е. проверять, использовалось ли конкретное сообщение).

NUnit (хотя не похоже, что вы используете его, поскольку у вас есть атрибут TestMethod ), имеет встроенную конструкцию, аналогичную предложенной вами:

Assert.Throws<ArgumentNullException>(() => Foo.DoStuff(null))

Другие советы

@adrianbanks ExpectException не работает должным образом, если параметр действия выдает другое исключение, чем ожидаемое исключение:

[TestMethod]
public void my_test()
{
    ExpectException<InvalidOperationException>(delegate()
    {
        throw new ArgumentException("hello");
    });
}

Когда я запускаю TestMethod " my_test " я только что получил сообщение о том, что поднял тестовый метод и System.ArgumentException: привет. В этом случае должно быть написано «Expected InvalidOperationException». Я предлагаю новую версию для метода ExpectException:

public static void VerifierException<T>(Action action) where T : Exception
{
    try
    {
        action();
    }
    catch (Exception ex)
    {
        Assert.IsInstanceOfType(ex, typeof(T));
        return;
    }

    Assert.Fail("Aucune exception n'a été déclenchée alors qu'une exception du type " + typeof(T).FullName + " était attendue");
}

Я знаю, что это старая тема, но я столкнулся с той же проблемой.

В конце концов я задал себе вопрос: зачем мне знать охват тестов? Не знаю! . Так что давайте исключим их, чтобы охват был более чистым.

В моем тестовом проекте я добавил файл CodeCoverage.runsettings , и это содержимое:

<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
  <DataCollectionRunSettings>
    <DataCollectors>
      <DataCollector friendlyName="Code Coverage" uri="datacollector://Microsoft/CodeCoverage/2.0" assemblyQualifiedName="Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
        <Configuration>
          <CodeCoverage>
            <ModulePaths>
              <Exclude>
                <ModulePath>.*tests.dll</ModulePath>
                <ModulePath>.*Tests.dll</ModulePath>
                <!-- Add more ModulePath nodes here. -->
              </Exclude>
            </ModulePaths>
          </CodeCoverage>
        </Configuration>
      </DataCollector>
    </DataCollectors>
  </DataCollectionRunSettings>
</RunSettings>

После выбора этого файла настроек теста мой охват кода составляет 100%

Таким образом, нет необходимости «взламывать» систему покрытия кода модульного теста, просто чтобы достичь 100%: -)

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top