你如何测试私有方法?

我正在建立一个有一些公共和私人方法的类库。 我希望能够对私有方法进行单元测试(大部分是在开发过程中,但对未来的重构也是有用的)。

什么是正确的方法来做到这一点?

你应该使用InternalsVisibleToAttribute 。

如果你想单元测试一个私有方法,可能是错误的。 单元测试(一般来说)是为了测试一个类的接口,意味着它的公共(和受保护的)方法。 你当然可以“破解”这个解决方案(即使只是公开方法),但是你也可以考虑:

  1. 如果你想测试的方法真的值得测试,那么把它移到自己的类中可能是值得的。
  2. 将更多的测试添加到调用私有方法的公共方法中,测试私有方法的功能。 (正如评论员所指出的那样,如果这些私有方法的功能真的成为公共接口的一部分,那么你只应该这样做,如果他们实际上执行的是隐藏在用户面前的功能(即单元测试),这可能是不好的)。

测试私有方法可能没有用处。 不过,我有时也喜欢从测试方法中调用私有方法。 大多数情况下,为了防止代码重复测试数据生成… … –

微软为此提供了两种机制:

访问器

  • 转到类定义的源代码
  • 用鼠标右键单击类的名称
  • 选择“创建私人访问者”
  • 选择应该创建访问器的项目=>最终将得到一个名为foo_accessor的新类。 这个类将在编译过程中动态生成,并提供所有可用的公共成员。

然而,当涉及到原始类的接口的改变时,该机制有时难以处理。 所以,我绝大多数时候都避免使用这个。

PrivateObject类另一种方法是使用Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject

 // Wrap an already existing instance PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped ); // Retrieve a private field MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" ); // Call a private method accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} ); 

我不同意“你只应该对测试外部接口感兴趣”的理念。 这有点像说汽车修理店应该只有测试,看轮子是否转动。 是的,最终我对外部行为感兴趣,但是我喜欢我自己的私人的内部测试,以便更具体一些。 是的,如果我重构,我可能不得不改变一些测试,但除非是大规模的重构,否则我只需要改变一些测试,其他(不变的)内部测试依然有效的事实是一个很好的指标重构已经成功。

您可以尝试仅使用公共接口覆盖所有内部案例,理论上可以完全通过使用公共接口来测试每个内部方法(或者至少每一个重要的方法),但是您可能不得不站在您的头上来实现这个以及测试用例之间的连接是通过公共接口和解决方案的内部部分来进行测试的,可能很难或根本无法辨别。 指出,保证内部机器正常工作的单个测试非常值得重构时发生的微小的测试变化 – 至少这是我的经验。 如果每次重构都必须对测试进行大的修改,那么也许这是没有意义的,但在这种情况下,也许你应该完全重新考虑你的设计。 一个好的设计应该足够灵活,以便在不进行大规模重新设计的情况下进行大部分更改

在罕见的情况下,我想测试私有函数,我通常修改它们以保护,而我写了一个公共包装函数的子类。

班上:

 ... protected void APrivateFunction() { ... } ... 

