Вопрос

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

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

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

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

Обновление 2:

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

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

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

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

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

Решение

Эта идея меня заинтересовала, и я немного покопался.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!

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

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

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

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

Начиная с версии 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();
    }
}

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

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

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

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

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

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

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

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

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

try
{
//Some assertion
}
catch
{
     CleanUpMethod();
     throw;
}

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

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

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

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.

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

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

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

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

Как это терпит неудачу?Можно ли его попробовать (провести тест)/поймать (исправить сломанную базу данных)/наконец заблокировать?

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

Я не говорю, что это отличная идея, но она должна сработать.Помните, что ошибки утверждения — это всего лишь исключения.Также не забывайте, что существует атрибут [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();
          }
     }
}

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

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