views:

63

answers:

2

I'm curious to know if there's any built-in mechanism to retry tests in the Visual Studio 2008 unit testing framework for C#. Case in point, I have a C# unit test which looks something like:

[TestMethod]
public void MyMethod() {
    DoSomething();
    Assert.Something();
}

Now, occasionally DoSomething() performs badly; in that case I would like to rerun the DoSomething() method before reaching the assertion. Obviously I can do something like:

...
do {
    Initialization();
    DoSomething();
} while (PerformedOK() == false);
Assert.Something();
...

Though this is a bit cumbersome because of the added loop and repeating the test initialization which would otherwise be completely handled by other methods / class constructor.

My question is whether there is a more convenient mechanism for retrying a test, something like:

DoSomething();
if (PerformedOK() == false) Retry();
else Assert.Something();

which will automatically retry the test without registering it as a failure, while performing all the regular initialization code as usual.

+7  A: 

Seriously...

"occasionally DoSomething() performs badly"

A test should be green every time. If the tested code sometimes perform "badly", then you need to fix your code, isolating the different behavior. You should have two test, one where it Asserts correct when DoSomething fails (and is supposed to fail), and one where it Asserts correct when DoSomething is ok (and is supposed to be ok).

Having retry logic in a test is just wrong imo. You should always Assert on the expected outcome, and you should be able to isolate and instrument your code to return what you expect.

[Edit - added some code which could be used for a retry loop]

You could create a loop wrapper which takes whatever method in and calls it X number of times, or until it succeeds. You could also have the Loop function call your init, or pass it as a separate argument. The method could also return a bool if successful. Change the signature to fit your needs.

[TestMethod]
public void something()
{
   Loop.LoopMe(TestMethod,3);            
   Assert.Something();
}

class Loop
{
    public static void LoopMe(Action action, int maxRetry )
    {
        Exception lastException = null;
        while (maxRetry > 0)
        {
            try
            {
                action();
                return;
            }
            catch (Exception e)
            {
                lastException = e;
                maxRetry--;                    
            }                
        }
        throw lastException;
    }

}

Mikael Svenson
I understand your opinion and appreciate the advice, but there are sometimes other considerations in play.
Oak
@Oak: Considerations where you are relying on third party systems? If it's an integration test, retry logic could be ok, but I still think it should be in your code base, not in your test.
Mikael Svenson
If "other considerations" are indeed third party, consider using mocks and/or stubs. Don't think up better ways of doing the wrong thing.
@lotsoffreetime: "better ways of doing the wrong thing", may I quote you?
John Saunders
@lotsoffreetime: Totally agree. It's better to mock the remote system for your tests than to actually call them. And I was thinking a bit broad of the term integration testing, as to test if a live system actually delivers data. Guess a better term would be "keep-alive" testing. But systems like Fitnesse are more suited for this.
Mikael Svenson
@Mikael @lots actually it's more of a pragmatic approach here. The cost of making `DoSomething()` become deterministic is *high*, the cost of retrying is low, and the aspect which makes `DoSomething()` be non-deterministic is irrelevant to this specific test case. Performance also comes into play.
Oak
@oak: Which tells you the retry loop should either be inside DoSomething or in some wrapper lib code handling the exception which triggers a retry, not in the test since you expect it to work as long as you retry it. How do you handle it in production?
Mikael Svenson
+1  A: 

Your second example is almost the same lines of code and same complexity. There are tons of ways to skin it, you could not that I am advocating it use recursion.

[TestMethod]
public void MyMethod() {
   bool success = DoSomething();
   Assert.IsTrue(success);
}

public boolean DoSomething(){
    //Do whatever

    if(performedOk){
       return true;
    }else{
        //find a way to stop it.
    }

}

But the point is it is a unit test. If something is causing it to go wrong, you need to find a way isolate your test, so that it is in a controlled environment.

Unless you have a requirement that says, test should pass eventually. The best retry logic you should use, is after it fails. Click the test and hit run again.

Nix