我正在开发自己的简单IoC库,我希望这个库可以保证线程安全。
我的典型单元测试如下:
[TestMethod]
public void TestContainerUseExistingObjectFromLifetimeManagerWithFactoryMethod()
{
// Arrange
var container = new FsContainer();
container
.For<IRepository>()
.Use(ctx => new Repository("sql_connection_string"), new ContainerControlledLifetimeManager());
// Act
var first = container.Resolve<IRepository>();
var second = container.Resolve<IRepository>();
// Arrange
Assert.AreEqual(first.ConnectionString, "sql_connection_string");
Assert.AreEqual(second.ConnectionString, "sql_connection_string");
Assert.AreSame(first, second);
}
在我尝试下一步测试之前,这很有效:
[TestMethod]
public async Task TestMultiThreadContainerUseExistingObjectFromLifetimeManagerWithFactoryMethodAsync()
{
// Arrange
var container = new FsContainer();
container
.For<IRepository>()
.Use(ctx => new Repository("sql_connection_string"), new ContainerControlledLifetimeManager());
// Act
var instances = await Task.WhenAll(
Task.Run(() => container.Resolve<IRepository>()),
Task.Run(() => container.Resolve<IRepository>())
);
var first = instances[0];
var second = instances[1];
// Arrange
Assert.AreEqual(first.ConnectionString, "sql_connection_string");
Assert.AreEqual(second.ConnectionString, "sql_connection_string");
Assert.AreSame(first, second);
}
此测试告诉我,我遇到Assert.AreSame
的问题(我的第一个和第二个实例不相同)。
我已经在Resolve方法中实现了lock
语句,一切都开始正常工作。
问题:这是以single
和multi-thread
方式复制大部分功能以测试线程安全性的正确方法吗?它有意义吗?
答案 0 :(得分:1)
在大多数情况下,如果不是不可能的话,测试螺纹安全性很难。
您的第二个测试用例可能会暴露一些问题但不保证代码行为正常。即它可以通过创建每线程实例而不是一个全局实例来检测代码违反设计,如果代码一致地执行它,但是很难抓住对共享字典的并行访问(或代码存储单例的任何集合)。你很幸运能够真正发现测试问题 - 可能是确保单例实例足够慢以让两个线程启动并解决问题的代码。如果代码快速且有些正确(即在没有锁定的情况下使用double-checked locking),测试不太可能发现错误。
对于编写线程安全的代码,您应该从已知的正确保守代码开始(即只是锁定所有操作),然后进行小的更改,以便通过代码审查证明正确性(并且具有帮助验证功能的测试)。
如果您关注特定的代码段,有时您可以故意减慢代码(即构造函数/回调中的Sleep(1000)
)以强制执行代码的特定时序。