单元测试私有代码[重复]

Posted

技术标签:

【中文标题】单元测试私有代码[重复]【英文标题】:Unit testing private code [duplicate] 【发布时间】:2010-12-03 03:33:06 【问题描述】:

我目前正在使用 C# 进行开发 - 以下是一些背景: 我们使用客户端应用程序实现 MVP,并且我们有一个圈规则,该规则规定任何方法的圈复杂度都不应大于 5。 这导致了很多小的私有方法,它们通常只负责一件事。

我的问题是关于单元测试一个类:

通过公共方法测试私有实现一切都很好......我实现这个没有问题。

但是……以下情况呢:

示例 1. 处理异步数据检索请求的结果(回调方法不应纯粹为了测试而公开)

示例 2. 执行操作的事件处理程序(例如更新视图标签的文本 - 我知道的愚蠢示例...)

示例 3. 您正在使用第三方框架,该框架允许您通过覆盖受保护的虚拟方法进行扩展(从公共方法到这些虚拟方法的路径通常被视为黑盒编程,并将有框架提供的各种你不想知道的依赖)

在我看来,上面的例子并不是设计不佳的结果。 它们似乎也不适合转移到单独的类进行单独测试,因为这些方法将失去它们的上下文。

有人对此有任何想法吗?

干杯, 杰森

编辑: 我认为我最初的问题不够清楚 - 我可以使用访问器测试私有方法并使用 TypeMock 模拟调用/方法。那不是问题。问题是测试不需要公开或不能公开的东西。

我不想为了测试而公开代码,因为它可能会引入安全漏洞(仅发布一个接口来隐藏这不是一种选择,因为任何人都可以将对象转换回其原始类型并获得访问权限的东西我不希望他们)

重构到另一个类进行测试的代码很好 - 但可能会丢失上下文。我一直认为拥有“帮助器”类是不好的做法,它可以包含一堆没有特定上下文的代码——(在这里考虑 SRP)。我真的认为这也不适用于事件处理程序。

我很高兴被证明是错误的 - 我只是不确定如何测试此功能!我一直认为如果它可以破坏或改变 - 测试它。

干杯,杰森

【问题讨论】:

你将使用什么隔离框架? 我正在使用 Typemock 隔离器。模拟方法调用很容易 - 我真的不喜欢这样做,因为它会改变被测试类的行为! 所有这一切让我希望我可以从头开始编写自己的框架,这样我就可以从一开始就设计这些问题!!! 【参考方案1】:

在我所有的单元测试中,我从不费心测试private 函数。我通常只测试public 函数。这与黑盒测试方法相一致。

你说得对,除非你公开私有类,否则你真的无法测试私有函数。

如果您的“用于测试的单独类”在同一个程序集中,您可以选择使用内部而不是私有。这会将内部方法暴露给您的代码,但它们的方法将无法被不在您的程序集中的代码访问。

编辑: 搜索这个主题时我遇到了this question。投票最多的答案与我的回答相似。

【讨论】:

啊,但您实际上可以测试私有课程 :)。应该在“但我应该吗?”下提交,但实际上是可能的。在某些情况下,我发现它很有用。【参考方案2】:

我承认,最近为 C# 编写单元测试时,我发现我所知道的许多 Java 技巧并没有真正适用(在我的例子中,它是在测试内部类)。

例如 1,如果您可以伪造/模拟数据检索处理程序,则可以通过伪造访问回调。 (我知道使用回调的大多数其他语言也倾向于不将它们设为私有)。

例如 2 我会考虑触发事件以测试处理程序。

示例 3 是其他语言中确实存在的模板模式的示例。我已经看到了两种方法:

    无论如何都要测试整个班级(或至少是其中的相关部分)。这特别适用于抽象基类带有自己的测试,或者整个类不太复杂的情况。例如,在 Java 中,如果我正在编写 AbstractList 的扩展,我可能会这样做。如果模板模式是通过重构生成的,也可能是这种情况。

    使用额外的钩子再次扩展类,允许直接调用受保护的方法。

