.NET单元测试 - 在发布代码中隐藏测试接缝的最佳实践

时间:2009-07-16 18:54:07

标签: .net visual-studio unit-testing

我正在寻找一些关于单元测试的建议,不会在生产代码中留下“测试挂钩”。

假设我有一个名为MethodLogger的静态类(VB.NET模块),它有一个“void WriteEntry(string Message)”方法,用于将调用方法和消息写入磁盘上的日志文件。 WriteEntry()的实际目标可以转移到IMethodLogger的虚假实现以运行单元测试,对于所有其他情况,它应该调用IMethodLogger的实际实现。

这是我到目前为止所提出的内容的粗略描述,我喜欢这种方法 - 但在一些快速测试中,它提出了一些问题和怀疑:

[InternalAttributesVisibleTo("MethodLogger.Tests")]
internal static class MethodLogger
{
    public void WriteEntry(string message)
    {
        LogWriter.WriteEntry(message);
    }

    private IMethodLogger LogWriter
    {
        get;
        set;
    }

#if DEBUG
    internal void SetLogWriter(IMethodLogger logger)
    {
        LogWriter = logger;
    }
#endif
}

以下是我的具体问题:

  • 这使得单元测试与DEBUG构建的运行紧密结合;当我运行单元测试时虽然看起来它没有专门重建DEBUG中的测试组件 - 是否可以这样做?

    • 我是否想要针对非调试版本运行单元测试?在我的头顶,我看到的价值较低 - 但会有吗?
  • 我可以使用像“TEST”这样的自定义预编译器标志代替“DEBUG”吗?我如何告诉Visual Studio始终使用这个标志重建目标以进行测试以确保我的钩子/接缝可用?

11 个答案:

答案 0 :(得分:4)

您可以使用Typemock Isolator来规避任何损害可测试性的设计问题。 与其他框架不同,它还可以使用静态方法,私有方法和非虚方法。 当你学会更好地设计时,你的代码默认会更“可测试”, 但如果你现在想解决这些问题,这将有所帮助

http://blog.typemock.com/2009/01/isolator-new-vbnet-friendly-api.html

(另外,它是唯一具有VB友好API的工具)

答案 1 :(得分:3)

我们确保针对Release / Obfuscated版本运行测试,因为我们经常以这种方式发现问题,所以,是的,我认为让它们工作是有价值的。

混淆会使内部构件完全无法使用,这对我来说已经足够了,所以我们留下测试方法(并将它们放在内部)。

总的来说,我宁愿让代码进行测试而不是担心它们存在 - 只要它们不会影响性能。

答案 2 :(得分:3)

我不喜欢不能亲自针对真实的生产二进制文件运行测试的想法。

你能不能将setter标记为“过时”并禁用测试代码中的过时警告?除了合适的文档之外,还应该阻止生产代码意外调用它,但仍然可以将其用于测试。

但是,要解决您的实际问题:

  • 是的,对非调试版本运行单元测试是有意义的 - 它使调试和非调试版本之间的更改不会破坏任何内容。
  • 是的,如果需要,您可以使用自己的预处理器符号(例如TEST);您可以在现有配置中应用它,也可以创建新的构建配置。

答案 3 :(得分:3)

实际上,你正在走向依赖注入 - 你还没有实现它:)

将接缝留在原位没有任何问题 - 您只需要对它们进行建模,以便它们不是特定于测试,而是实际上是API的增强功能。这需要一些练习,但首先要改变思维方式。

我不久前在Testability Is Really The Open/Closed Principle写了这篇文章。

在您的特定情况下,您应该对MethodLogger类进行一些更改:

  1. 使其成为实例类而不是静态类。正如所有经验丰富的TDD实践者都会告诉你的那样,静力学是 evil
  2. only 构造函数获取IMethodLogger的实例。这称为构造函数注入,并强制所有客户端明确决定如何记录。
  3. 手动设置Dependency Graph,或使用DI容器将MethodLogger连接到所需的IMethodLogger。

答案 4 :(得分:2)

拥有#IF DEBUG等......是不好的形式。

生产代码应独立于测试/调试代码。

我建议研究依赖注入以帮助进行单元测试。

答案 5 :(得分:2)

根据Michael Feathers的“有效使用遗留代码”一书,如果你将setter命名为特定于测试的东西,那么它似乎会更好。

实施例: SetTestingLogger(IMethodLogger记录器)

我也会删除预处理器语句。这本书还说明并引用

  

“你可能会有点不安   您要删除访问权限的想法   使用静态setter时保护,   但要记住的目的   访问保护是为了防止   错误。我们正在进行测试   也防止错误。“

<强>结论: 看起来最好放置这些钩子,因为你出于测试原因放置它们并最终消除错误。

书中的“介绍静态设定器”部分更详细地解释了这一点。我会推荐它作为每个开发人员应该拥有的书。

答案 6 :(得分:2)

  1. 像其他人所说的那样,转向依赖注入和控制反转(IoC)
  2. 在隔离框架(Moq等)的帮助下很好地隔离测试类
  3. 将测试移动到单独的程序集中 - 不需要#if DEBUG ... #endif

答案 7 :(得分:1)

这是一种内部方法。只需保留原样,无需条件编译,只需从单元测试中调用即可。

离开钩子没有任何问题 - 事实上,您甚至可能想要考虑使用 public 方法并允许任何代码更改日志记录钩子。

再次查看您的代码;你甚至不需要 SetLogWriter方法;只需为静态类使用私有访问器,您就可以在单元测试中自行设置。

  

我可以看到将钩子留在原位是可以的,删除它只是一个偏好,但我不同意将其公开 - 感觉太像提升权限只是因为它删除了小麻烦。 - Yoooder

好吧,我更多地评论一般代码组织原则。为代码创建这样的钩子通常是个好主意;在您的情况下,您将不会“提升权限以消除麻烦”,您将“设计一个强大而灵活的日志框架”。

答案 8 :(得分:1)

对我来说,这似乎是IoC容器的主要候选者。我会让我的记录器不是静态的,并为测试,开发和生产设置不同的配置。

答案 9 :(得分:1)

您的构建过程必须包含一个生成版本,并对其执行单元测试。如果没有,你不能保证有人没有使用pragma(例如发布与调试)。

在记录的情况下,我总是使用Log4Net。在开发和测试期间,日志消息设置在最详细的级别,在生产中我倾向于将它们保留在错误或信息级别,但在必要时设置为更详细的故障排除。

IMO是使用SetLogWriter执行您要完成的任务的最佳方法实际上是使用控件容器的反转,如StructureMap,根据各种配置在运行时分配混凝土。在单元测试期间,您可以使用像Moq这样的库来制造以预期方式运行的混凝土(SQL查询始终返回相同的精确结果),但在您的特定情况下,您执行相反的操作(所需的日志记录)在单元测试期间与生产运行时没有记录)。

答案 10 :(得分:0)

  

我可以使用   自定义预编译器标志,如“TEST”   代替“调查”?我怎么去   关于告诉Visual Studio永远   使用此标志重建目标   测试以确保我的钩子/接缝   可用?

是。您要做的是添加条件编译符号。您可以在“构建”选项卡下的项目属性中执行此操作。您可以在Configuration Manager中创建不同的构建配置,并仅将TEST符号添加到新配置中。