43

我们有一些访问数据库的 NUnit 测试。当其中一个失败时,它可能会使数据库处于不一致的状态 - 这不是问题,因为我们为每次测试运行重建数据库 - 但它可能导致其他测试在同一次运行中失败。

是否可以检测到其中一个测试失败并执行某种清理?

我们不想在每个测试中都编写清理代码,我们现在已经这样做了。我想在 Teardown 中执行清理,但前提是测试失败,因为清理可能很昂贵。

更新:澄清 - 我希望测试简单,不包括任何清理或错误处理逻辑。我也不想在每次测试运行时都执行数据库重置——只有在测试失败的情况下。这段代码可能应该在 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() 的实现

4

11 回答 11

73

从 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();
    }
}
于 2011-05-03T14:59:39.793 回答
21

这个想法引起了我的兴趣,所以我做了一点挖掘。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);
    }
  }
}

但这里有一个棘手的部分:插件必须放在addinsNUnit 运行器旁边的目录中。我的放在 TestDriven.NET 目录中的 NUnit runner 旁边:

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

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

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

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

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

希望有帮助。

于 2009-07-15T23:44:04.077 回答
2

虽然可以强制 nUnit 执行此操作,但这不是最明智的设计,但您始终可以在某处设置一个临时文件,如果该文件存在,请运行您的清理。

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

于 2009-07-15T14:45:14.683 回答
2

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

此属性在 TestFixture 中使用,以提供在每个测试方法运行后执行的一组通用函数。

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

不过,我也确实看到您不需要任何复杂的逻辑或错误处理代码。

鉴于此,我认为标准的 Setup/ Teardown最适合您。是否有错误无关紧要,您不必有任何错误处理代码。

如果您需要特别清理,因为下一个测试取决于当前测试的成功完成,我建议重新访问您的测试——它们可能不应该相互依赖。

于 2009-07-15T14:35:41.890 回答
1

我现在确实希望 phsr 建议,当你负担得起时,重构测试,这样他们就不必依赖另一个测试需要的相同数据,甚至更好地抽象数据访问层并模拟来自该数据库的结果。听起来您的测试相当昂贵,您应该在程序集中对数据库和业务逻辑执行所有查询逻辑,您并不真正关心返回的结果是什么。

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

于 2009-07-15T14:49:41.913 回答
1

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

以下是该技术的一些细节。如果您对单元测试和事务范围进行搜索,您可能会找到更多信息(尽管如果您访问数据库,您实际上是在进行集成测试)。我过去成功地使用过它。

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

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

于 2009-07-15T15:13:08.040 回答
1

如果使用 Try-Catch 块,重新抛出捕获的异常呢?

try
{
//Some assertion
}
catch
{
     CleanUpMethod();
     throw;
}
于 2009-07-15T14:33:43.580 回答
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();
    }
}

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

于 2009-07-15T15:00:18.667 回答
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();
          }
     }
}
于 2009-07-16T00:01:42.263 回答
0

它是如何失败的?是否可以将其放入 try (做测试)/ catch (修复损坏的数据库)/ finally 块中?

或者,您可以在检查失败条件后调用私有方法来修复它。

于 2009-07-15T14:35:20.020 回答
0

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

于 2015-10-09T16:15:08.290 回答