【讨论】:

【参考方案3】:

正如 Chris 所说,仅对公共方法进行单元测试是标准做法。这是因为,作为该对象的消费者,您只关心对您公开可用的内容。而且,理论上,带有边缘案例的适当单元测试将充分利用它们拥有的所有私有方法依赖关系。

话虽如此,我发现在某些情况下,直接针对私有方法编写单元测试可能非常有用,并且通过单元测试最简洁地解释了一些更复杂的场景或边缘情况可能是遇到过。

如果是这样,您仍然可以使用反射调用私有方法。

MyClass obj = new MyClass();
MethodInfo methodInfo = obj.GetType().GetMethod("MethodName", BindingFlags.Instance | BindingFlags.NonPublic);
object result = methodInfo.Invoke(obj, new object[]  "asdf", 1, 2 );
// assert your expected result against the one above

【讨论】:

+1,这很甜蜜。我从来不知道反射可以做到这一点。 您也可以使用私有访问器并(可选地)传递一个私有对象来包装您的真实实例,这更清洁。我认为它还可以防止您在上课时出现任何反射问题(不要引用我的话——我从来没有这样做过!!)【参考方案4】:

我们有一个圈规则 任何方法都不应该有 圈复杂度大于 5

我喜欢这条规则。

关键是私有方法是实现细节。它们可能会发生变化/重构。您想测试公共接口。

如果您有具有复杂逻辑的私有方法,请考虑将它们重构为单独的类。这也有助于降低圈复杂度。另一种选择是将方法设为内部并使用 InternalsVisibleTo (在 Chris 的答案中的一个链接中提到)。

当您在私有方法中引用了外部依赖项时,问题往往会出现。在大多数情况下,您可以使用诸如Dependency Injection 之类的技术来解耦您的类。对于您使用第三方框架的示例,这可能很困难。我会首先尝试重构设计以分离第三方依赖项。如果这不可能,请考虑使用Typemock Isolator。我没用过,但它的关键特性是能够“模拟”出私有、静态等方法。

类是黑盒子。以这种方式测试它们。

编辑:我将尝试回复 Jason 对我的回答的评论以及对原始问题的编辑。首先,我认为SRP 将推向更多类,而不是远离它们。是的,最好避免瑞士军队的助手课程。但是设计用于处理异步操作的类呢?还是数据检索类?这些是原班职责的一部分,还是可以分开?

例如,假设您将此逻辑移至另一个类(可能是内部的)。该类实现了一个Asynchronous Design Pattern,它允许调用者选择该方法是同步调用还是异步调用。单元测试或集成测试是针对同步方法编写的。异步调用使用标准模式,复杂度低;我们不测试那些(验收测试除外)。如果异步类是内部的,请使用 InternalsVisibleTo 对其进行测试。

【讨论】:

感谢您的回答 - 我应该提到我确实使用 TypeMock Isolator,并且模拟类、类到方法不是问题。处理私有代码也不是问题。问题是(以示例 1 为例)如果我模拟出执行数据检索的类 - 我无法让它调用回调。 (示例 2)如果不深入研究类的私有方法,我将无法测试私有事件处理程序。【参考方案5】:

实际上只有两种情况需要考虑:

    从公共代码直接或间接调用私有代码和 私有代码不是从公共代码调用的。

在第一种情况下,私有代码会自动由调用私有代码的公共代码的测试进行测试,因此无需测试私有代码。而第二种情况,私有代码根本无法调用,所以应该删除,而不是测试。

Ergo:无需显式测试私有代码。

请注意,当您执行 TDD 时,不可能未经测试的私有代码甚至存在。因为当您进行 TDD 时,私有代码可以出现的唯一方法是通过从公共代码中提取 Method|Class|... 重构。根据定义,重构是保持行为的,因此是保持测试覆盖率的。公共代码出现的唯一方式是测试失败的结果。如果公共代码只能作为测试失败的结果显示为已测试的代码,而私有代码只能作为通过行为保持重构从公共代码中提取的结果出现,那么未经测试的私有代码永远不会出现。

