unit testing私有代码

我目前正参与使用C#进行开发 – 这里有一些背景知识:我们使用客户端应用程序实现MVP,并且我们有一个循环规则,该规则指出没有方法应该具有大于5的圈复杂度。这会导致许多小的私有方法这通常是一件事。

我的问题是关于unit testing一个类:

通过公共方法测试私有实现都很好……我没有遇到实现这个问题的问题。

但是……以下情况如何:

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

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

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

上面的例子在我看来并不是设计不良的结果。 他们似乎也没有成为单独进行单独测试的候选人,因为这样的方法会失去他们的背景。

没有人对此有任何想法吗?

干杯,杰森

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

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

被重构到另一个类进行测试的代码很好 – 但可能会失去上下文。 我一直认为让’帮助’类可以包含一堆没有特定上下文的代码是不好的做法 – (在这里思考SRP)。 我真的不认为这适用于事件处理程序。

我很高兴被certificate是错的 – 我只是不确定如何测试这个function! 我一直认为,如果它可以破坏或改变 – 测试它。

干杯,杰森

正如克里斯所说,标准做法只是对公共方法进行unit testing。 这是因为,作为该对象的消费者,您只关心公开可用的内容。 而且,理论上,使用边缘情况进行适当的unit testing将完全运用它们具有的所有私有方法依赖性。

话虽这么说,我发现有几次直接针对私有方法编写unit testing非常有用,而且最简洁的是通过unit testing解释一些可能遇到的更复杂的场景或边缘情况。

如果是这种情况,您仍然可以使用reflection调用私有方法。

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 

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

我喜欢这个规则。

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

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

当您在私有方法中引用外部依赖项时,捕获往往会进入。 在大多数情况下,您可以使用dependency injection等技术来分离类。 对于使用第三方框架的示例,这可能很困难。 我首先尝试重构设计以分离第三方依赖项。 如果无法做到这一点,请考虑使用Typemock Isolator 。 我没有使用它,但它的关键特性是能够“模拟”私有,静态等方法。

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

编辑:我会尝试回应杰森对我的答案和原始问题的编辑的评论。 首先,我认为SRP推动更多的课程,而不是远离他们。 是的,最好避免使用瑞士军队帮手class级。 但是设计用于处理异步操作的类呢? 还是数据检索类? 这些部分是原始课程的责任,还是可以分开?

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

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

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

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

Ergo:没有必要明确测试私有代码。

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

在我的所有unit testing中,我从未打扰过测试privatefunction。 我通常只测试publicfunction。 这与Black Box测试方法一起使用。

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

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

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

来自一个一直在C#中敲打的TDD家伙的几点:

1)如果你编程到接口,那么不在接口中的类的任何方法实际上是私有的。 您可能会发现这是一种更好的方法来提升可测试性以及更好的方式来使用接口。 将这些测试为公共成员。

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

3)如果你检查一些小的私人方法,你可能会发现它们具有凝聚力。 它们可能代表与原始类别分开的较小类别的兴趣。 如果是这样,那个类可以拥有所有公共方法,但可以作为原始类的私有成员保存,也可以在函数中创建和销毁。 在新class级中测试这些作为公共成员。

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

所有这些都使得对当前私有辅助方法的方法进行测试变得非常简单,而不会弄乱intellisense的工作方式。

这里有一些很好的答案,我基本同意重复建议发布新课程。 但是,对于例3,有一种偷偷摸摸的简单技巧:

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

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

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

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

我认为这种方法对于validation异步行为非常有用,我一直在一些javascript测试和一些lua测试中这样做。 好处是我有两个小的简单测试(一个validation回调是否设置,一个validation它的行为符合预期)。 如果您尝试将回调设置为私有,则validation回调行为的测试需要做更多设置,并且该设置将与应该在其他测试中进行的行为重叠。 坏耦合。

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

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

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

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

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

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

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

不要测试私有代码,或者在重构的时候你会后悔。 然后,你会像Joel和博客那样关于TDD如何工作太多,因为你不得不用你的代码重构你的测试。

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

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

SRP – 维基百科 / pdf

这也导致更强大和适应性更强的代码,因为单一责任原则实际上只是说你的class级应具有高凝聚力 。

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

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

只需使用internal标记您的私有方法,测试项目仍将看到该方法。 简单! 你可以保持封装并进行测试,没有所有的TDD废话。