关于c#:单元测试私有代码

Unit testing private code

本问题已经有最佳答案,请猛点这里访问。

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

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

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

但是......以下情况如何:

Example 1. Handle the result of an async data retrival request (The callback method shouldn't be public purely for testing)

Example 2. An event handler which does an operation (such as update a View label's text - silly example I know...)

Example 3. You are using a third party framework which allows you to extend by overriding protected virtual methods (the path from the public methods to these virtual methods are generally treated as black box programming and will have all sorts of dependancies that the framework provides that you don't want to know about)

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

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

干杯,
贾森

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

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

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

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

干杯,杰森


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

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

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

1
2
3
4
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


we have a cyclomatic rule which states
that no method should have a
cyclomatic complexity greater than 5

我喜欢这个规则。

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

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

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

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

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

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


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

  • 私有代码直接或间接地从公共代码和
  • 私有代码不是从公共代码调用的。
  • 在第一种情况下,私有代码会被执行调用私有代码的公共代码的测试自动测试,因此不需要测试私有代码。在第二种情况下,根本不能调用私有代码,因此应该删除它,而不是测试它。

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

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


    在我的所有单元测试中,我从未打扰过测试private功能。我通常只测试public函数。这与Black Box测试方法一致。

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

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

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


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

    Example 3. You are using a third party
    framework which allows you to extend
    by overriding protected virtual
    methods (the path from the public
    methods to these virtual methods are
    generally treated as black box
    programming and will have all sorts of
    dependencies that the framework
    provides that you don't want to know
    about)

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


    来自一位在C#中喋喋不休的TDD家伙的几点:

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

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

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

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

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


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

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

    我认为这种方法对于验证异步行为很有用,我一直在一些javascript测试和一些lua测试中这样做。好处是我有两个小的简单测试(一个验证回调设置,一个验证它的行为符合预期)。如果你试图保持回调私有,那么验证回调行为的测试有很多设置要做,并且该设置将与应该在其他测试中的行为重叠。坏耦合。

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


    访问者文件是否有效? http://msdn.microsoft.com/en-us/library/bb514191.aspx我从未直接与他们合作,但我知道同事用它们来测试某些Windows窗体上的私有方法。


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

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

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


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

    SRP - 维基百科/ pdf

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


    不要测试私人代码,否则你将在以后重构时感到抱歉。然后,你会像Joel和博客一样了解TDD是如何工作的,因为你不得不用你的代码重构你的测试。

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


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

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

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

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

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

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