【讨论】:

当您考虑我给出的第一个或第二个示例时,我认为这不是真的。您断言私有代码只能作为重构公共代码的直接结果存在 - 但这并没有解释如何测试事件处理程序或异步回调的代码。这是我真正遇到的两个问题,无法解决!【参考方案6】:

来自一个一直在 C# 中乱搞的 TDD 人的几点意见:

1) 如果您对接口进行编程,那么不在接口中的类的任何方法实际上都是私有的。您可能会发现这是提高可测试性的更好方法,也是使用接口的更好方法。将这些作为公共成员进行测试。

2) 那些小的辅助方法可能更适合属于某个其他类。寻找功能嫉妒。作为原始类(您在其中找到它)的私有成员可能不合理的东西可能是它所羡慕的类的合理公共方法。作为公共成员在新类中测试这些。

3) 如果您检查一些小的私有方法,您可能会发现它们具有内聚性。它们可能代表与原始类别不同的​​较小的兴趣类别。如果是这样,则该类可以具有所有公共方法,但要么作为原始类的私有成员持有,要么可能在函数中创建和销毁。作为公共成员在新类中测试这些。

4) 您可以从原始类派生一个“可测试”类,其中创建一个新的公共方法是一项微不足道的任务,该方法除了调用旧的私有方法之外什么都不做。可测试类是测试框架的一部分,而不是生产代码的一部分,因此拥有特殊访问权限是很酷的。在测试框架中对其进行测试,就像它是公开的一样。

所有这些都使得对当前私有辅助方法的方法进行测试变得非常简单,而不会破坏智能感知的工作方式。

【讨论】:

【参考方案7】:

访问器文件会起作用吗? http://msdn.microsoft.com/en-us/library/bb514191.aspx 我从未直接与他们合作过,但我知道一位同事使用他们在某些 Windows 窗体上测试私有方法。

【讨论】:

【参考方案8】:

不要测试私有代码,否则当需要重构时你会后悔的。然后,您会像 Joel 和博客一样,讲述 TDD 工作量太大,因为您必须不断地用代码重构测试。

有一些技术(模拟、存根)可以进行适当的黑盒测试。查找它们。

【讨论】:

【参考方案9】:

一些人回应说私有方法不应该直接测试,或者他们应该被转移到另一个类。虽然我认为这很好,但有时它只是不值得。虽然我原则上同意这一点,但我发现这是可以打破的规则之一,以节省时间而不会产生负面影响。如果函数很小/简单,那么创建另一个类和测试类的开销就太大了。我会将这些私有方法公开,但不会将它们添加到接口中。这样类的消费者(他们应该只能通过我的 IoC 库获取接口)不会意外使用它们,但它们可以用于测试。

现在在回调的情况下,这是一个很好的例子,公开私有属性可以使测试更容易编写和维护。例如,如果 A 类将回调传递给 B 类,我将把该回调设为 A 类的公共属性。A 类的一个测试使用 B 的存根实现来记录传入的回调。然后测试验证在适当的条件下将回调传递给 B。然后对 A 类的单独测试可以直接调用回调,验证它是否具有适当的副作用。

我认为这种方法非常适合验证异步行为,我在一些 javascript 测试和一些 lua 测试中一直在这样做。好处是我有两个简单的小测试(一个验证回调是否设置,一个验证它的行为是否符合预期)。如果您尝试保持回调私有,那么验证回调行为的测试需要做更多的设置,并且该设置将与其他测试中的行为重叠。耦合不良。

我知道,它不漂亮,但我认为它运作良好。

【讨论】:

