2009-07-15 74 views
35

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

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

我们不想在每个测试中编写清理代码,现在我们已经这样做了。我想在拆解时进行清理,但只有在测试失败的时候,因为清理工作可能会很昂贵。

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

+0

如果您不想在每次测试中进行清理,或者每次测试后都不会进行清理。抱歉。 – rein 2009-07-15 14:47:09

回答

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目录中。 Mine被放置在TestDriven的NUnit runner旁边。NET目录:

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

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

编辑另一件事是清理方法必须static

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

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

希望有所帮助。

1

有关使用try-catch块,重新抛出异常捕获什么?

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

这不是每个测试都能解释吗? “我们不想在每次测试中都写清理代码” – 2009-07-15 14:53:37

+0

是的,我发布了第二个解决方案,以更好地解决更新的意见 – 2009-07-15 15:01:28

0

它是如何失败的?是否有可能把它放在一个尝试(做测试)/捕获(修复损坏的数据库)/ finally块?

或者你可以调用一个私人方法来解决它,当你检查你的失败情况。

2

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

这一属性用于一个 的TestFixture内,以提供一组通用的被 运行每个测试方法之后执行 功能。

更新:根据的评论和更新的问题,我说你可以使用拆卸属性和使用私有变量,以表明该方法的内容是否应该解雇。

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

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

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

+0

我认为他知道拆解,他想做的是在拆卸时是否意识到是否有任何测试在夹具失败的情况下,如果没有,他可以跳过数据库重建。 – 2009-07-15 14:41:34

+0

那么,可以生成测试数据以独立于待测试的测试,但要为每个测试设置数据库需要更长的时间,然后在测试中编写清理代码。 我的目标是减少构建时间和清理设置/拆卸代码。不,嘲笑不是这些测试的选项,我们已经在我们的单元测试中使用它们... – bh213 2009-07-15 14:55:23

+0

我明白你的困境,但我没有任何进一步的想法.... – 2009-07-15 15:01:28

2

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

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

1

我会像现在这样做phsr建议,当你能负担得起,重构测试,以便他们永远不必依赖于另一个测试需要的相同数据,或者甚至更好地抽象数据访问层并嘲笑结果从那个数据库。这听起来像是你的测试是相当昂贵的,你应该对数据库中的数据库和业务逻辑执行所有的查询逻辑,你并不关心返回的结果是什么。

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

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

1

一个选项没有提及到目前为止是包装测试起来一个TransactionScope对象,因此测试从不向数据库提交任何内容时会发生什么并不重要。

这是some details on the technique。如果你在单元测试和交易范围上进行搜索,你可能会发现更多(尽管如果你打了一个数据库你真的在做集成测试)。我过去成功地使用了它。

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

编辑 - 我刚才注意到雷海耶斯的回答也类似于我的。

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(); 
      } 
    } 
} 
60

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

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