Visual Studio允许通过自动生成的访问器类对私有方法进行单元测试。我编写了一个成功编译的私有方法的测试,但它在运行时失败了。一个相当小的代码和测试版本是:
//in project MyProj
class TypeA
{
private List<TypeB> myList = new List<TypeB>();
private class TypeB
{
public TypeB()
{
}
}
public TypeA()
{
}
private void MyFunc()
{
//processing of myList that changes state of instance
}
}
//in project TestMyProj
public void MyFuncTest()
{
TypeA_Accessor target = new TypeA_Accessor();
//following line is the one that throws exception
target.myList.Add(new TypeA_Accessor.TypeB());
target.MyFunc();
//check changed state of target
}
运行时错误是:
Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.
根据intellisense - 因此我猜编译器 - 目标是TypeA_Accessor类型。但在运行时它的类型为TypeA,因此列表添加失败。
有什么办法可以阻止这个错误吗?或者,或许更有可能的是,其他人有什么其他建议(我预测可能“不测试私有方法”和“没有单元测试操纵对象的状态”)。
答案 0 :(得分:552)
您可以使用PrivateObject Class
Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);
答案 1 :(得分:248)
是的,不要测试私有方法....单元测试的想法是通过公共'API'测试单元。
如果您发现需要测试很多私人行为,很可能您在要测试的类中隐藏了一个新的“类”,将其解压缩并通过其公共接口进行测试。
一条建议/思考工具.....有一种观点认为任何方法都不应该是私密的。这意味着所有方法都应该存在于对象的公共接口上....如果您觉得需要将其设置为私有,则很可能存在于另一个对象上。
这条建议在实践中并没有完全奏效,但它的建议大多是好的,而且往往会促使人们将对象分解成更小的对象。
答案 2 :(得分:78)
同样适用于此讨论。
这一切都取决于你认为是一个单位,如果你认为UNIT是一个类,那么你只会点击公共方法。如果你认为UNIT是代码行,打私人方法不会让你感到内疚。
如果要调用私有方法,可以使用“PrivateObject”类并调用invoke方法。你可以观看这个暗淡的youtube视频(http://www.youtube.com/watch?v=Vq6Gcs9LrPQ),它展示了如何使用“PrivateObject”,还讨论了私有方法的测试是否合乎逻辑。
答案 3 :(得分:37)
这里的另一个想法是将测试扩展到“内部”类/方法,给出更多白盒感测的测试。您可以在程序集上使用InternalsVisibleToAttribute将它们公开给单独的单元测试模块。
结合密封类,您可以接近这样的封装,即测试方法只能从您的方法的unittest汇编中看到。考虑密封类中的受保护方法事实上是私有的。
[assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
#pragma warning disable CS0628 //invalid because of InternalsVisibleTo
public sealed class MyWatch
{
Func<DateTime> _getNow = delegate () { return DateTime.Now; };
//construktor for testing purposes where you "can change DateTime.Now"
internal protected MyWatch(Func<DateTime> getNow)
{
_getNow = getNow;
}
public MyWatch()
{
}
}
}
单元测试:
namespace MyCode.UnitTests
{
[TestMethod]
public void TestminuteChanged()
{
//watch for traviling in time
DateTime baseTime = DateTime.Now;
DateTime nowforTesting = baseTime;
Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };
MyWatch myWatch= new MyWatch(_getNowForTesting );
nowforTesting = baseTime.AddMinute(1); //skip minute
//TODO check myWatch
}
[TestMethod]
public void TestStabilityOnFebruary29()
{
Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
MyWatch myWatch= new MyWatch(_getNowForTesting );
//component does not crash in overlap year
}
}
答案 4 :(得分:20)
测试私有方法的一种方法是通过反射。这也适用于NUnit和XUnit:
include()
答案 5 :(得分:6)
您可以围绕包含要测试的私有方法的类创建包装类。 这个包装类包含一个名为Call_MyPrivateFunction的公共方法,该方法又调用其基类的私有函数。
请注意,方法的访问级别应更改为[protected]
代码示例:
public class Order
{
public int Quantity { get; set; }
protected bool OrderIsBig ()
{
//This is the method we want to test. It needs to be protected in stead of private. Else... no cigar
return Quantity > 100;
}
}
//Use this wrapper class in your unit test.
public class FakeOrder : Order
{
public bool Call_OrderIsBig()
{
//This makes the actual call to the protected method "OrderIsBig"
return OrderIsBig();
}
}
单元测试代码可能如下所示:
FakeOrder order = new FakeOrder();
order.Quantity = 200;
bool isBig = order.Call_OrderIsBig(); //Make a call to a public method of the FakeOrder class which in turn makes a call to the protected method.
答案 6 :(得分:5)
Ermh ...出现了完全相同的问题:测试一个简单但却是关键的 private 方法。读完该线程后,看起来就像“我想在这种简单的金属上钻出这个简单的孔,然后确保质量符合规格”,然后出现“好吧,这不是首先,没有合适的工具可以这样做,但是您可以在花园中建造一个重力波观测台,请阅读http://foobar.brigther-than-einstein.org/上的文章首先,当然,您必须参加一些高级研究物理课程,那么您需要大量的超冷氮,然后,当然,我的书就可以在亚马逊上找到”。
换句话说...
不,首先是
每种方法,无论是私有的,内部的,受保护的,公开的,都可以进行测试。必须有一种方法来实现这种测试,而无需如本文所述。
为什么?正是因为到目前为止,一些贡献者所做的体系结构提及。简单重申一下软件原理可能会消除一些误会。
在这种情况下,通常的嫌疑人是:OCP,SRP和一如既往的KIS。
但是请稍等。使所有内容公开可用的想法更多是出于政治目的,而不是一种态度。但。关于代码,即使在当时的开源社区中,这也不是教条。相反,“隐藏”某些东西是使您更容易熟悉某个API的好习惯。例如,您将隐藏新上市的数字温度计构建模块的核心计算,而不是将数学运算隐藏在真实测得的曲线后面,以免引起好奇的代码阅读者,但要防止您的代码变得依赖某些代码,也许突然之间,重要的用户无法抗拒使用您以前私有的,内部受保护的代码来实现自己的想法。
我在说什么?
private double TranslateMeasurementIntoLinear(double actualMeasurement);
很容易宣布水瓶座时代或当今所谓的水瓶座时代,但是如果我的传感器从1.0升级到2.0,那么Translate ...的实现可能会从简单的线性方程式改变,该线性方程式易于理解并且对于每个人来说都是“可重用的”,可以使用分析或其他方式进行相当复杂的计算,因此我会破坏他人的代码。为什么?因为他们不了解软件编码的基本原理,甚至都不了解KIS。
简而言之,我们需要一种简单的方法来测试私有方法,而无需费力。
第一:大家新年快乐!
第二:演练您的建筑师课程。
第三:“公共”修饰语是宗教,而不是解决方案。
答案 7 :(得分:2)
TL; DR:将私有方法解压缩到另一个类,对该类进行测试;阅读更多关于SRP原则(单一责任原则)
您似乎需要将private
方法提取到另一个类;在这应该是public
。您应该测试另一个类的private
方法,而不是尝试测试public
方法。
我们有以下情况:
Class A
+ outputFile: Stream
- _someLogic(arg1, arg2)
我们需要测试_someLogic
的逻辑;但似乎Class A
承担的角色超出了它的需要(违反了SRP原则);只需重构为两个类
Class A1
+ A1(logicHandler: A2) # take A2 for handle logic
+ outputFile: Stream
Class A2
+ someLogic(arg1, arg2)
这样someLogic
可以在A2上测试;在A1中创建一些假的A2然后注入构造函数来测试A2被调用到名为someLogic
的函数。
答案 8 :(得分:0)
另一个未提及的选项是将单元测试类创建为要测试的对象的子代。 NUnit示例:
<canvas id="canvas" width=48 height=48 style="background: #383838">
这将允许轻松测试私有和受保护(但不是继承的私有)方法,并且可以将所有测试与真实代码分开,从而避免将测试程序集部署到生产环境中。在许多继承的对象中,将您的私有方法切换到受保护的方法是可以接受的,并且这是非常简单的更改。
但是...
尽管这是解决如何测试隐藏方法的有趣方法,但是我不确定我会主张在所有情况下这都是解决问题的正确方法。在内部测试对象似乎有些奇怪,我怀疑在某些情况下这种方法会对您造成打击。 (例如,不变的对象可能会使某些测试变得非常困难)。
虽然我提到了这种方法,但我认为这更多是集思广益的建议,而不是合法的解决方案。撒一粒盐。
答案 9 :(得分:0)
摘自这本书有效地使用旧版代码:
“如果需要测试私有方法,则应将其公开。如果 在大多数情况下,将其公之于众使我们感到困扰。 做得太多,我们应该修复它。”
作者认为,解决此问题的方法是创建一个新类,并将方法添加为my_dict = {'motorway': {'left': [100, 120, 130, 600, 800]}, 'trunk': {'left': [200, 240, 290, 345, 980]}, 'primary': {'left': [320, 340, 330]}}
。
作者进一步解释:
“好的设计是可以测试的,而不能测试的设计是不好的。”
因此,在这些限制内,您唯一的实际选择是在当前类或新类中使用方法public
。
答案 10 :(得分:0)
我使用这个助手(对象类型扩展)
public static TReturn CallPrivateMethod<TReturn>(
this object instance,
string methodName,
params object[] parameters)
{
Type type = instance.GetType();
BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Instance;
MethodInfo method = type.GetMethod(methodName, bindingAttr);
return (TReturn)method.Invoke(instance, parameters);
}
你可以这样称呼它
Calculator systemUnderTest = new Calculator();
int result = systemUnderTest.CallPrivateMethod<int>("PrivateAdd",1,8);
优点之一是它使用泛型来预先确定返回类型。
答案 11 :(得分:-3)
在VS 2005/2008中,您可以使用private accessor来测试私有成员,但这种方式在VS的后续版本中消失