3

例如,我正在针对 CsvReader 编写测试。这是一个枚举和拆分文本行的简单类。它唯一存在的理由是忽略引号中的逗号。它不到一页。

通过“黑盒”测试课程,我检查了类似

  • 如果文件不存在怎么办?
  • 如果我没有文件的权限怎么办?
  • 如果文件有非 Windows 换行符怎么办?

但实际上,所有这些都是 StreamReader 的事情。我的班级在没有对这些案例做任何事情的情况下工作。所以本质上,我的测试是捕捉 StreamReader 抛出的错误,以及框架处理的测试行为。感觉很多工作都白费了。

我看过相关的问题

我的问题是,如果我使用我所知道的来避免这种工作,我是否错过了“玻璃盒”测试的意义?

4

6 回答 6

3

我认为您不应该浪费时间测试不是您的代码的东西。这是一个设计选择,而不是测试选择,是处理底层框架的错误还是让它们传播到调用者。FWIW,我认为您让它们传播是正确的。但是,一旦您做出了设计决定,您的单元测试应该覆盖您的代码(并且很好地覆盖它),而不需要测试底层框架。使用依赖注入和模拟 Stream 可能也是一个好主意。

[编辑] 依赖注入示例(有关更多信息,请参见上面的链接)

不使用依赖注入我们有:

public class CvsReader {
   private string filename;
   public CvsReader(string filename)
   {
      this.filename = filename;
   }

   public string Read()
   {
      StreamReader reader = new StreamReader( this.filename );
      string contents = reader.ReadToEnd();
      .... do some stuff with contents...
      return contents;
   }
}

使用依赖注入(构造函数注入),我们可以:

public class CvsReader {
   private IStream stream;
   public CvsReader( IStream stream )
   {
      this.stream = stream;
   }

   public string Read()
   {
       StreamReader reader = new StreamReader( this.stream );
       string contents = reader.ReadToEnd();
       ...  do some stuff with contents ...
       return contents;
   }
}

这使得 CvsReader 更容易测试。我们在构造函数中传递了一个实现我们依赖的接口的实例,在本例中为 IStream。因此,我们可以创建另一个实现 IStream 的类(可能是模拟类),但不一定执行文件 I/O。我们可以使用这个类向我们的读者提供我们想要的任何数据,而无需涉及任何底层框架。在这种情况下,我会使用 MemoryStream,因为我们只是从中读取。不过,在我们想要的情况下,我们可以使用一个模拟类并给它一个更丰富的接口,让我们的测试配置它给出的响应。这样我们就可以测试我们编写的代码,而完全不涉及底层框架代码。或者,我们也可以传递一个 TextReader,但正常的依赖注入模式使用接口,我想用接口展示模式。可以说传入 TextReader 会更好,因为上面的代码仍然依赖于 StreamReader 实现。

于 2008-10-14T23:11:13.523 回答
3

这实际上取决于您的 CsvReader 的接口,您需要考虑该类的用户期望什么。

例如,如果其中一个参数是文件名并且文件不存在应该发生什么?这不应该取决于您是否使用流阅读器。单元测试应该测试你的类的可观察到的外部行为,并且在某些情况下,稍微深入挖掘并另外确保覆盖某些实现细节,例如当阅读器完成时关闭文件。

但是,您不希望单元测试依赖于所有细节,或者假设由于实现细节而发生某些事情。

您在问题中提到的所有示例都涉及您班级的可观察行为(在这种情况下为例外情况),因此应该进行与它们相关的单元测试。

于 2008-10-14T23:11:18.250 回答
2

是的,但这将严格用于单元测试:

您可以通过定义抽象流读取器接口并使用实现该接口的模拟流读取器测试您自己的实现,从任何特定的 StreamReader 抽象您的 CSV 读取器实现。您的模拟阅读器显然不会受到不存在的文件、权限问题、操作系统差异等错误的影响。因此,您将完全测试自己的代码,并且可以实现 100% 的代码覆盖率。

于 2008-10-14T22:42:12.617 回答
1

我倾向于同意 tvanfosson:如果您从 StreamReader 继承并以某种方式对其进行扩展,那么您的单元测试应该只执行您添加或更改的功能。否则,您将浪费大量时间和认知能量来编写、阅读和维护不会增加任何价值的测试。

尽管 markj 认为测试应该涵盖一个类的“可观察的外部行为”是正确的,但我认为考虑该行为的来源是合适。如果它是通过从另一个(可能是单元测试的)类继承而来的行为,那么我认为添加自己的单元测试没有任何好处。OTOH,如果它是通过组合的行为,那么它可能会证明一些测试是合理的,以确保传递正常工作。

我的偏好是对您更改的特定功能进行单元测试,然后编写检查错误条件的集成测试,但在您最终支持的业务需求的上下文中。

于 2008-10-15T21:33:39.853 回答
1

仅供参考,如果这是 .NET,您应该考虑不要重新发明轮子。

对于 C#

添加对 Microsoft.VisualBasic 的引用 使用奇妙的类 Microsoft.VisualBasic.FileIO.TextFieldParser() 来处理您的 CSV 解析需求。

Microsoft 已经对其进行了测试,因此您不必这样做。

享受。

于 2010-08-26T18:29:42.037 回答
0

您应该始终管理框架抛出的错误;这样您的应用程序就很健壮并且不会因灾难性错误而崩溃...

于 2008-10-14T22:23:38.363 回答