xUnit test deadlock

2019-08-15 05:35发布

问题:

When I run my xUnit unit tests I sometimes get an error message like "Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen as the deadlock victim" on one or more of the tests, seemingly randomly. If I re-run any failing test on its own it passes.

What should I do to prevent this? Is there an option to run the tests one-after-another instead of all at once?

(N.B. I'm running the tests over the API methods in my ASP.Net 5 MVC controllers under Visual Studio 2015)

Here's an example of one of my occasionally failing tests:

[Fact]
private void TestREAD()
{
    Linq2SQLTestHelpers.SQLCommands.AddCollections(TestCollections.Select(collection => Convert.Collection2DB(collection)).ToList(), TestSettings.LocalConnectionString);
    foreach (var testCollection in TestCollections)
    {
        var testCollectionFromDB = CollectionsController.Get(testCollection.Id);
        Assert.Equal(testCollection.Description, testCollectionFromDB.Description);
        Assert.Equal(testCollection.Id, testCollectionFromDB.Id);
        Assert.Equal(testCollection.IsPublic, testCollectionFromDB.IsPublic);
        Assert.Equal(testCollection.LayoutSettings, testCollectionFromDB.LayoutSettings);
        Assert.Equal(testCollection.Name, testCollectionFromDB.Name);
        Assert.Equal(testCollection.UserId, testCollectionFromDB.UserId);
    }
}

There are two methods the test calls, here's the controller method:

[HttpGet("{id}")]
public Collection Get(Guid id)
{
    var sql = @"SELECT * FROM Collections WHERE id = @id";
    using (var connection = new SqlConnection(ConnectionString))
    {
        var collection = connection.Query<Collection>(sql, new { id = id }).First();
        return collection;
    }
}

and here's the helper method:

public static void AddCollections(List<Collection> collections, string connectionString)
{
    using (var db = new DataClassesDataContext(connectionString))
    {
        db.Collections.InsertAllOnSubmit(collections);
        db.SubmitChanges();
    }
}

(Note that I'm using Dapper as the micro-ORM in the controller method and so, to avoid potentially duplicating errors in the test, I'm using LINQ to SQL instead in the test to set-up and clean-up test data.)

There are also database calls in the unit test's class's constructor and Dispose method. I can add them to the post if needed.

回答1:

OK, so looks like a plain vanilla case of deadlocks in your app and the need to handle that - what is your plan on the app side?

The tests and their data rigging can potentially fall prey to the same thing. xUnit doesnt have anything to address this and I'd strongly argue it shouldnt.

So in both the test and the app, you need failure/retry management.

For a web app, you have a fire them a picture of a whale and let them try again pattern but ultimately you want a real solution.

For a test, you don't want whales and definitely want to handle it, i.e. not be brittle.

I'd be using Poly to wrap retry decoration around anything in either the app or the tests that's prone to significant failures -- your exercise is to figure out what are the significant failures in your context.


Under normal circumstances a database with a single reader/writer operating synchronously shouldn't deadlock. Analysing why it happens is a matter of doing the analysis on the DB side. The tools that side would also likely quickly reveal to you if e.g. you have some aspect of your overall System Under Test which is resulting in competing work.

(Obviously your snippets are incomplete as there is a disconnect between CollectionsController.Get(testCollection.Id) and the fact that the controller method is not static - the point of this discussion should not be down at that level IMO though)