测试子类:

 ... [Test] public void TestAPrivateFunction() { APrivateFunction(); //or whatever testing code you want here } ... 

我认为应该问一个更基本的问题,那就是你为什么试图首先测试私有方法。 这是一种代码气味,你试图通过该类的公共接口来测试私有方法,而该方法是私有的,因为它是一个实现细节。 人们只应该关注公众界面的行为,而不是关注如何实施。

如果我想测试私有方法的行为,通过使用常见的重构,我可以将其代码提取到另一个类中(可能具有程序包级可见性,因此请确保它不是公共API的一部分)。 然后,我可以孤立地测试它的行为。

重构的产物意味着私有方法现在是一个独立的类,它已经成为原始类的合作者。 它的行为将通过自己的单元测试变得很好理解。

当我尝试测试原始类时,我可以嘲弄它的行为,以便我可以专注于测试该类的公共接口的行为,而不必测试公共接口的组合爆炸以及所有私有方法的行为。

我看到这类似于驾驶汽车。 当我开车的时候,我不会开着发动机罩,所以我可以看到发动机在工作。 我依靠汽车提供的接口,即转速表和车速表来了解发动机在工作。 我依靠的事实是,当我按下油门时,汽车实际上在移动。 如果我想测试引擎,我可以做隔离检查。 :d

当然,如果你有一个遗留的应用程序,直接测试私有方法可能是最后的手段,但是我更喜欢遗留代码被重构以实现更好的测试。 迈克尔羽毛已经写了一本关于这个主题的伟大的书。 http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052

私人类型,内部和私人成员是因为某种原因,而且往往不想直接混淆它们。 如果你这样做,很可能你会晚点休息,因为不能保证创建这些程序集的人会保持私有/内部实现。

但是,有时候,在编译或第三方程序集的时候,我自己最终想要使用私有或内部构造函数初始化一个私有类或类。 或者,有时,当处理预编译的遗留库,我不能改变 – 我最终编写一些私人方法的测试。

因此出生的AccessPrivateWrapper – http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html – 这是一个快速的包装类,将使工作轻松使用C#4.0的动态功能和反射。

您可以创建内部/私有类型

  //Note that the wrapper is dynamic dynamic wrapper = AccessPrivateWrapper.FromType (typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor"); //Access the private members wrapper.PrivateMethodInPrivateClass(); 

我也使用了InternalsVisibleToAttribute方法。 值得一提的是,如果你为了达到这个目的而使你之前的私有方法变得不自在,那么也许他们不应该成为单元测试的主题。

毕竟,你正在测试你的类的行为 ,而不是它的具体实现 – 你可以改变后者而不改变前者,你的测试仍然应该通过。

那么你可以用两种方法来测试私有方法

  1. 你可以创建PrivateObject类的实例,语法如下

     PrivateObject obj= new PrivateObject(PrivateClass); //now with this obj you can call the private method of PrivateCalss. obj.PrivateMethod("Parameters"); 
  2. 你可以使用反射。

     PrivateClass obj = new PrivateClass(); // Class containing private obj Type t = typeof(PrivateClass); var x = t.InvokeMember("PrivateFunc", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, obj, new object[] { 5 }); 

MS Test内置了一个很好的功能,通过创建一个名为VSCodeGenAccessors的文件,可以在项目中使用私有成员和方法

 [System.Diagnostics.DebuggerStepThrough()] [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")] internal class BaseAccessor { protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject; protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type) { m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type); } protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type) : this(null, type) { } internal virtual object Target { get { return m_privateObject.Target; } } public override string ToString() { return this.Target.ToString(); } public override bool Equals(object obj) { if (typeof(BaseAccessor).IsInstanceOfType(obj)) { obj = ((BaseAccessor)(obj)).Target; } return this.Target.Equals(obj); } public override int GetHashCode() { return this.Target.GetHashCode(); } } 

使用来自BaseAccessor的类

 [System.Diagnostics.DebuggerStepThrough()] [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")] internal class SomeClassAccessor : BaseAccessor { protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass)); internal SomeClassAccessor(global::Namespace.Someclass target) : base(target, m_privateType) { } internal static string STATIC_STRING { get { string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING"))); return ret; } set { m_privateType.SetStaticField("STATIC_STRING", value); } } internal int memberVar { get { int ret = ((int)(m_privateObject.GetField("memberVar"))); return ret; } set { m_privateObject.SetField("memberVar", value); } } internal int PrivateMethodName(int paramName) { object[] args = new object[] { paramName}; int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] { typeof(int)}, args))); return ret; } 

有两种私人方法。 静态私有方法和非静态私有方法(实例方法)。 以下两篇文章解释了如何用示例对私有方法进行单元测试。

  1. 单元测试静态私有方法
  2. 单元测试非静态私有方法

我倾向于不使用编译器指令,因为它们使事情变得很快。 如果你真的需要这种方法,那么减轻它的一个方法就是把它们放到一个部分类中,让你的内部版本在生产版本时忽略这个.cs文件。

在CodeProject上,有一篇文章简要讨论了测试私有方法的优缺点。 然后它提供了一些反射代码来访问私有方法(类似于Marcus在上面提供的代码)。我发现这个示例唯一的问题是代码没有考虑重载的方法。

你可以在这里找到这篇文章:

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx

声明它们是internal ,然后使用InternalsVisibleToAttribute来让你的单元测试程序集看到它们。

有时候,测试私有声明是很好的。 基本上,编译器只有一个公共方法:Compile(string outputFileName,params string [] sourceSFileNames)。 我相信你明白,如果不测试每个“隐藏的”声明,就很难测试这种方法!

这就是为什么我们创建了Visual T#:使测试更简单。 这是一个免费的.NET编程语言(兼容C#v2.0)。

我们添加了“.-”运营商。 它只是表现得像'。' 除了你也可以从你的测试中访问任何隐藏的声明,而不用改变你的测试项目中的任何东西。

看看我们的网站: 免费下载 。

你不应该首先测试你的代码的私有方法。 你应该测试“公共接口”或API,你的类的公共事物。 这些API都是公开给外部调用者的方法。

原因是,一旦你开始测试你的类的私有方法和内部,你将你的类的实现(私有事物)耦合到你的测试。 这意味着当你决定改变你的实现细节时,你也将不得不改变你的测试。

你应该为此避免使用InternalsVisibleToAtrribute。

伊恩·库珀(Ian Cooper)在这方面进行了一次精彩的演讲: 伊恩·库珀(Ian Cooper):TDD,哪里出了问题?

MbUnit得到了一个很好的包装这个叫Reflector。

 Reflector dogReflector = new Reflector(new Dog()); dogReflector.Invoke("DreamAbout", DogDream.Food); 

你也可以设置和获取属性的值

 dogReflector.GetProperty("Age"); 

关于“测试私人”我同意..在完美的世界。 做私人单元测试没有意义。 但在现实世界中,您可能最终想要写私人测试而不是重构代码。

我很惊讶没有人这样说过,但我采用的一个解决方案是在类内部建立一个静态方法来测试自己。 这使您可以访问公开和私人测试的所有内容。

