【问题标题】:Unit Test Garbage Collection单元测试垃圾收集
【发布时间】:2014-05-03 11:53:03
【问题描述】:

问题
一旦测试完成,单元测试是否会通过垃圾收集(在我的情况下为System.IO.Stream)自动处理资源,或者是否有东西处于打开/使用状态,需要处理IDisposable 对象?

上下文/信息
我目前正在为使用 System.IO.Stream 的文件上传器进行单元测试。
我已经将HttpPostedFileBase 与由System.IO.MemoryStream 驱动的文件的InputStream 一起Moq 出,这一切都按预期工作。

我目前有(为简洁起见进行了更改):

[TestMethod]
public void TestUpload()
{
  var stream = FunctionCreatingTheMemoryStream();
  try
  {
    var file = new Mock<HttpPostedFileBase>();
    file.Setup(f => f.FileName).Returns("test.txt");
    file.Setup(f => f.InputStream).Returns(stream);  
    MethodThatUsesTheStream(file.Object)  
    // rest of test code with Assert
  }
  finally
  {
    stream.Dispose();
  }
}

问题在于创建的MemoryStream 实例:

var stream = new FunctionCreatingTheMemoryStream();

是否值得将任何代码放在try catch 中,然后在finally 语句中处理流,或者作为单元测试,是否会自动处理内存流?

那么是否有必要这样做,或者它可以只是:

[TestMethod]
public void TestUpload()
{
   var stream = FunctionCreatingTheMemoryStream();
   var file = new Mock<HttpPostedFileBase>();  

   file.Setup(f => f.FileName).Returns("test.txt");
   file.Setup(f => f.InputStream).Returns(stream);  
   MethodThatUsesTheStream(file.Object)
   // rest of test code with Assert
}

【问题讨论】:

  • 我很确定这个问题在这里有很多重复。如果您没有很好的理由不这样做,请始终在完成 IDisposable 的对象上调用 Dispose。既然你在问这件事,我会说你没有充分的理由,所以就去做吧。
  • mocking 框架可能有一些魔力,但你自己做也没有坏处。而且我相信一般DisposeClose 在流上是同义词,所以你只需要一个。然后我会使用using 声明。
  • 我没有看到你在这里测试的类 - 你只有内存流和模拟
  • 我实际上找不到类似的问题@LasseV.Karlsen,因此我问了这个问题。如果您可以链接适当的问题,那么请这样做。我也问,因为我不想在我的测试中有不必要的代码。
  • @Chris 谢谢。我已经尝试过使用 using 语句来创建流,但它在返回时已关闭,所以我想我可以将它全部包装在一个闭包中,但我的情况与将所有内容包装在一个 try catch 中的情况类似。

标签: c# unit-testing moq mstest


【解决方案1】:

答案最终取决于您使用的单元测试框架,但在 .NET 中,三大测试框架(MSTest、NUnit、xUnit.net)都不会自动处理任何事情。您必须手动要求他们这样做。

您可能会争辩说,在执行测试套件时,测试运行程序原则上会启动一个新进程,运行所有测试,然后进程退出。

对于IDisposable 的某些实现,例如MemoryStream,这仅意味着在进程退出时回收内存。但是,您不能总是依赖它,因为某些一次性类型可能会访问进程外资源。理论上,您可以让对象持有内存映射文件、命名管道、SQL Server 连接等。即使测试进程退出,您也可能会留下这些资源。它们可能迟早会超时(例如 SQL Server 连接返回到池中),但这可能会降低您的系统速度。

此外,现在一些测试运行者正努力变得聪明,因此他们重用一个或多个进程以更快地运行,从而在 AppDomain 内外更改您的测试套件。

因此,最后,除非您有类似 MemoryStream 的东西,并且您绝对确定将其抛在脑后没什么​​大不了的,否则您应该在测试中确定性地处置您的对象。

但是,如果您正在进行测试驱动开发,您应该采取GOOS 的态度,即倾听您的测试。如果您编写了很多涉及 IDisposable 对象的测试,您应该考虑是否可以简化您的 SUT 的 API。这取决于您在做什么,但是如果您编写的代码主要是托管代码,则不需要太多 IDisposable,因为它是一个 Leaky Abstraction 泄漏 SUT 依赖于非托管资源。

【讨论】:

  • 谢谢马克,这很清楚。具有讽刺意味的是,最终是您出色的“.NET 中的依赖注入”一书让我首先陷入了这种境地。从是/否 pov 来看,这是一个“不,它没有”,所以如果事情可以/将会被抛在后面,那么听起来好像无论如何都值得放弃良好的做法。我认为,如果我正在编写一个非常大/密集的单个或多个测试,我认为这更像是一个问题,但在简单的场景中,处置或不处置听起来几乎无关紧要。我只是通过 TDD 方法编写小规模托管代码
  • 不是通过 TDD 方法。
  • +1 获取一般建议。但是最近几周 xUnit v2 已经开始 Disposeing 理论参数 [即 IDisposable]
【解决方案2】:

过去,我编写了一个“Dustcart”类,它实现了 IDisposable,并包含要处理的对象集合。它们按照添加方式的操作顺序排列(使用堆栈来实现)。

那么测试代码的样子。

[Setup]
Public void Setup()
{
    _dustcart = new Dustcart()
}

[TearDown] 
public void TearDown ()
{
  _dustcart.Dispose();
}

[TestMethod]
public void TestUpload()
{
   var stream = _dustcart.DisposeOnTearDown(FunctionCreatingTheMemoryStream());
   var file = new Mock<HttpPostedFileBase>();  

   file.Setup(f => f.FileName).Returns("test.txt");
   file.Setup(f => f.InputStream).Returns(stream);  
   MethodThatUsesTheStream(file.Object)
   // rest of test code with Assert
}

DisposeOnTearDown() 是一个通用方法。你可以把它放在一个超类中,你的所有测试都来自这个超类,其中还包括 object mothers 用于你需要模拟的类等。

但是,如果您不十分小心,您的测试代码会变得难以理解,并且对您正在测试的软件的质量没有真正的好处。测试是为了完成一项工作,它们必须不比完成这项工作所需要的更好。

【讨论】:

    【解决方案3】:

    完全同意您需要处置此类物品的每个人。您可以通过使用“使用”语句来稍微简化代码,如下所示:

    [TestMethod]
    public void TestUpload()
    {
      using (var stream = FunctionCreatingTheMemoryStream())
      {
        var file = new Mock<HttpPostedFileBase>();
        file.Setup(f => f.FileName).Returns("test.txt");
        file.Setup(f => f.InputStream).Returns(stream);  
        MethodThatUsesTheStream(file.Object)  
        // rest of test code with Assert
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-06
      • 1970-01-01
      • 1970-01-01
      • 2011-01-21
      • 1970-01-01
      • 1970-01-01
      • 2018-12-30
      • 1970-01-01
      相关资源
      最近更新 更多