是否有可能模拟/假冒失败的锁定导致测试失败?

时间:2013-02-26 18:57:39

标签: c# unit-testing thread-safety locking mocking

我正在编写一个围绕Dictionary设计为线程安全的瘦包装器。因此,需要一些锁定,并且大多数逻辑都是确保正确锁定事物并以线程安全的方式访问。

现在,我正在尝试进行单元测试。我想要进行单元测试的一件大事就是锁定行为,以确保它是正确的。但是,我从未在任何地方看到过这种情况,所以我不确定如何去做。另外,我知道我可以使用一堆线程将内容扔到墙上,但是通过这种类型的测试,无法保证它在错误时会失败。这取决于操作系统定义的线程调度行为。

有哪些方法可以确保我的锁定行为在单元测试中是正确的?

3 个答案:

答案 0 :(得分:3)

锁定只是一个实现细节。您应该模拟竞争条件本身并测试数据是否在测试中没有失去其完整性。

如果您决定更改字典的实现并使用其他同步原语,这将是一个好处。

测试锁定将证明您正在调用您期望的锁定语句。虽然使用模拟竞争条件进行测试可能会显示出您不希望同步的位置。

答案 1 :(得分:2)

我基本上最终做了@Alexei说的话。更具体地说,这就是我所做的:

  1. 创建一个PausingDictionary,基本上每个方法都有一个回调,但是只是传递给常规字典
  2. 抽象我的代码(使用DI等),以便我可以在测试时使用PausingDictionary而不是常规Dictionary
  3. 在我的单元测试中添加了两个ConcurrentDictionaries。一个名为“访问”,一个名为“GoAhead”。 thiis的关键是"action"+Thread.GetHashCode().ToString()(每个回调的动作不同)的组合
  4. 将所有内容初始化为false并添加了一些扩展方法,以便更轻松地使用它
  5. 设置字典的回调以将线程的Accessed设置为true,但它会在回调中等待,直到GoAhead为真
  6. 在单元测试中启动了两个线程。一个线程会访问字典,但由于GoAhead对于该线程是假的,它会坐在那里。然后第二个线程也会尝试访问字典
  7. 我有一个断言,该线程的Accessed是假的,因为我的代码应该将其锁定。
  8. 还有更多的东西。我还需要模拟一个IList,但我不认为我会。这些单元测试尽管很有价值,但绝对不是世界上最容易编写的。除了设置代码和虚假接口实现等,每个测试最终都是大约25行非样板代码。锁定很难。证明锁定有效甚至更难。但令人惊讶的是,这种模式可以让你测试几乎任何场景。但是,它非常冗长,并不适合进行漂亮的测试

    所以,尽管编写测试很难,但这种方法非常有效。当我删除锁定时始终失败,当我添加锁定时,它会一直通过。

    编辑:

    我认为这种“控制交错”线程的方法也可以测试线程安全性,因为你为每个可能的交错编写了一个测试。使用一些代码这是不可能的,但我只是想说这绝不仅限于锁定代码。您可以采用相同的方法来一致地复制线程安全故障,例如foo.Contains(x)然后var tmp=foo[x]

答案 2 :(得分:1)

我不认为Dictionary本身可以实现可靠的测试 - 你的目标是进行2次并行运行,这是不可靠的。

您可以尝试使用自定义词典(即从常规词典派生)并为所有Get / Add方法添加回调。你可以根据需要在2个线程中延迟调用...你需要在2个线程之间进行单独的同步,以使你的测试代码以你想要的方式运行,而不是一直死锁。