NUnit - очистка после сбоя теста - PullRequest
39 голосов
/ 15 июля 2009

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

Можно ли обнаружить, что один из тестов не прошел и выполнить какую-то очистку?

Мы не хотим писать код очистки в каждом тесте, мы уже делаем это сейчас. Я хотел бы выполнить очистку в Teardown, но только если тест не пройден, так как очистка может быть дорогой.

Обновление : Чтобы уточнить - я хотел бы, чтобы тесты были простыми и НЕ содержали никакой логики очистки или обработки ошибок. Я также не хочу выполнять сброс базы данных при каждом запуске теста - только если тест не пройден. И этот код, вероятно, должен быть выполнен в методе Teardown, но я не знаю ни одного способа получить информацию, если тест, который мы в настоящее время разрываем, не удался или был успешным.

Update2

        [Test]
        public void MyFailTest()
        {
            throw new InvalidOperationException();
        }

        [Test]
        public void MySuccessTest()
        {
            Assert.That(true, Is.True);
        }

        [TearDown]
        public void CleanUpOnError()
        {
            if (HasLastTestFailed()) CleanUpDatabase();
        }

Я ищу реализацию HasLastTestFailed ()

Ответы [ 11 ]

63 голосов
/ 03 мая 2011

Начиная с версии 2.5.7, NUnit позволяет Teardown определять, не прошел ли последний тест. Новый класс TestContext позволяет тестам получать доступ к информации о себе, включая TestStauts.

Для получения более подробной информации, пожалуйста, обратитесь к http://nunit.org/?p=releaseNotes&r=2.5.7

[TearDown]
public void TearDown()
{
    if (TestContext.CurrentContext.Result.Status == TestStatus.Failed)
    {
        PerformCleanUpFromTest();
    }
}
20 голосов
/ 16 июля 2009

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

Вот как выглядит тест:

  [TestFixture]
  public class NUnitAddinTest
  {
    [CleanupOnError]
    public static void CleanupOnError()
    {
      Console.WriteLine("There was an error, cleaning up...");
      // perform cleanup logic
    }

    [Test]
    public void Test1_this_test_passes()
    {
      Console.WriteLine("Hello from Test1");
    }

    [Test]
    public void Test2_this_test_fails()
    {
      throw new Exception("Test2 failed");
    }

    [Test]
    public void Test3_this_test_passes()
    {
      Console.WriteLine("Hello from Test3");
    }
  }

где атрибут просто:

  [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
  public sealed class CleanupOnErrorAttribute : Attribute
  {
  }

А вот как это выполняется из надстройки:

public void RunFinished(TestResult result)
{
  if (result.IsFailure)
  {
    if (_CurrentFixture != null)
    {
      MethodInfo[] methods = Reflect.GetMethodsWithAttribute(_CurrentFixture.FixtureType,
                                                             CleanupAttributeFullName, false);
      if (methods == null || methods.Length == 0)
      {
        return;
      }

      Reflect.InvokeMethod(methods[0], _CurrentFixture);
    }
  }
}

Но вот сложная часть: надстройка должна быть помещена в каталог addins рядом с бегуном NUnit. Мой был помещен рядом с бегуном NUnit в каталоге TestDriven.NET:

C:\Program Files\TestDriven.NET 2.0\NUnit\addins

(я создал каталог addins, его там не было)

РЕДАКТИРОВАТЬ Другое дело, что метод очистки должен быть static!

Я взломал простой плагин, вы можете скачать исходный код с my SkyDrive . Вам нужно будет добавить ссылки на nunit.framework.dll, nunit.core.dll и nunit.core.interfaces.dll в соответствующих местах.

Несколько замечаний: класс атрибута может быть размещен в любом месте вашего кода. Я не хотел помещать его в ту же сборку, что и само дополнение, потому что он ссылается на две сборки Core NUnit, поэтому я поместил его в другую сборку. Просто не забудьте изменить строку в CleanAddin.cs, если вы решите поместить ее в другое место.

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

2 голосов
/ 15 июля 2009

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

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

2 голосов
/ 15 июля 2009

Да, есть. Вы можете использовать атрибут Teardown , который будет разрушаться после каждого теста. Вы хотели бы применить этот сценарий «сброса» базы данных, который у вас есть, а также демонтировать и переустановить до и после каждого теста.

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

Обновление : основываясь на комментариях и обновлении вопроса, я бы сказал, что вы можете использовать атрибут teardown и частные переменные, чтобы указать, должно ли выполняться содержимое метода.

Хотя я также видел, что вам не нужна сложная логика или код обработки ошибок.

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

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

1 голос
/ 15 июля 2009

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

Вот некоторые подробности о технике . Возможно, вы найдете больше, если выполните поиск по модульному тестированию и транзакциям (хотя вы действительно выполняете интеграционное тестирование, если попадаете в БД). Я успешно использовал его в прошлом.

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

Редактировать. Я только что заметил, что ответ Рэя Хейса также похож на мой.

1 голос
/ 15 июля 2009

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

public abstract class CleanOnErrorFixture
{
     protected bool threwException = false;

     protected void ThrowException(Exception someException)
     {
         threwException = true;
         throw someException;
     }

     protected bool HasTestFailed()
     {
          if(threwException)
          {
               threwException = false; //So that this is reset after each teardown
               return true;
          }
          return false;
     }
}

Тогда, используя ваш пример:

[TestFixture]
public class SomeFixture : CleanOnErrorFixture
{
    [Test]
    public void MyFailTest()
    {
        ThrowException(new InvalidOperationException());
    }

    [Test]
    public void MySuccessTest()
    {
        Assert.That(true, Is.True);
    }

    [TearDown]
    public void CleanUpOnError()
    {
        if (HasLastTestFailed()) CleanUpDatabase();
    }
}

Единственная проблема здесь в том, что трассировка стека приведет к CleanOnErrorFixture

1 голос
/ 15 июля 2009

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

Вы также сможете намного лучше протестировать свое ExceptionHandling.

1 голос
/ 15 июля 2009

А как насчет использования блока Try-Catch, перебрасывающего пойманную исключительную ситуацию?

try
{
//Some assertion
}
catch
{
     CleanUpMethod();
     throw;
}
0 голосов
/ 09 октября 2015

Вы можете добавить метод [TearDown] с помощью
if (TestContext.CurrentContext.Result.Status != TestStatus.Passed)
некоторый код, который будет выполнен, если тест не пройден.

0 голосов
/ 16 июля 2009

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

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

Я не рекомендую это, но это будет работать. Вы на самом деле не используете NUnit по назначению, но вы можете это сделать.


[TestFixture]
public class Tests {
     private bool testsFailed = false;

     [Test]
     public void ATest() {
         try {
             DoSomething();
             Assert.AreEqual(....);
         } catch {
            testFailed = true;
         }
     }

     [TestFixtureTearDown]
     public void CleanUp() {
          if (testsFailed) {
              DoCleanup();
          }
     }
}
...