多线程单元测试

6
我是一名有用的助手,可以为您翻译文本。
我有一个 C# 方法,将使用不同的线程多次调用。因此,我想创建一个单元测试,以在几个线程上测试此方法,但我不确定是否做得正确。
这是我的单元测试,没有使用线程:
    [TestMethod]
    public void FromLocalPoints()
    {
        var projectedCoordinates = this.ConvertFromLocalPoints();
        foreach (var projectedCoordinate in projectedCoordinates)
        {
            Debug.Write(projectedCoordinate.X);
            Debug.Write("; ");
            Debug.WriteLine(projectedCoordinate.Y);
        }
    }

this.ConvertFromLocalPoints() 是我想测试的实际方法。

我已创建了一个委托、一个事件和一个处理程序:

public delegate void ReprojectCompleteHandler(IEnumerable<Coordinate> projectedCoordinates);
public event ReprojectCompleteHandler ReprojectCompleteEvent;
private void ReprojectHandler(IEnumerable<Coordinate> projectedCoordinates)
{
        Debug.WriteLine("Conversion is complete");
}

在我的TestSetup中,我监听我的事件:
    [TestInitialize]
    public void TestSetup()
    {
        this.ReprojectCompleteEvent += this.ReprojectHandler;
    }

我的单元测试如下:

    [TestMethod]
    public void FromLocalPointsThreaded()
    {
        // Call FromLocalPoints multiple times in separate threads to check if it is thread safe
        for (var i = 0; i < 10; i++)
        {
            var myThread = new Thread(this.ConvertFromLocalPointsThreaded);    
        }

        Debug.WriteLine("FromLocalPointsThreaded is done");
    }

    private void ConvertFromLocalPointsThreaded()
    {
        var projectedCoordinates = this.ConvertFromLocalPoints();

        // Send result to delegate/event:
        if (this.ReprojectCompleteEvent != null)
        {
            this.ReprojectCompleteEvent(projectedCoordinates);
        }
    }

当我运行这个单元测试时,输出中会有一次“FromLocalPointsThreaded is done”,但没有“Conversion is complete”。我漏掉了什么才能让它正常工作?或者我应该使用不同的方法?
更新:我们正在切换库,用于执行实际转换。旧库不是线程安全的,所以我们添加了锁。新库应该是线程安全的,所以我想删除锁。但我需要一个单元测试来证明我们使用新库的方法真的是线程安全的。

没有任何东西告诉主线程等待生成的线程。它只是在它们做任何工作之前退出。看看Thread.Join。 - mrmcgreg
你的代码中并没有实际“启动”线程。 - user585968
除了以上回答(mrmcgreg和Micky Duncan的回答)之外,如果您希望在并行环境中测量代码的性能,您可能希望在一个运行中启动线程函数。请参见我在另一个问题的答案中(https://dev59.com/qV0a5IYBdhLWcg3wPGeQ#30568340),了解如何在C#中完成此操作。 - mg30rg
2个回答

5
好的Unit测试的一个特性是需要可重复性。每次运行时,它应该与以前的运行方式相同。但这在线程中是不可能的。例如,测试可能正常运行999次,但1次会出现死锁。这意味着线程不仅无用,而且会给你错误的信心,即你的代码实际上没有死锁。
为了测试线程安全性,还有几种方法可以实现: 模拟线程 将线程代码提取并替换为可以在测试中模拟的抽象化内容。这样,单元测试代码将模拟多个线程,而线程本身不会成为问题。但这需要您知道所有可能的路径,使其对于任何复杂的事情都变得无用。
这也可以通过使用不可变数据结构和纯方法来帮助。然后,测试的代码被限制为提供线程之间同步的小部分代码。 耐久测试 设计测试以长时间运行,不断产生新线程并调用代码。如果它在没有死锁的情况下运行了数小时,那么您就可以获得很好的信心,表明没有死锁。这不能作为正常测试套件的一部分运行,也不能给您100%的信心。但这比尝试枚举线程可以交互的所有可能方式要实际得多。

我猜测下投票的原因可能是因为楼主已经知道了在单元测试中使用线程的缺点,而他特别询问如何测试“有线程”的情况。话虽如此,你对于“为什么这样做不好”的解释值得点赞。 - Johann Gerell
感谢您的建议。我认为耐久性测试选项对我们有用,因为我想证明我的代码是线程安全的(我已更新我的问题)。但是为了证明它,我需要在输出中显示结果,这就是为什么我创建了处理程序。那部分似乎不起作用。 - Paul Meems
@PaulMeems 在你使用某种形式的方法之前,你无法证明你的代码是线程安全的。这需要完全不同于你现在所做的方法。 - Euphoric

2

根据建议,我改变了我的方法。现在我正在使用:

    [TestMethod]
    public void FromLocalPointsParallel()
    {
        var loop = new int[10];
        Parallel.ForEach(
            loop,
            item =>
                {
                    var projectedCoordinates = this.ConvertToLocalPoints();
                    foreach (var projectedCoordinate in projectedCoordinates)
                    {
                        var x = projectedCoordinate.X;
                        var y = projectedCoordinate.Y;
                        Assert.IsFalse(double.IsInfinity(x) || double.IsNaN(x), "Projected X is not a valid number");
                        Assert.IsFalse(double.IsInfinity(y) || double.IsNaN(y), "Projected Y is not a valid number");
                    }
                });
    }

这个单元测试正在做我需要的事情:在不同的线程中调用我的转换方法。它已经证明了它的用途,因为我发现我使用的一个字典不是线程安全的。我已经修复它,现在我很有信心新的库是线程安全的。

感谢大家的帮助和建议。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接