12

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

现在,我正在尝试对其进行单元测试。我想单元测试的一件大事是锁定行为,以确保它是正确的。但是,我从来没有在任何地方看到过这种情况,所以我不知道该怎么做。另外,我知道我可以用一堆线把东西扔到墙上,但是通过这种类型的测试,不能保证它出错时会失败。这取决于操作系统定义的线程调度行为。

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

4

3 回答 3

3

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

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

测试锁定将证明您正在调用您期望的锁定语句。虽然使用模拟的比赛条件进行测试可能会发现您没想到会同步的地方。

于 2013-02-26T19:03:49.430 回答
2

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

  1. 制作一个PausingDictionary基本上只有每个方法的回调,否则只是传递给常规字典
  2. 抽象我的代码(使用 DI 等),以便在测试时可以使用 PausingDictionary 而不是常规 Dictionary
  3. 在我的单元测试中添加了两个 ConcurrentDictionaries。一种称为“Accessed”,一种称为“GoAhead”。thisis 的关键是"action"+Thread.GetHashCode().ToString()(其中每个回调的操作不同)的组合
  4. 将所有内容初始化为 false 并添加了一些扩展方法以使其更容易使用
  5. 设置字典的回调以将线程的 Accessed 设置为 true,但它会在回调中等待直到 GoAhead 为 true
  6. 从单元测试中启动了两个线程。一个线程会访问字典,但因为GoAhead该线程是错误的,所以它会坐在那里。然后第二个线程也将尝试访问字典
  7. 我会断言该Accessed线程是错误的,因为我的代码应该将其锁定。

不止于此。我还需要模拟一个 IList,但我想我不会。这些单元测试虽然很有价值,但绝对不是世界上最容易编写的东西。除了设置代码和假接口实现等之外,每个测试最终都是大约 25 行非样板代码。锁定很难。证明你的锁定是有效的更加困难。但令人惊讶的是,这种模式可以让您测试几乎任何场景。但是,它非常冗长,不能进行漂亮的测试

因此,尽管编写测试很困难,但它可以完美运行。当我删除一个锁时总是失败,当我重新添加锁时,它总是通过。

编辑:

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

于 2013-02-27T15:29:48.550 回答
1

我不认为Dictionary你自己可以实现可靠的测试 - 你的目标是进行 2 次调用以并行运行,这不会可靠地发生。

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

于 2013-02-26T19:17:34.393 回答