15

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

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

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

但是......以下情况呢:

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

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

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

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

有人对此有任何想法吗?

干杯,杰森

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

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

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

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

干杯,杰森

4

12 回答 12

15

正如 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
于 2009-10-01T21:22:23.917 回答
7

我们有一个圈规则,它指出任何方法的圈复杂度都不应大于 5

我喜欢这个规则。

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

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

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

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

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

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

于 2009-10-02T01:54:55.413 回答
4

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

  1. 私有代码直接或间接地从公共代码中调用,并且
  2. 私有代码不是从公共代码中调用的。

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

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

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

于 2009-10-02T14:24:06.130 回答
3

在我所有的单元测试中,我从不打扰测试private功能。我通常只是测试public功能。这与黑盒测试方法相一致。

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

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

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

于 2009-10-01T21:16:06.377 回答
2

一位一直在 C# 中四处奔波的 TDD 人的几点看法:

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

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

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

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

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

于 2009-10-02T21:54:25.757 回答
2

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

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

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

于 2009-10-03T14:28:22.640 回答
1

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

于 2009-10-02T21:59:15.590 回答
1

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

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

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

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

于 2009-10-02T22:06:50.210 回答
0

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

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

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

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

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

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

于 2009-10-01T21:19:33.357 回答
0

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

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

于 2009-10-02T22:05:48.730 回答
0

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

SRP -维基百科/ pdf

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

于 2009-10-02T22:16:18.527 回答
0

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

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

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

于 2016-12-29T05:22:50.093 回答