views:

732

answers:

9

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()

+1  A: 

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

try
{
//Some assertion
}
catch
{
     CleanUpMethod();
     throw;
}
phsr
Wouldn't that be explicate to each test? "We don't want to write cleanup code in every test"
Ray Hayes
Yes, I have posted a second solution to better address the updated comments
phsr
A: 

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.

DeletedAccount
+2  A: 

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.

Frank V
I think he knows about Teardown, what he wants to do is be aware in teardown whether any tests in the fixture failed, if not, he can skip the database rebuild.
Ray Hayes
Well, it is possible to generate test data to be independant from test to test, but it would take much longer to setup the db for each test then it is to write cleanup code in tests.My goal is to reduce build time and clean-up setup/teardown code. And, no, mocking is not an option for those tests, we already use them in our unit tests...
bh213
I understand your dilemma but I don't have any further ideas....
Frank V
See my solution with the Abstract Class for an expansion of this idea
phsr
From nunit docs: "... TestFixtureTearDown ... will not run if a TestFixtureSetUp method fails or throws an exception."
Precipitous
+2  A: 

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).

Ray Hayes
There can be multiple transactions and we also have tests that use REST/WCF where transactions don't exist. This are automated tests we are talking about, not unit tests.
bh213
It nUnit the right thing to use then? You don't want the default behaviour of a unit testing framework and you also say they're not unit-tests.... sounds like nUnit is the wrong tool! Why not just have some console applications and have a batch file or an msbuild file call them in sequence?
Ray Hayes
@Ray- my thoughts too so +1. I didn't see your answer initially. My answer expands on how to do this with TransactionScope.
RichardOD
+1  A: 

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.

AutomatedTester
+1  A: 

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

phsr
+1 but this still requires error handling, which bh213 is trying to stay away from. It's a good solution, but doesn't quite fit with what is being asked for.... which is probably the case with most of the answers given thus far...
Frank V
In his example, he had the "bad test" throwing an exception. The abstract class provides the same functionality as the example, along with implementing the "HasLastTestFailed" method he desires. Inheriting from this class does not add the error handling like a try catch block. This example only calls for a change in how to throw the exception (using a methond instead of throw)
phsr
-1 requires system under test to call method within test code (CleanOnErrorFixture.ThrowException)
Frank Schwieterman
+1  A: 

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.

RichardOD
+8  A: 

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.

hmemcpy
You implementation has only integration tests. Creating a variation of it, I ran into various problems. Care to answer my question on how to write a unit test for an Nunit addin? http://stackoverflow.com/questions/3927349/how-to-write-a-nunit-test-for-an-nunit-add-in
Precipitous
A: 

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