NUnit - очистка после отказа теста
У нас есть несколько тестов NUnit, которые обращаются к базе данных. Когда один из них терпит неудачу, он может оставить базу данных в несогласованном состоянии - что не является проблемой, поскольку мы перестраиваем базу данных для каждого тестового прогона - но это может привести к сбою других тестов в одном и том же прогоне.
Можно ли обнаружить, что один из тестов не выполнен и выполняет какую-то очистку?
Мы не хотим писать код очистки в каждом тесте, мы уже это делаем. Я хотел бы выполнить очистку в Teardown, но только если тест завершился неудачно, так как очистка может быть дорогостоящей.
Обновить. Чтобы уточнить, я хотел бы, чтобы тесты были простыми и НЕ включали логику обработки ошибок или ошибок. Я также не хочу выполнять базу данных reset при каждом тестовом прогоне - только в случае сбоя теста. И этот код, вероятно, должен быть выполнен в методе 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()
Ответы
Ответ 1
Эта идея заинтересовала меня, поэтому я немного погубил. 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
{
}
И вот как он выполнен из addin:
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);
}
}
}
Но здесь сложная часть: addin должен быть помещен в каталог addins
рядом с бегуном NUnit. Шахта была помещена рядом с бегуном NUnit в каталоге TestDriven.NET:
C:\Program Files\TestDriven.NET 2.0\NUnit\addins
(я создал каталог addins
, его там не было)
EDIT Другое дело, что метод очистки должен быть static
!
Я взломал простую добавку, вы можете скачать источник из моего SkyDrive. Вам нужно будет добавить ссылки на nunit.framework.dll
, nunit.core.dll
и nunit.core.interfaces.dll
в соответствующих местах.
Несколько примечаний: класс атрибутов можно поместить в любом месте вашего кода. Я не хотел размещать его в той же сборке, что и сама добавка, потому что она ссылается на две сборки Core
NUnit, поэтому я поместил ее в другую сборку. Просто не забудьте изменить строку в CleanAddin.cs
, если вы решите поместить ее в другое место.
Надеюсь, что это поможет.
Ответ 2
Начиная с версии 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();
}
}
Ответ 3
Да, есть. Вы можете использовать атрибут Teardown, который будет срываться после каждого теста. Вы хотите применить эту базу данных "reset" script, которую у вас есть, и отменить и переустановить до и после каждого теста.
Этот атрибут используется внутри TestFixture, чтобы обеспечить общий набор функции, выполняемые после каждый тестовый метод запускается.
Обновить. На основе комментариев и обновления к вопросу я бы сказал, что вы можете использовать атрибут teardown и использовать частные переменные, чтобы указать, должно ли должно быть содержимое метода.
Хотя, я также видел, что вам не нужна сложная логика или код обработки ошибок.
Учитывая это, я думаю, что стандартный Setup/Teardown будет работать лучше всего для вас. Не имеет значения, есть ли ошибка, и вам не нужно иметь код обработки ошибок.
Если вам нужна специальная очистка, потому что следующие тесты зависят от успешного завершения текущего теста, я бы предложил вернуться к вашим тестам - они, вероятно, не должны зависеть друг от друга.
Ответ 4
В то время как можно было бы принудить nUnit делать это, это не самый разумный дизайн, вы всегда можете где-то установить временный файл, и если этот файл существует, запустите очистку.
Я бы порекомендовал изменить ваш код, чтобы вы включили транзакции базы данных и в конце теста просто вернули базу данных в исходное состояние (например, отмените транзакцию, которая представляет ваши модульные тесты).
Ответ 5
Как насчет использования блока Try-Catch, перетащив исключение?
try
{
//Some assertion
}
catch
{
CleanUpMethod();
throw;
}
Ответ 6
Я бы хотел, чтобы phsr предлагал на данный момент и когда вы можете себе это позволить, реорганизовать тесты, чтобы им никогда не приходилось полагаться на те же данные, что и другой тест, или даже лучше абстрагировать уровень доступа к данным и высмеивать результаты, исходящие из эта база данных. Похоже, что ваши тесты довольно дороги, и вы должны делать всю свою логику запросов в базе данных и бизнес-логике в своей сборке, вам все равно, какие результаты будут возвращены.
Вы также сможете лучше проверить свой ExceptionHandling.
Ответ 7
Другой вариант - иметь специальную функцию, которая будет генерировать ваши исключения, которая устанавливает переключатель в testfixture, который говорит, что произошло исключение.
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
Ответ 8
Один из вариантов, о которых не упоминалось до сих пор, заключается в том, чтобы завершить тестирование в объекте TransactionScope, поэтому не имеет значения, что происходит, когда тест никогда ничего не делает для БД.
Здесь некоторые сведения о технике. Вероятно, вы можете найти больше, если выполните поиск по модульному тестированию и транзакциям (хотя вы действительно выполняете интеграционное тестирование, если попадете в БД). Я использовал его в прошлом.
Этот подход прост, не требует никакой очистки и гарантирует, что тесты изолированы.
Изменить. Я только что заметил, что ответ Рэй Хейс тоже похож на мой.
Ответ 9
Как это происходит? Можно ли поместить его в try (do test)/catch (fix broken db)/finally block?
Или вы можете вызвать частный метод, чтобы исправить его, когда вы проверили состояние сбоя.
Ответ 10
Я не говорю, что это отличная идея, но она должна работать.
Помните, что ошибки утверждения являются лишь исключениями. Также не забывайте, что есть и атрибут [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();
}
}
}
Ответ 11
Вы можете добавить метод [TearDown]
с помощью
if (TestContext.CurrentContext.Result.Status != TestStatus.Passed)
некоторый код, который должен быть выполнен, если тест не выполнен.