垃圾收集器如何与单元测试一起工作?

时间:2010-06-21 20:25:10

标签: .net sql unit-testing garbage-collection

最近,我询问(并回答)一个关于StackOverflow的问题,关于为什么单元测试在自行运行时会起作用,然后在运行整批单元测试时偶尔失败。见这里:SQL Server and TransactionScope (with MSDTC): Sporadically can't get connection

在一次运行一个单元测试时通过,然后在一起运行时失败,这是代码严重错误的经典迹象。

我发现有一点资源泄漏。由于一个微妙的错误导致SQL服务器的连接无法释放,我的连接用完了,测试失败了。 AFAIK,这几乎就像内存泄漏一样;连接是从连接池分配的,并且永远不会被释放,因为内存可以分配然后不被释放。

然而,这确实给我留下了一个令人费解的疑问?一次运行一个测试并将它们作为套件运行有什么区别?如果测试在一次运行一次时通过,然后在一起运行时失败,则必须在测试运行之间进行某种清理,只有在每次运行一次测试时才会发生。

我猜想这可能与.net垃圾收集器在测试之间做或不做的事情有关。在一种情况下,在测试之间释放连接;在另一种情况下,他们不是。

我该如何解释?

更新:对于那些询问代码细节的人来说,这很简单。我在Setup方法中声明了一个新的TransactionScope对象,并将其置于我的Teardown方法中。然而,问题测试是一个包含100个测试用例的数据驱动测试;正在测试的代码使用SqlHelper类从select语句填充SqlDataReader对象,然后没有在SqlDataReader上调用close方法。因为我使用SqlHelper类来获取SqlDataReader,所以我预计会为我处理连接。不是这样!

但要澄清,我不是在询问我的具体情况。我想知道的是:一般来说,测试之间如何释放资源?我想这会是垃圾收集器的一些应用。我想知道垃圾收集器是否仍然可以清理以前的测试,因为下一个测试运行(竞争条件?)

更新:我对单元测试垃圾收集的了解。根据我自己的好奇心,我拿出了失败的单元测试,因为SqlDataReader对象打开了连接。我尝试在每次测试结束时添加System.GC.Collect()。这成功地释放了连接,但确实造成了约50%的性能损失。

5 个答案:

答案 0 :(得分:3)

听起来可行,是的。单元测试框架要求垃圾收集器在测试之间运行并不奇怪。

或者,不同的执行模式可能会在它们一个接一个地运行时自然地触发垃圾收集。分析这类事情的麻烦在于它非常动态 - 从测试运行到测试运行都会有所不同。

不要忘记它可能没有释放所有测试之间的连接 - 只是足以让它们继续运行......

除非以特定方式配置测试运行程序进程,否则垃圾收集器本身在单元测试中的行为可能不同。另一方面,无论是在调试器中运行测试还是不在中运行都会影响垃圾收集器的急切程度等。

答案 1 :(得分:2)

通常,每个测试运行都在一个单独的应用程序域中执行,原因有几个。现在当卸载appdomain时,它将释放与之关联的资源,以便关闭打开的连接,从而防止“泄漏”显现出来。

另见Cbrumme's blog on this topic

答案 2 :(得分:1)

垃圾收集是一项定期的后台任务。具体来说,有一个线程除了完成已标记为已死的对象之外什么都不做。通过一次运行一个测试,您可以给该线程一个机会来完成对象以关闭连接。

答案 3 :(得分:1)

  

运行一个时传递的单元测试   一次,然后跑步时失败   在一起是一个经典的标志   事情是严重错误的   代码。

我认为您编写单元测试的方式存在严重问题。每个测试应独立于其他测试运行。一种方法是确保您有一个设置和拆卸方法([SetUp][TearDown]),它们可以创建和清理运行测试所需的环境。

在您的安装方法中,您可以在拆卸方法中创建连接。现在在运行每个测试之前,将调用您的Setup方法,并且在每次测试之后将调用您的拆解方法,这将确保您不会泄漏任何资源。

答案 4 :(得分:0)

哇,这里有多个问题!

首先,您希望您的单元测试系列更快。不要命中数据库来测试业务逻辑等。

其次,如果您的生产代码泄漏资源(?),这是您的主要问题。不要通过更改设置/拆除测试代码的方式来解决该问题。现在,如果您的测试代码正在分配系统资源但未正确处置,则需要以正确的方式修复,而不是通过尝试控制垃圾收集器何时运行。你不应该担心这一点。

第三,您真的不应该在单元测试中创建TransactionScope。这对我来说毫无意义。您在测试代码中使用的编码样式有问题。单元测试不仅仅是任何自动化测试,例如集成测试或系统测试。单元测试是小型且集中的测试,用于测试ISOLATION中SMALL生产代码的行为,与所有其他生产代码无关。

现在,关于泄漏资源的提示。一个好的编程习惯是在创建一次性对象时使用using语句,以保证这些资源得到妥善处理。

using (SqlDataReader reader = ...)
{
   ...
}