如何使用NUnit测试私有方法?

123

我想知道如何正确使用NUnit。首先,我创建了一个单独的测试项目,并将我的主要项目作为引用使用。但是,在这种情况下,我无法测试私有方法。我的猜测是我需要将我的测试代码包含到我的主代码中?! - 那似乎不是正确的做法。(我不喜欢在代码中嵌入测试的想法。)

如何使用NUnit测试私有方法?

13个回答

77

尽管我认为单元测试的重点应该是公共接口,但如果您也测试私有方法,您将更加深入地了解您的代码。 MS测试框架通过使用PrivateObject和PrivateType允许这样做,而NUnit则不允许。我的做法是:

private MethodInfo GetMethod(string methodName)
{
    if (string.IsNullOrWhiteSpace(methodName))
        Assert.Fail("methodName cannot be null or whitespace");

    var method = this.objectUnderTest.GetType()
        .GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);

    if (method == null)
        Assert.Fail(string.Format("{0} method not found", methodName));

    return method;
}

这种方式意味着您无需为了可测试性而牺牲封装性。请注意,如果您想测试私有静态方法,则需要修改您的BindingFlags。上面的示例仅适用于实例方法。


7
测试那些小的辅助方法可以在单元测试中捕获单元部分。但并不是所有的都适合于实用类。 - Johan Larsson
14
这正是我所需要的,谢谢!我只需要检查一个私有字段是否被正确设置,而且我不需要花费三个小时通过公共接口来确定它。这是现有的代码,不能轻易重构。有时候一个简单的问题却会被回答得太理想化了... - Droj
6
这应该是被选中的答案,因为它是唯一真正回答了这个问题的答案。 - bavaza
7
同意。所选答案有其优点,但它是针对“我应该测试私有方法吗”的问题而非提问者的问题。 - chesterbr
3
它有效。谢谢!这就是许多人正在寻找的答案。这应该是被选择的答案。 - Able Johnson
显示剩余2条评论

74

一般来说,单元测试关注类的公共接口,认为实现是无关紧要的,只要从客户的角度看结果正确即可。

因此,NUnit不提供测试非公开成员的机制。


1
+1 我刚刚遇到这个问题,在我的情况下,私有和公共之间存在一种“映射”算法,如果我要对公共进行单元测试,则实际上是一种集成测试。在这种情况下,我认为它试图编写测试点以解决代码中的设计问题。如果是这种情况,我应该创建一个执行该“私有”方法的不同类。 - andy
161
我完全不同意这个论点。单元测试并不是关于类,而是关于代码。如果我有一个带有一个公共方法和十个私有方法的类用于创建公共方法结果的情况下,我无法确保每个私有方法按照预期运作。我可以尝试创建针对公共方法的测试用例来以正确的方式调用正确的私有方法。但是我无法确定私有方法是否真正被调用,除非我信任公共方法永远不会改变。私有方法旨在为生产代码的用户保留私密性,并非作者。 - Quango
3
在标准测试设置中,“作者”实际上是代码的生产用户。不过,如果您没有发现设计问题,您可以选择在不更改类的情况下测试非公开方法。System.Reflection允许您使用绑定标志访问和调用非公共方法,因此您可以对 NUnit 进行黑客攻击或设置自己的框架。或者(我认为更容易的方法),您可以设置编译时标志(#if TESTING)来更改访问修饰符,从而可以使用现有的框架。 - harpo
25
私有方法是一些实现细节。编写单元测试最重要的一点就是允许在不改变行为的情况下重构实现。如果私有方法变得非常复杂,希望能够单独覆盖它们进行测试,那么可以使用这个引理:“私有方法总是可以成为一个单独类的公共(或内部)方法”。也就是说,如果一段逻辑足够复杂,需要进行测试,那么很可能就是它自己的类,具有自己的测试的候选对象。 - Anders Forsgren
8
与_integration(集成)或_functional(功能)测试不同,_unit_测试的重点正是测试这些细节。代码覆盖率被认为是一个重要的单元测试指标,因此理论上_每个_逻辑部分都“足够先进以进行测试”。@AndersForsgren - Kyle Strand
显示剩余3条评论

51

编写单元测试的常见模式是只测试公共方法。

如果您发现有许多私有方法需要测试,通常这表明您应该重构代码。

将这些方法在当前所在类上设置为公共方法是错误的。这会破坏您希望该类具有的契约。

将它们移到助手类并在那里设置为公共方法可能是正确的做法。此类可能不会被您的 API 公开暴露。

这样测试代码就永远不会与您的公共代码混合。

类似的问题是测试私有类,即您不从程序集导出的类。在这种情况下,可以使用属性 InternalsVisibleTo 显式地使您的测试代码程序集成为生产代码程序集的友元。


1
+1 是啊!我在写测试的时候也得出了这个结论,好建议! - andy
1
将你想要测试但不想向API用户公开的私有方法移动到一个未公开的不同类中,并在那里将它们公开,这正是我正在寻找的。 - Thomas N
嗨。最近收到了一些没有评论的负评。请提供一些反馈,以解释您的想法或关注点。 - morechilli
9
如果你有一个私有方法,在类的多个地方被调用,只为该类执行某些非常特定的操作,并且不需要作为公共API的一部分暴露出来...... 你是在说把它放在一个辅助类中,只是为了进行测试?那么你还不如将其设为该类的公共方法。 - Daniel Macias
距离我写下这个答案已经有7年了……现在看来,状态性是另一个有趣的考虑点。将该方法移动到帮助类的替代方案是确保该方法是静态的,从而消除它对对象中状态进行突变的能力,因此也许消除了公开它的某些担忧(通常将其移动到帮助类会导致相同的更改)。然后决策仅成为API的整洁度之一。 - morechilli
显示剩余2条评论

22

通过将你的测试程序集声明为目标程序集的友元程序集,可以测试私有方法。请参考下面链接了解详细信息:

http://msdn.microsoft.com/en-us/library/0tke9fxk.aspx

这种方法很有用,因为它基本上将测试代码与生产代码分开。我自己从未使用过这种方法,因为我从未觉得有需要。我想你可以使用它来尝试并测试极端案例,这些案例无法在测试环境中复制,以了解你的代码如何处理它。

正如先前所说,你确实不应该需要测试私有方法。你更有可能想将代码重构为较小的构建块。一个可能有帮助的提示是,尝试考虑系统相关的领域,并思考居住在这个领域的“真实”对象。你系统中的对象/类应直接与真实对象相关联,从而使你能够隔离对象应包含的确切行为,并限制对象的责任。这将意味着你是在逻辑上进行重构,而不仅仅是为了测试特定方法而进行重构;你将能够测试对象的行为。

如果你仍然感觉需要测试内部方法,你可能还需要在测试中考虑模拟,因为你可能想要专注于一段代码。模拟是将对象的依赖项注入其中,但注入的对象不是“真实”或生产对象。它们是带有硬编码行为的虚假对象,以更容易地隔离行为错误。Rhino.Mocks是一种流行的免费模拟框架,它将基本上为你编写对象。TypeMock.NET(一个带有社区版的商业产品)是一种更强大的框架,可以模拟CLR对象。当测试数据库应用程序时,对于模拟SqlConnection/SqlCommand和Datatable类非常有用。

希望这个答案能给你更多有关单元测试的信息,并帮助你从单元测试中获得更好的结果。


15
可以使用NUnit测试内部方法,而不是私有方法。 - TrueWill

6

我支持测试私有方法的能力。当xUnit开始时,它旨在测试代码编写后的功能。测试接口对于这个目的来说已经足够了。

单元测试已经发展到测试驱动开发。拥有测试所有方法的能力对于这个应用程序是有用的。


5
这个问题已经存在很久了,但我想分享我的解决方法。基本上,我把所有的单元测试类都放在它们所测试的程序集中,使用一个名为“UnitTest”的命名空间来包含这些类,该命名空间位于该程序集的“default”命名空间下,每个测试文件都被包裹在一个:
#if DEBUG

...test code...

#endif

这里所说的“block”意味着a)它未在发布中分发,并且 b)我可以使用internal / Friend级别声明而无需进行额外操作。

