NUnit - 测试失败后的清理

发布于 2024-07-26 20:48:13 字数 835 浏览 9 评论 0原文

我们有一些访问数据库的 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() 的实现

We have some NUnit tests that access the database. When one of them fails it can leave database in inconsistent state - which is not an issue, since we rebuild database for every test run - but it can cause other tests to fail in the same run.

Is it possible to detect that one of the tests failed and perform some sort of cleanup?

We don't want to write cleanup code in every test, we already do that now. I'd like to perfrom cleanup in Teardown but only if test failed, as cleanup might be expensive.

Update: To clarify - I would like tests to be simple and NOT include any cleanup or error handling logic. I also don't want to perform database reset on every test run - only if test fails. And this code should probably be executed in Teardown method but I am not aware of any way to get info if test we are currently tearing down from failed or was successful.

Update2:

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

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

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

I am looking for implementation of HasLastTestFailed()

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

帅气尐潴 2024-08-02 20:48:13

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

Since version 2.5.7, NUnit allows Teardown to detect if last test failed.
A new TestContext class allows tests to access information about themselves including the TestStauts.

For more details, please refer to http://nunit.org/?p=releaseNotes&r=2.5.7

[TearDown]
public void TearDown()
{
    if (TestContext.CurrentContext.Result.Status == TestStatus.Failed)
    {
        PerformCleanUpFromTest();
    }
}
月野兔 2024-08-02 20:48:13

这个想法引起了我的兴趣,所以我做了一些挖掘。 NUnit 不具备这种开箱即用的能力,但 NUnit 提供了一个完整的可扩展性框架。 我发现这篇关于扩展 NUnit 的精彩文章< /a> - 这是一个很好的起点。 经过尝试后,我想出了以下解决方案:如果夹具中的测试之一失败,将调用用自定义 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 目录,它不在那里)

编辑另一个问题是清理方法必须是静态的!

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

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

希望有帮助。

This idea got me interested, so I did a little digging. NUnit doesn't have this ability out of the box, but there is a whole extensibility framework supplied with NUnit. I found this great article about extending NUnit - it was a good starting point. After playing around with it, I came up with the following solution: a method decorated with a custom CleanupOnError attribute will be called if one of the tests in the fixture failed.

Here's how the test looks like:

  [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");
    }
  }

where the attribute is simply:

  [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
  public sealed class CleanupOnErrorAttribute : Attribute
  {
  }

And here is how it's executed from the 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);
    }
  }
}

But here's the tricky part: the addin must be placed in the addins directory next to the NUnit runner. Mine was placed next to the NUnit runner in TestDriven.NET directory:

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

(I created the addins directory, it wasn't there)

EDIT Another thing is that the cleanup method needs to be static!

I hacked together a simple addin, you can download the source from my SkyDrive. You will have to add references to nunit.framework.dll, nunit.core.dll and nunit.core.interfaces.dll in the appropriate places.

A few notes: The attribute class can be placed anywhere in your code. I didn't want to place it in the same assembly as the addin itself, because it references two Core NUnit assemblies, so I placed it in a different assembly. Just remember to change the line in the CleanAddin.cs, if you decide to put it anywhere else.

Hope that helps.

歌入人心 2024-08-02 20:48:13

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

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

Whilst it might be possible to coerce nUnit into doing this it isn't the most sensible design, you could always set a temporary file somewhere and if that file exists, run your cleanup.

I would recommend changing your code so that you have database-transactions enabled and at the end of the test, simply revert the database to the original state (e.g. discard the transaction that represents your unit-tests).

醉生梦死 2024-08-02 20:48:13

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

该属性在
TestFixture提供一组通用的
之后执行的函数
运行每个测试方法。

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

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

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

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

Yes, there is. You can use the Teardown attribute which will teardown after each test. You'd want to apply that Database "reset" script that you have and teardown and re-setup before and after each test.

This attribute is used inside a
TestFixture to provide a common set of
functions that are performed after
each test method is run.

Update: Based on the comments and update to the question, I'd say you can use the teardown attribute and use private variables to indicate whether the method contents should fire.

Though, I did also see that you don't want any complex logic or error handling code.

Given that, I'd think that a standard Setup/Teardown would work best for you. It doesn't matter if there is an error and you don't have to have any error handling code.

If you need to special clean up because the next tests depends on successful completion of the current test, I'd suggest to revisit your tests -- they probably shouldn't depend on each other.

冰葑 2024-08-02 20:48:13

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

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

What about using a Try-Catch block, rethrowing the exception caught?

try
{
//Some assertion
}
catch
{
     CleanUpMethod();
     throw;
}
岁吢 2024-08-02 20:48:13

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

您还可以更好地测试您的异常处理。

I would do like phsr suggests for now and when you can afford it, refactor the tests so that they never have to rely on the same data that another test needs or even better abstract the data access layer and mock the results coming from that database. It sounds like your tests are rather expensive and you you should do all your query logic on the database and business logic in your assembly you don't really care what the results are that are returned.

You will also be able to test your ExceptionHandling a lot better.

蓝天 2024-08-02 20:48:13

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

Another option is to have a special function that will throw your exceptions, that sets a switch in the testfixture that says an exception occured.

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;
     }
}

Then using your example:

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

The only issue here is that the Stack trace will lead to the CleanOnErrorFixture

夜清冷一曲。 2024-08-02 20:48:13

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

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

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

编辑-我刚刚注意到雷·海耶斯的答案也与我的类似。

One option not mentioned so far is to wrap the test up in a TransactionScope object, so it doesn't matter what happens as the test never commits anything to the DB.

Here's some details on the technique. You can probably find more if you do a search on unit testing and transactionscope (though you are really doing integration testing if you hit a DB). I've used it successfully in the past.

This approach is simple, does not require any cleanup and ensures that tests are isolated.

Edit- I've just noticed Ray Hayes answer is also similar to mine.

书间行客 2024-08-02 20:48:13

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

You can add a [TearDown] method with
if (TestContext.CurrentContext.Result.Status != TestStatus.Passed)
some code to be executed if test failed.

软的没边 2024-08-02 20:48:13

怎么会失败呢? 是否可以将其放入 try(进行测试)/catch(修复损坏的数据库)/finally 块中?

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

How does it fail? Is it possible to put it in a try (do test) / catch (fix broken db) / finally block?

Or you could call a private method to fix it when you've checked your fail condition.

没企图 2024-08-02 20:48:13

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

I'm not saying this is a great idea, but it should work.
Remember that assertion failures are just exceptions. Also don't forget there is also a [TestFixtureTearDown] attribute that runs just once after all tests in the fixture have run.

Using those two facts you can write something like setting a flag if a tests failed and checking the value of the flag in the test fixture tear down.

I don't recommend this, but it would work. You aren't really using NUnit as intended, but you can do it.


[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();
          }
     }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文