而且,在脚本语言(使用OO功能,如Python,Ruby和PHP)中,您可以在运行时自行测试文件。 确保您的更改不会破坏任何内容的快速方法。 这显然是一个可扩展的解决方案来测试你的所有类:只运行它们。 (你也可以在其他语言中使用void main来运行测试)。

我想在这里创建一个清晰的代码示例,您可以在任何想要测试私有方法的类上使用它。

在你的测试用例类中只包含这些方法,然后按照指示使用它们。

  /** * * @var Class_name_of_class_you_want_to_test_private_methods_in * note: the actual class and the private variable to store the * class instance in, should at least be different case so that * they do not get confused in the code. Here the class name is * is upper case while the private instance variable is all lower * case */ private $class_name_of_class_you_want_to_test_private_methods_in; /** * This uses reflection to be able to get private methods to test * @param $methodName * @return ReflectionMethod */ protected static function getMethod($methodName) { $class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in'); $method = $class->getMethod($methodName); $method->setAccessible(true); return $method; } /** * Uses reflection class to call private methods and get return values. * @param $methodName * @param array $params * @return mixed * * usage: $this->_callMethod('_someFunctionName', array(param1,param2,param3)); * {params are in * order in which they appear in the function declaration} */ protected function _callMethod($methodName, $params=array()) { $method = self::getMethod($methodName); return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params); } 

$ this – > _ callMethod('_ someFunctionName',array(param1,param2,param3));

只需按照出现在原始专用函数中的顺序发出参数即可

对于任何想要运行私人方法的人来说,没有什么大杂烩。 这适用于任何单元测试框架,只使用旧的Reflection。

 public class ReflectionTools { // If the class is non-static public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args) { Type t = objectUnderTest.GetType(); return t.InvokeMember(method, BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, null, objectUnderTest, args); } // if the class is static public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args) { MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static); foreach(var member in members) { if (member.Name == method) { return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args); } } return null; } } 

Then in your actual tests, you can do something like this:

 Assert.AreEqual( ReflectionTools.InvokePrivate( typeof(StaticClassOfMethod), "PrivateMethod"), "Expected Result"); Assert.AreEqual( ReflectionTools.InvokePrivate( new ClassOfMethod(), "PrivateMethod"), "Expected Result"); 
 CC -Dprivate=public 

Here is good article about unit testing of private methods. But I'm not sure what's better, to make you application designed specially for testing(it's like creating tests for testing only) or use reflexion for testing. Pretty sure most of us will choose second way.

You could generate the test method for the private method from Visual studio 2008. When you create a unit test for a private method, a Test References folder is added to your test project and an accessor is added to that folder. The accessor is also referred to in the logic of the unit test method. This accessor allows your unit test to call private methods in the code that you are testing. For details have a look at

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

Also note that the InternalsVisibleToAtrribute has a requirement that your assembly be strong named , which creates it's own set of problems if you're working in a solution that had not had that requirement before. I use the accessor to test private methods. See this question that for an example of this.

Here's an example, first the method signature:

 private string[] SplitInternal() { return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+") .Cast<Match>() .Select(m => m.Value) .Where(s => !string.IsNullOrEmpty(s)) .ToArray(); } 

Here's the test:

 /// <summary> ///A test for SplitInternal ///</summary> [TestMethod()] [DeploymentItem("Git XmlLib vs2008.dll")] public void SplitInternalTest() { string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date"; object[] values = new object[] { 2, "Martin" }; XPathString xp = new XPathString(path, values); PrivateObject param0 = new PrivateObject(xp); XPathString_Accessor target = new XPathString_Accessor(param0); string[] expected = new string[] { "pair[path/to/@Key={0}]", "Items", "Item[Name={1}]", "Date" }; string[] actual; actual = target.SplitInternal(); CollectionAssert.AreEqual(expected, actual); } 

A way to do this is to have your method protected and write a test fixture which inherits your class to be tested. This way, you are nor turning your method public , but you enable the testing.

1) If you have a legacy code then the only way to test private methods is by reflection.

2) If it is new code then you have the following options:

  • Use reflection (to complicated)
  • Write unit test in the same class (makes the production code ugly by having test code also in it)
  • Refactor and make the method public in some kind of util class
  • Use @VisibleForTesting annotation and remove private

I prefer the annotation method, simplest and least complicated. The only issue is that we have increased the visibility which I think is not a big concern. We should always be coding to interface, so if we have an interface MyService and an implementation MyServiceImpl then we can have the corresponding test classes that is MyServiceTest (test interface methods) and MyServiceImplTest (test private methods). All clients should anyway be using the interface so in a way even though the visibility of the private method has been increased it should not really matter.

You could also declare it as public or internal (with InternalsVisibleToAttribute) while building in debug-Mode:

  /// <summary> /// This Method is private. /// </summary> #if DEBUG public #else private #endif static string MyPrivateMethod() { return "false"; } 

It bloats the code, but it will be private in a release build.

In my opinion you should only unit test your classe's public API.

Making a method public, in order to unit test it, breaks encapsulation exposing implementation details.

A good public API solves an immediate goal of the client code and solves that goal completely.