你如何用 NUnit 测试私有方法?
Posted
技术标签:
【中文标题】你如何用 NUnit 测试私有方法?【英文标题】:How do you test private methods with NUnit? 【发布时间】:2010-09-19 23:23:48 【问题描述】:我想知道如何正确使用 NUnit。首先,我创建了一个单独的测试项目,它使用我的主项目作为参考。但在那种情况下,我无法测试私有方法。我的猜测是我需要将我的测试代码包含到我的主代码中?! - 这似乎不是正确的方法。 (我不喜欢发送带有测试的代码的想法。)
如何使用 NUnit 测试私有方法?
【问题讨论】:
【参考方案1】:一般来说,单元测试针对的是类的公共接口,理论上实现并不重要,只要从客户的角度来看结果是正确的。
因此,NUnit 不提供任何测试非公共成员的机制。
【讨论】:
+1 我刚刚遇到了这个问题,在我的情况下,如果我要对公众进行单元测试,那么在私有和公共含义之间发生了一个“映射”算法,它会实际上是一个集成测试。在这种情况下,我认为它试图将测试点写入代码中的设计问题。在这种情况下,我可能应该创建一个执行该“私有”方法的不同类。 我完全不同意这个论点。单元测试不是关于类,而是关于代码。如果我有一个具有一个公共方法的类,并且有十个私有方法用于创建公共方法的结果,那么我无法确保每个私有方法都以预期的方式工作。我可以尝试在公共方法上以正确的方式创建正确的私有方法的测试用例。但是我不知道是否真的调用了私有方法,除非我相信公共方法永远不会改变。私有方法旨在为代码的生产用户而不是作者私有。 @Quango,在标准测试设置中,“作者”是代码的生产用户。不过,如果您没有发现设计问题,您可以选择在不更改类的情况下测试非公共方法。System.Reflection
允许您使用绑定标志访问和调用非公共方法,因此您可以破解 NUnit 或设置自己的框架。或者(我认为更简单),您可以设置一个编译时标志(#if TESTING)来更改访问修饰符,从而允许您使用现有框架。
私有方法是一个实现细节。进行单元测试的很多意义在于允许重构实现而不改变行为。如果私有方法变得如此复杂以至于人们想用单独的测试来覆盖它们,那么可以使用这个引理:“私有方法总是可以是单独类的公共(或内部)方法”。也就是说,如果一个逻辑足够先进,可以接受测试,它可能是它自己的类,有自己的测试的候选者。
@AndersForsgren 但是,unit 测试的重点,与 integration 或 functional 测试相反,正是为了测试这样的细节。并且代码覆盖率被认为是一个重要的单元测试指标,因此理论上每个逻辑片段“都足够先进,可以进行测试”。【参考方案2】:
虽然我同意单元测试的重点应该是公共接口,但如果您也测试私有方法,您对代码的印象就会更加细致。 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。上面的例子只是实例方法。
【讨论】:
测试那些小的辅助方法可以捕获单元测试中的单元部分。也不是所有的都适合实用程序类。 这正是我所需要的。谢谢!我只需要检查一个私有字段是否设置正确,我不需要花 3 个小时来弄清楚如何通过公共接口来确定它。这是不能随心所欲地重构的现有代码。一个简单的问题怎么可以用理想主义的教条来回答,这很有趣…… 这应该是选择的答案,因为它是唯一真正回答问题的答案。 同意。选择的答案有其优点,但它是“我应该测试私有方法”的答案,而不是 OP 的问题。 它有效。谢谢!这是很多人都在寻找的。这应该是选择的答案,【参考方案3】:编写单元测试的一个常见模式是只测试公共方法。
如果您发现有许多私有方法要测试,通常这表明您应该重构代码。
将这些方法公开在它们当前所在的类上是错误的。 这将违反您希望该班级拥有的合同。
将它们移到辅助类并在那里公开它们可能是正确的。 您的 API 可能不会公开此类。
这样测试代码永远不会与您的公共代码混合。
一个类似的问题是测试私有类,即。您不从程序集中导出的类。 在这种情况下,您可以使用属性 InternalsVisibleTo 显式使您的测试代码程序集成为生产代码程序集的朋友。
【讨论】:
+ 1 是的!我刚刚在编写测试时得出了这个结论,好建议! 将您想要测试但不向 API 用户公开的私有方法移动到未公开的不同类并将它们公开,这正是我想要的。 感谢@morechilli。以前从未见过 InternalsVisibleTo。使测试变得更加容易。 嗨。最近收到了一些没有 cmets 的反对票。请提供一些反馈来解释您的想法或疑虑。 因此,如果您有一个私有方法在一个类中的多个位置被调用以执行该类仅需要的非常具体的操作,并且它不会作为公共 API 的一部分公开......您'是说把它放在一个助手类中只是为了测试?你不妨把它公开给全班。【参考方案4】:可以通过将您的测试程序集声明为您正在测试的目标程序集的友元程序集来测试私有方法。详情见以下链接:
http://msdn.microsoft.com/en-us/library/0tke9fxk.aspx
这很有用,因为它主要将测试代码与生产代码分开。我自己从来没有使用过这种方法,因为我从来没有发现需要它。我想您可以使用它来尝试和测试您根本无法在测试环境中复制的极端测试用例,以查看您的代码如何处理它。
正如已经说过的,您确实不需要测试私有方法。您不仅希望将代码重构为更小的构建块。当您开始重构时,一个可能对您有所帮助的技巧是尝试考虑与您的系统相关的域,并考虑居住在该域中的“真实”对象。您系统中的对象/类应直接与真实对象相关,这将允许您隔离对象应包含的确切行为并限制对象职责。这将意味着您在逻辑上进行重构,而不仅仅是为了使测试特定方法成为可能;您将能够测试对象的行为。
如果您仍然觉得需要进行内部测试,那么您可能还需要考虑在测试中进行模拟,因为您可能希望专注于一段代码。模拟是将对象依赖项注入其中,但注入的对象不是“真实”或生产对象。它们是具有硬编码行为的虚拟对象,以便更容易隔离行为错误。 Rhino.Mocks 是一个流行的免费模拟框架,它基本上会为您编写对象。 TypeMock.NET(提供社区版的商业产品)是一个更强大的框架,可以模拟 CLR 对象。对于模拟 SqlConnection/SqlCommand 和 Datatable 类非常有用,例如在测试数据库应用程序时。
希望这个答案能给您提供更多信息,让您大致了解单元测试,并帮助您从单元测试中获得更好的结果。
【讨论】:
可以测试 internal 方法,而不是私有(使用 NUnit)。【参考方案5】:我赞成拥有测试私有方法的能力。当 xUnit 启动时,它的目的是在编写代码后测试功能。为此目的,测试接口就足够了。
单元测试已经发展为测试驱动的开发。具有测试所有方法的能力对于该应用程序很有用。
【讨论】:
【参考方案6】:这个问题是在它的高级年,但我想我会分享我这样做的方式。
基本上,我的所有单元测试类都在他们正在测试的程序集中的“UnitTest”命名空间中,该名称空间位于该程序集的“默认”下方 - 每个测试文件都包含在:
#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
显然,您需要确保在开发时不使用此方法,尽管如果您这样做,Release 版本很快就会表明对它的无意调用。
【讨论】:
【参考方案7】:单元测试的主要目标是测试类的公共方法。那些公共方法将使用那些私有方法。单元测试将测试公开内容的行为。
【讨论】:
【参考方案8】:抱歉,如果这不能回答问题,但使用反射、#if #endif 语句或使私有方法可见等解决方案不能解决问题。不让私有方法可见可能有几个原因......例如,如果它是生产代码并且团队正在回顾性地编写单元测试怎么办。
对于我正在处理的项目,只有 MSTest(遗憾的是)似乎有办法使用访问器对私有方法进行单元测试。
【讨论】:
【参考方案9】:您不测试私有函数。 有一些方法可以使用反射来进入私有方法和属性。但这并不容易,我强烈反对这种做法。
你根本不应该测试任何不公开的东西。
如果您有一些内部方法和属性,您应该考虑将其更改为公开,或者将您的测试与应用程序一起发布(我认为这不是问题)。
如果您的客户能够运行测试套件并看到您交付的代码实际上是“工作”,我不认为这是一个问题(只要您不通过这个泄露您的 IP )。我在每个版本中都包含测试报告和代码覆盖率报告。
【讨论】:
一些客户试图控制预算并开始讨论测试范围。很难解释这些人写测试不是因为你是一个糟糕的编码器并且不相信自己的技能。【参考方案10】:在单元测试的理论上,只有合约应该被测试。即只有班级的公共成员。但在实践中,开发人员通常也想测试内部成员 - 这还不错。是的,它与理论背道而驰,但实际上它有时会很有用。
所以如果你真的想测试内部成员,你可以使用以下方法之一:
-
公开您的会员。在许多书中,作者都建议这样做
方法很简单
您可以将您的成员设为内部成员并将InternalVisibleTo 添加到
组装
您可以保护类成员并继承您的测试类
来自你的被测班级。
代码示例(伪代码):
public class SomeClass
protected int SomeMethod()
[TestFixture]
public class TestClass : SomeClass
protected void SomeMethod2()
[Test]
public void SomeMethodTest() SomeMethod2();
【讨论】:
似乎 cockroach 隐藏在您的示例代码中;) NUnit 夹具中的方法必须是公共的,否则您会得到Message: Method is not public
。
@Gucu112 谢谢。我修好了它。一般来说,这并不重要,因为目标是展示设计观点的方法。【参考方案11】:
你可以让你的方法在内部受到保护,然后使用
assembly: InternalsVisibleTo("NAMESPACE")
到您的测试命名空间。
因此,不!您无法访问私有方法,但这是一种解决方法。
【讨论】:
【参考方案12】:如果你需要访问类的非静态私有方法,可以试试这个:
class Foo
private int Sum(int num1, int num2)
return num1 + num2;
MethodInfo sumPrivate =
typeof(Foo).GetMethod("Sum", BindingFlags.NonPublic | BindingFlags.Instance);
int sum = (int)sumPrivate.Invoke(new Foo(), new object[] 2, 5 );
// 7
【讨论】:
【参考方案13】:我会让私有方法包可见。这样你就可以保持它相当私密,同时仍然能够测试这些方法。我不同意人们说公共接口是唯一应该测试的接口。私有方法中经常有非常关键的代码,仅通过外部接口无法正确测试。
所以归根结底是您是否更关心正确的代码或信息隐藏。我想说包可见性是一个很好的折衷方案,因为为了访问这些方法,有人必须将他们的类放在你的包中。这真的应该让他们三思而后行,看看这是否真的很聪明。
顺便说一句,我是一个 Java 人,所以包 visiblilty 在 C# 中可能被称为完全不同的东西。可以说,两个类必须在同一个命名空间中才能访问这些方法。
【讨论】:
以上是关于你如何用 NUnit 测试私有方法?的主要内容,如果未能解决你的问题,请参考以下文章
是否可以为具有 1)返回类型 void、2)访问说明符私有或受保护的方法编写单元测试?
平台化测试难度大?京东教你如何用无人测试实现产品质量效率双提升