更为重要的是,它提供了使用partial类的机会,可以用于创建代理以测试私有方法。例如,要测试返回整数值的私有方法:

public partial class TheClassBeingTested
{
    private int TheMethodToBeTested() { return -1; }
}

在汇编的主要类和测试类中:
#if DEBUG

using NUnit.Framework;

public partial class TheClassBeingTested
{
    internal int NUnit_TheMethodToBeTested()
    {
        return TheMethodToBeTested();
    }
}

[TestFixture]
public class ClassTests
{
    [Test]
    public void TestMethod()
    {
        var tc = new TheClassBeingTested();
        Assert.That(tc.NUnit_TheMethodToBeTested(), Is.EqualTo(-1));
    }
}

#endif

显然,在开发过程中,您需要确保不使用此方法,但如果您这样做,发布版本将很快指出无意间调用它。

4
单元测试的主要目标是测试类的公共方法。这些公共方法将使用那些私有方法。单元测试将测试公开可用内容的行为。

3

如果这个回答没有解决问题,那么使用反射、#if #endif语句或使私有方法可见等解决方案并不能解决问题。不将私有方法公开可能有几个原因...例如,如果它是生产代码,并且团队正在回顾性地编写单元测试。

对于我正在工作的项目,只有MSTest(遗憾的是)似乎有一种方法,使用访问器来单元测试私有方法。


2

你不要测试私有函数。

有方法可以使用反射来获取私有方法和属性。但这并不容易,我强烈不建议这种做法。

你根本不应该测试任何非公共的东西。

如果你有一些内部方法和属性,你应该考虑将其改为公共的,或者将测试与应用程序一起发布(我认为这不是一个问题)。

如果你的客户能够运行测试套件并看到所提供的代码实际上是“工作的”,我不认为这是一个问题(只要你不通过此方式泄露你的知识产权)。我在每个版本中包含的内容是测试报告和代码覆盖率报告。


一些客户试图控制预算,开始讨论测试范围。很难向这些人解释编写测试并不是因为你是一个糟糕的程序员,也不是因为你不相信自己的技能。 - MrFox

2
在单元测试理论中,只有公共成员(即类的公共成员)应该被测试。但在实践中,开发人员通常也想测试内部成员 - 这并不是坏事。是的,这与理论相悖,但在实践中有时很有用。
所以,如果您真的想测试内部成员,可以使用以下其中一种方法:
1. 将成员设置为公共。在许多书籍中,作者建议采用这种简单的方法。 2. 您可以将成员设置为内部,并将 InternalVisibleTo 添加到程序集中。 3. 您可以将类成员设置为受保护,并从您要测试的类继承您的测试类。
代码示例(伪代码):
public class SomeClass
{
    protected int SomeMethod() {}
}
[TestFixture]
public class TestClass : SomeClass{
    
    protected void SomeMethod2() {}
    [Test]
    public void SomeMethodTest() { SomeMethod2(); }
}

似乎你的示例代码里藏了一只蟑螂 ;) NUnit 的 fixture 内部方法必须是公共的,否则会出现 Message: Method is not public - Gucu112
@Gucu112 谢谢。我已经修复了。总的来说,这并不重要,因为目标是展示设计视角下的方法。 - Maxim Kitsenko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接