对于您的第一个建议 - 我不愿意这样做,因为只需进行一些简单的转换,您就可以在您的应用程序中引入巨大的安全性。您对处理回调的建议是关于我测试异步方法的建议......我实际上最终将代码更改回私有并更改我的测试以最终使用访问器测试私有实现,因为我不想要公开的代码。这样做只是有点“气味”:( 您能否详细说明与投射相关的安全漏洞? 嗨弗兰克,使用一点反射来计算出接口背后的实际类型并不难。然后可以将实现接口的对象转换回其原始类型,并访问您无法通过接口访问的公共方法。据我了解,(在这种情况下)接口的重点不是安全性——它是向开发人员指示类(或多态性)的正确用法。 使用反射可以直接调用私有方法,无论类是否位于接口后面。如果有人能够将代码注入您的进程并做这样的事情,那就是安全问题,而不是他们之后所做的事情。它本身不是一个安全问题,而是一个防御性编码问题。防御性编码只能防止合理的误用,我认为如果调用者通过接口访问组件就可以做到。【参考方案10】:

这是一个在引入测试时很早就出现的问题。解决此问题的最佳技术是黑盒测试(如上所述)并遵循单一责任原则。如果你的每个类只有一个改变的理由,那么它们应该很容易测试它们的行为而无需使用它们的私有方法。

SRP - wikipedia / pdf

这也导致代码更健壮和适应性更强,因为单一职责原则实际上只是说你的类应该有高 cohesion。

【讨论】:

在这种情况下,您将如何处理示例 1? 我必须查看代码,但我马上会说文件检索本身不需要知道它是异步的。您应该单独实现该功能并对其进行扩展以支持异步传输。在这种情况下,我假设您在异步请求中使用 full 并在回调中处理结果。这两者已经很明显地分开了。一个发送,另一个接收。测试发送。测试接收。一起测试。更多内容将会公开,但不会在您现有的环境中。 这是我如何尝试实施您的建议的示例(如果我错了,请纠正我!)我有一个可以以异步方式获取数据的类 (C1)。我的调用类 (C2) 调用数据检索类。调用类不知道异步调用需要多长时间(因为它是在另一个线程上定义的)数据检索类发出信号通知它已检索数据。 C2 然后从 C1 中提取数据。那么如何在不引入竞争条件的情况下对其进行全面测试呢?测试发送和测试接收很好 - 但我如何同时测试两者?【参考方案11】:

这里有一些很好的答案,我基本上同意反复提出的新课程的建议。然而,对于您的示例 3,有一个偷偷摸摸的简单技术:

示例 3. 您正在使用第三方 允许您扩展的框架 通过覆盖受保护的虚拟 方法(来自公众的路径 这些虚拟方法的方法是 通常被视为黑匣子 编程,并且会有各种各样的 框架的依赖 提供你不想知道的 关于)

假设 MyClass 扩展了 FrameworkClass。让 MyTestableClass 扩展 MyClass,然后在 MyTestableClass 中提供公共方法,以公开您需要的 MyClass 的受保护方法。不是一个很好的做法——它是糟糕设计的助推器——但有时很有用,而且非常简单。

【讨论】:

【参考方案12】:

在 C# 中,您可以使用 AssemblyInfo.cs 中的属性:

[assembly: InternalsVisibleTo("Worker.Tests")]

只需用 internal 标记您的私有方法,测试项目仍然会看到该方法。简单的!您可以继续封装并进行测试,而无需所有 TDD 废话。

【讨论】:

下次,请将问题标记为重复,而不是发布多个相同的答案。谢谢!

以上是关于单元测试私有代码[重复]的主要内容,如果未能解决你的问题,请参考以下文章

使用 FakeItEasy 的私有方法和属性的单元测试用例 [重复]

将私有方法公开以对其进行单元测试...好主意吗?

如何对私有 Cocoapods 库进行单元测试?

如何在 Typescript 中对私有方法进行单元测试

如何对私有 Windows 窗体进行单元测试?

如何在Typescript中对私有方法进行单元测试