简体   繁体   中英

How to abort unit test from another thread?

I have unit test sample code that basically runs 2 threads: the main test thread and another thread I'm launching that is supposed to fail the test execution after some time (this is basically a timeout thread)

Code is as follows:

[TestClass]
public class SomeTestClass
{
    [TestInitialize]
    public void BeforeTest()
    {
        var task = new Task(abortIfTestStilRunsAfterTimeout);
        task.Start();
    }

    [TestMethod]
    public void TestMethod()
    {
        Thread.Sleep(5000);
    }

    private void abortIfTestStilRunsAfterTimeout()
    {
        Assert.Fail("timeout passed!");

    }
}

Well, I was expecting that the TestMethod() test should fail but what actually happens is that the task thread that runs the Assert.Fail method gets an exception while the other thread keeps on running and the test passes.

I'm looking for a way to fail the test method

You can try and get a reference to the test thread and call Abort() on it. You can pass an exception state object to Abort() which you can use to pass a fail message:

[TestClass]
public class SomeTestClass
{
    Thread testThread;

    [TestInitialize]
    public void BeforeTest()
    {
        testThread = Thread.CurrentThread;
        var task = new Task(abortIfTestStilRunsAfterTimeout);
        task.Start();
    }

    [TestMethod]
    public void TestMethod()
    {
        try
        {
            Thread.Sleep(5000);
        }
        catch (ThreadAbortException e)
        {
             Assert.Fail((string)e.ExceptionState);
        }
    }

    private void abortIfTestStilRunsAfterTimeout()
    {
        testThread.Abort("timeout passed!");
    }
}

In case you do not want to modify some 100 tests you can use a tool like PostSharp to modify your test cases by inserting the try {} catch {} logic around each test case for you. All it boils down to is to write an attribute and decorate you test assembly with it (it's called Aspect Oriented Programming and the framework in PostSharp for that is called Laos).

The idea is simple - just run main test logic in a task/thread, and just put timeout handling code in the test method/test initialize. Some kind of Inversion of Control :

// Test class level
ManualResetEvent mre = new ManualResetEvent(false);                                

[TestMethod]     
public void TestMethod()     
{                     
    // start waiting task
    Task task = Task.Factory.StartNew(() =>
        {
            // Test Body HERE!
            // ... 

            // if test passed - set event explicitly
            mre.Set();
        });


    // Timeout handling logic, 
    // !!! I believe you can put it once in the TestInitialize, just check
    // whether Assert.Fail() fails the test when called from TestInitialize
    mre.WaitOne(5000);

    // Check whether ManualResetEvent was set explicitly or was timeouted
    if (!mre.WaitOne(0))
    {
        task.Dispose();
        Assert.Fail("Timeout");
    }                
}                    

PS: Regarding WaitOne(0) trick, MSDN :

If millisecondsTimeout is zero, the method does not block. It tests the state of the wait handle and returns immediately.

This post is somewhat old, but recently I was having a similar issue.

My solution is different:

use the NUnit TimeoutAttribute



See NUnit documentation here:
NUnit 2.5: https://nunit.org/docs/2.5/timeout.html
NUnit 3.0: https://github.com/nunit/docs/wiki/Timeout-Attribute

A solution leveraging TimeoutAttribute and/or TestContext.CancellationTokenSource :

[TestClass]
public class SomeTestClass
{
    private Task abortTask;

    // Test runner will set this automatically.
    public TestContext Context { get; set; }

    [TestInitialize]
    public void BeforeTest()
    {
        this.abortTask = Task.Run(
            () => 
            {
                this.WaitForAbort();
                this.Context.CancellationTokenSource.Cancel();
            });
    }

    [TestMethod]
    public Task TestMethod1()
    {
        // Cancellation triggered by manual abort logic.
        this.TestWithCancellation(Context.CancellationTokenSource.Token);
    }

    [TestMethod]
    [Timeout(5000)]
    public Task TestMethod2()
    {
        // Cancellation triggered automatically by the runner after 5 sec.
        this.TestWithCancellation(Context.CancellationTokenSource.Token);
    }

    private void WaitForAbort()
    {
        // Concurrently check for some abort condition...
    }

    private void TestWithCancellation(CancellationToken cancellationToken)
    {
        // Do your test, but pass/respect the token!
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM