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


104

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

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

Answers:


74

通常,单元测试针对的是类的公共接口,其理论是实现是无关紧要的,只要从客户的角度来看结果是正确的即可。

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


1
+1我刚刚遇到这个问题,在我的情况下,在私有和公共之间会发生一个“映射”算法,这意味着如果我要对公众进行单元测试,那实际上是一个集成测试。在这种情况下,我认为它试图在代码中编写测试点以解决设计问题。在这种情况下,我可能应该创建一个执行该“私有”方法的不同类。
安迪

140
我完全不同意这种说法。单元测试与类无关,而与代码有关。如果我有一个带有一个公共方法的类,而有十个私有方法用来创建公共方法的结果,那么我将无法确保每种私有方法都能按预期的方式工作。我可以尝试在公共方法上创建测试用例,以正确的方式命中正确的私有方法。但是我不知道私有方法是否真正被调用,除非我相信公共方法永远不会改变。专用方法旨在对代码的生产用户(而不是作者)专用。
Quango 2013年

3
@Quango,在标准测试设置中,“作者” 代码的生产用户。但是,如果您没有发现设计问题,则可以选择在不更改类的情况下测试非公共方法。 System.Reflection允许您使用绑定标志访问和调用非公共方法,因此您可以破解NUnit或设置自己的框架。或者(我想更容易),您可以设置一个编译时标志(#if TESTING)来更改访问修饰符,从而允许您使用现有框架。
harpo 2013年

23
私有方法是实现细节。进行单元测试的许多要点是允许重构实现而不更改行为。如果私有方法变得如此复杂以至于一个人想单独用测试来覆盖它们,那么就可以使用这个引理:“私有方法可以始终是单独类的公共(或内部)方法”。也就是说,如果某种逻辑足够先进以进行测试,则它很可能成为具有自己的测试的自己的类。
2013年

6
@AndersForsgren但是,与集成功能测试相反,单元测试的重点是测试这些细节。而且代码覆盖率被认为是重要的单元测试指标,因此,从理论上讲,每条逻辑都“足够先进,可以接受测试”。
凯尔·斯特兰德

57

虽然我同意单元测试的重点应该是公共接口,但是如果您也测试私有方法,您会对代码的印象更深。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
测试那些小的辅助方法将捕获单元测试中的单元部分。也不是全部都适用于实用程序类。
约翰·拉尔森

12
这正是我所需要的。谢谢!我所需要做的只是检查是否正确设置了私有字段,并且我不需要花3个小时来弄清楚如何通过公共界面确定该字段。这是无法一时重构的现有代码。有趣的是,如何用理想主义的教条来回答一个简单的问题……
Droj 2012年

5
这应该是选定的答案,因为它是唯一实际回答该问题的答案。
bavaza 2013年

6
同意 选择的答案各有千秋,但这是对“我应该测试私有方法”的回答,而不是OP的问题。
chesterbr 2014年

2
可以。谢谢!这是很多人正在寻找的。这应该是选择的答案,
Able Johnson

47

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

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

将这些方法在它们当前所在的类上公开是错误的。那会破坏您希望该类拥有的合同。

将他们转移到帮助者级别并在其中公开是正确的。您的API可能未公开此类。

这样,测试代码就不会与您的公共代码混在一起。

一个类似的问题是测试私有类,即。您不会从程序集中导出的类。在这种情况下,您可以使用属性InternalsVisibleTo将测试代码程序集明确地与生产代码程序集成为朋友。


1
+是的!我只是在编写测​​试时得出了这个结论,很好的建议!
安迪

1
将您要测试但不向API用户公开的私有方法移到另一个未公开并使其公开的类中,这正是我在寻找的东西。
Thomas N

谢谢@morechilli。以前从未见过InternalsVisibleTo。使测试变得更加容易。
Andrew MacNaughton 2014年

你好 最近收到了一些否决,没有评论。请提供一些反馈,以解释您的想法或疑虑。
morechilli 2015年

6
因此,如果您有一个私有方法在一个类中的多个位置被调用,以执行该类仅需要的非常特定的操作,并且该方法不作为公共API的一部分公开...就是说将其放在帮助器中上课只是为了测试?您不妨将其公开授课。
Daniel Macias

21

通过将测试程序集声明为要测试的目标程序集的朋友程序集,可以测试私有方法。有关详细信息,请参见下面的链接:

http://msdn.microsoft.com/zh-CN/library/0tke9fxk.aspx

这很有用,因为它主要将测试代码与生产代码分开。我从未亲自使用过这种方法,因为我从未发现过需要这种方法。我想您可以使用它来测试极端的测试用例,而这些测试用例根本无法在测试环境中复制以查看您的代码如何处理它。

如前所述,您确实不需要测试私有方法。您比likley更希望将代码重构为较小的构建块。当您进行重构时,可能会帮助您的一个技巧是尝试考虑系统所关联的域,并考虑驻留在该域中的“实际”对象。系统中的对象/类应直接与真实对象相关,这将使您能够隔离出对象应包含的确切行为,并限制对象的责任。这将意味着您在逻辑上进行重构,而不仅仅是使测试特定方法成为可能。您将能够测试对象的行为。

如果您仍然需要进行内部测试,那么您可能还想在测试中考虑模拟,因为您很想集中精力于一段代码。模拟是在其中注入对象依赖项的对象,但是注入的对象不是“真实”或生产对象。它们是具有硬编码行为的伪对象,可以更轻松地隔离行为错误。Rhino.Mocks是一个流行的免费模拟框架,该框架实际上将为您编写对象。TypeMock.NET(提供社区版本的商业产品)是一个功能更强大的框架,可以模拟CLR对象。例如,在测试数据库应用程序时,对模拟SqlConnection / SqlCommand和Datatable类非常有用。

希望这个答案会给您更多的信息,以使您大致了解单元测试,并帮助您从单元测试中获得更好的结果。


12
可以测试内部方法,而不是私有方法(使用NUnit)。
TrueWill

6

我赞成具有测试私有方法的能力。xUnit启动时,它旨在在编写代码后测试功能。为此,只需测试接口即可。

单元测试已经演变为测试驱动的开发。能够测试所有方法的功能对该应用程序很有用。


5

这个问题已经进入晚期,但我想我会分享自己的方式。

基本上,我将所有我的单元测试类放在程序集中,它们都在该程序集的“默认”下方的“ UnitTest”命名空间中进行测试-每个测试文件都包装在:

#if DEBUG

...test code...

#endif

阻止,而所有这些都意味着a)不在发行版本中分发,b)我可以使用internal/Friend level声明而不会发生箍跳。

与该问题更相关的是,此方法提供的另一件事是使用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

显然,您需要确保在开发过程中不要使用此方法,尽管如果您使用Release版本,它将很快表示对其进行了无意的调用。



3

如果这不能解决问题,则表示歉意,但是使用反射,#if #endif语句或使私有方法可见的解决方案无法解决问题。有几种原因可能导致私有方法不可见……如果是生产代码,而团队正在追溯性地编写单元测试,该怎么办?

对于我仅在进行MSTest的项目(不幸),似乎有一种使用访问器对私有方法进行单元测试的方法。


2

您不测试私有功能。有多种方法可以使用反射来进入私有方法和属性。但这并不是一件容易的事,我强烈反对这种做法。

您根本不应该测试任何不公开的东西。

如果您有一些内部方法和属性,则应考虑将其更改为公开状态,或将其随应用程序一起交付测试(这并不是我真正认为的问题)。

如果您的客户能够运行Test-Suite,并且看到您提供的代码实际上是“有效的”,那么我认为这不是问题(只要您不通过它放弃IP)。我在每个版本中包括的内容是测试报告和代码覆盖率报告。


一些客户试图使预算保持较小,并开始讨论测试范围。很难向这些人解释,编写测试并不是因为您是一个糟糕的编码人员,并且不信任自己的技能。
MrFox

1

理论上,单元测试仅应测试合同。即只有班上的公众成员。但实际上,开发人员通常希望测试内部成员。-而且还不错。是的,它与理论背道而驰,但在实践中有时可能有用。

因此,如果您真的想测试内部成员,则可以使用以下方法之一:

  1. 公开您的会员。在许多书中,作者建议这种方法很简单
  2. 您可以使您的成员成为内部成员,并添加InternalVisibleTo到assebly
  3. 您可以使类成员受到保护,并从测试中的类继承测试类。

代码示例(伪代码):

public class SomeClass
{
    protected int SomeMethod() {}
}
[TestFixture]
public class TestClass : SomeClass{

    protected void SomeMethod2() {}
    [Test]
    public void SomeMethodTest() { SomeMethod2(); }
}

似乎蟑螂隐藏在您的示例代码中;)NUnit固定装置中的方法必须是公共的,否则您将得到Message: Method is not public
Gucu112 '17

@ Gucu112谢谢。我修好了它。总的来说,这并不重要,因为目标是展示设计观点。
burzhuy

1

您可以将方法设置为内部保护,然后 assembly: InternalsVisibleTo("NAMESPACE") 用于测试名称空间。

因此,不!您无法访问私有方法,但这是一种解决方法。


0

我将使私有方法包可见。这样,您就可以合理地保持私有状态,同时仍然能够测试这些方法。我不同意人们所说的公共接口是唯一应该测试的接口。私有方法中通常存在真正关键的代码,仅通过外部接口就无法正确测试它们。

因此,实际上可以归结为您是否更关心正确的代码或信息隐藏。我想说软件包的可见性是一个很好的折衷,因为要访问那些方法,有人必须将其类放入您的软件包中。那确实应该使他们三思而后行,这是否是一件很明智的事情。

我是一名Java专家,所以程序包可视性在C#中可能被称为完全不同的东西。足以说这是两个类必须位于同一名称空间中才能访问这些方法的时候。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.