Pau*_*ems 7 c# multithreading unit-testing
我有一个 C# 方法,将使用不同的线程多次调用该方法。所以我想创建一个单元测试来在多个线程上测试此方法,但我不确定我做得是否正确。
这是我没有线程的单元测试:
[TestMethod]
public void FromLocalPoints()
{
var projectedCoordinates = this.ConvertFromLocalPoints();
foreach (var projectedCoordinate in projectedCoordinates)
{
Debug.Write(projectedCoordinate.X);
Debug.Write("; ");
Debug.WriteLine(projectedCoordinate.Y);
}
}
Run Code Online (Sandbox Code Playgroud)
this.ConvertFromLocalPoints() 正在调用我想测试的实际方法。
我创建了一个委托、一个事件和一个处理程序:
public delegate void ReprojectCompleteHandler(IEnumerable<Coordinate> projectedCoordinates);
public event ReprojectCompleteHandler ReprojectCompleteEvent;
private void ReprojectHandler(IEnumerable<Coordinate> projectedCoordinates)
{
Debug.WriteLine("Conversion is complete");
}
Run Code Online (Sandbox Code Playgroud)
在我的 TestSetup 中,我监听我的事件:
[TestInitialize]
public void TestSetup()
{
this.ReprojectCompleteEvent += this.ReprojectHandler;
}
Run Code Online (Sandbox Code Playgroud)
我的单元测试是:
[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);
}
}
Run Code Online (Sandbox Code Playgroud)
当我运行此单元测试时,我在输出中得到一次“FromLocalPointsThreaded 已完成”,但没有“转换已完成”。
为了让这个工作我缺少什么?或者我应该使用不同的方法?
更新 我们目前正在切换正在进行实际转换的库。旧库不是线程安全的,因此我们添加了锁。新库应该是线程安全的,所以我想删除锁。但我需要一个单元测试来证明我们使用新库的方法确实是线程安全的。
良好的单元测试的特性之一是它需要可重复。每次运行时,它都应该以与之前相同的方式运行。这对于线程来说是不可能的。例如,测试可能正确运行 999 次,但遇到死锁 1 次。这意味着该线程不仅无用,而且会给您一种错误的信心,认为您的代码实际上没有死锁。
为了测试线程安全性,有几种其他方法可以实现:
模拟线程
提取线程代码并将其替换为可以在测试中模拟的抽象。这样,单元测试代码将模拟多个线程,而线程本身不会成为问题。但这需要您知道线程可以通过您的代码的所有可能路径,这使得它对于任何复杂的事情都毫无用处。
使用不可变数据结构和纯方法也可以帮助实现这一点。然后,测试的代码仅限于提供线程之间同步的一小部分代码。
耐力测试
将测试设计为运行极长的时间,始终生成新线程并调用代码。如果它运行几个小时而没有出现死锁,那么您就可以确信不存在死锁。这不能作为正常测试套件的一部分运行,也不能给您 100% 的信心。但它比尝试枚举线程交互的所有可能方式要实用得多。
正如建议的那样,我改变了我的方法。我现在正在使用:
[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");
}
});
}
Run Code Online (Sandbox Code Playgroud)
这个单元测试正在做我需要的事情:在不同的线程中调用我的转换方法。它已经证明了它的用途,因为我检测到我使用的字典不是线程安全的。我修复了它,现在我确信新库是线程安全的。
感谢大家的帮助和建议。