NUnit - 测试失败后清理

时间:2009-07-15 14:31:01

标签: .net nunit nunit-2.5

我们有一些访问数据库的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 个答案:

答案 0 :(得分:63)

从版本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();
    }
}

答案 1 :(得分:20)

这个想法让我感兴趣,所以我做了一点挖掘。 NUnit没有开箱即用的这种能力,但NUnit提供了一个完整的可扩展性框架。我发现this great article about extending 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);
    }
  }
}

但这里有一个棘手的部分:必须将插件放在NUnit跑步者旁边的addins目录中。我被放置在TestDriven.NET目录中的NUnit跑步者旁边:

  

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

(我创建了addins目录,它不在那里)

编辑另一件事是清理方法需要static

我将一个简单的插件拼凑在一起,您可以从my SkyDrive下载源代码。您必须在适当的位置添加对nunit.framework.dllnunit.core.dllnunit.core.interfaces.dll的引用。

一些注意事项:属性类可以放在代码中的任何位置。我不想将它放在与插件本身相同的程序集中,因为它引用了两个Core NUnit程序集,因此我将它放在不同的程序集中。如果您决定将其放在其他位置,请记住更改CleanAddin.cs中的行。

希望有所帮助。

答案 2 :(得分:2)

是的,有。您可以使用 Teardown属性,该属性将在每次测试后进行拆分。您希望在每次测试之前和之后应用您拥有的数据库“重置”脚本并进行拆卸和重新设置。

  

此属性用于a   TestFixture提供了一套通用的   之后执行的功能   每种测试方法都在运行。

更新:根据评论和问题更新,我会说您可以使用拆解属性并使用私有变量来指示方法内容是否应该触发。

虽然,我也看到你不想要任何复杂的逻辑或错误处理代码。

鉴于此,我认为标准的Setup / Teardown最适合您。如果有错误并且您不必有任何错误处理代码也没关系。

如果你需要特别清理,因为接下来的测试取决于当前测试的成功完成,我建议你重新考试一下 - 他们可能不应该相互依赖。

答案 3 :(得分:2)

虽然有可能强制nUnit这样做,但这不是最明智的设计,你总是可以在某处设置一个临时文件,如果该文件存在,请运行清理。

我建议您更改代码,以便启用数据库事务,并在测试结束时,只需将数据库恢复到原始状态(例如,丢弃代表您的单元测试的事务)。

答案 4 :(得分:1)

使用Try-Catch块怎么样,重新抛出捕获的异常?

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

答案 5 :(得分:1)

我想像phsr建议的那样,当你负担得起时,重构测试,以便他们永远不必依赖于另一个测试需要的相同数据,甚至更好地抽象数据访问层并模拟来自的结果那个数据库。听起来你的测试相当昂贵,你应该在数据库和程序集中的业务逻辑上执行所有查询逻辑,而不关心返回的结果是什么。

您还可以更好地测试您的ExceptionHandling。

答案 6 :(得分:1)

另一种选择是使用一个特殊的函数来抛出异常,它会在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();
    }
}

这里唯一的问题是Stack跟踪将导致CleanOnErrorFixture

答案 7 :(得分:1)

到目前为止,未提及的一个选项是将测试包装在TransactionScope对象中,因此测试从未向数据库提交任何内容并不重要。

这是some details on the technique。如果你在单元测试和事务管理器上进行搜索,你可能会发现更多(尽管你真的在进行集成测试时遇到了数据库)。我过去成功使用过它。

这种方法很简单,不需要任何清理并确保测试被隔离。

编辑 - 我刚刚注意到Ray Hayes的答案也与我的相似。

答案 8 :(得分:0)

它怎么会失败?是否可以将其置于try(do test)/ catch(修复破坏的db)/ finally块?

或者,当您检查失败情况时,可以调用私有方法来修复它。

答案 9 :(得分:0)

我不是说这是一个好主意,但它应该有效。 请记住,断言失败只是例外。另外不要忘记还有一个[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();
          }
     }
}

答案 10 :(得分:0)

您可以使用
添加[TearDown]方法 if (TestContext.CurrentContext.Result.Status != TestStatus.Passed)
测试失败时要执行的一些代码。