我正在建立一个有一些公共场所的班级图书馆。私人方法。我希望能够对私有方法进行单元测试(主要是在开发过程中,但也可能对将来的重构有用)。
这样做的正确方法是什么?
答案 0 :(得分:340)
如果要对私有方法进行单元测试,可能会出现问题。单元测试(一般来说)用于测试类的接口,即其公共(和受保护)方法。你当然可以“破解”这个解决方案(即使只是通过公开方法),但你可能也想考虑:
答案 1 :(得分:118)
如果您使用的是.net,则应使用InternalsVisibleToAttribute。
答案 2 :(得分:115)
测试私有方法可能没用。但是,我有时也喜欢从测试方法中调用私有方法。大多数情况下,为了防止代码重复生成测试数据...
Microsoft为此提供了两种机制:
<强>访问者强>
然而,当涉及原始类的接口的更改时,该机制有时有点棘手。所以,大多数时候我都避免使用它。
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[] {/* ... */} );
答案 3 :(得分:76)
我不同意“你应该只对测试外部接口感兴趣”的理念。这有点像说汽车维修店应该只进行测试,看车轮是否转动。是的,最终我对外部行为感兴趣,但我喜欢我自己的私人内部测试更具体和更重要。是的,如果我重构,我可能不得不改变一些测试,但除非它是一个巨大的重构,我只需要改变一些,而其他(未改变的)内部测试仍然有效的事实是一个很好的指标,重构已经成功。
您可以尝试仅使用公共界面覆盖所有内部案例,理论上可以通过使用公共界面完全测试每个内部方法(或至少每个重要的方法),但您可能必须站在您的要实现这一点,并且通过公共接口运行的测试用例与他们设计用于测试的解决方案的内部部分之间的联系可能很难或无法辨别。有针对性的,确保内部机器正常工作的个别测试非常值得重构的小测试变化 - 至少这是我的经验。如果你必须对每次重构的测试做出巨大的改变,那么也许这没有意义,但在这种情况下,也许你应该完全重新考虑你的设计。一个好的设计应该足够灵活,以允许大多数更改,而无需大量重新设计。
答案 4 :(得分:50)
在极少数情况下,我想测试私有函数,我通常会将它们修改为受保护,而我已经编写了一个带有公共包装函数的子类。
班级:
...
protected void APrivateFunction()
{
...
}
...
测试的子类:
...
[Test]
public void TestAPrivateFunction()
{
APrivateFunction();
//or whatever testing code you want here
}
...
答案 5 :(得分:22)
我认为应该问一个更基本的问题是你为什么要首先尝试测试私有方法。这是一个代码气味,你试图通过该类的公共接口测试私有方法,而该方法是私有的,因为它是一个实现细节。人们应该只关注公共接口的行为,而不是关于它如何在封面下实施。
如果我想测试私有方法的行为,通过使用常见的重构,我可以将其代码提取到另一个类(可能具有包级别可见性,因此确保它不是公共API的一部分)。然后我可以孤立地测试它的行为。
重构的产品意味着私有方法现在是一个单独的类,它已成为原始类的协作者。通过自己的单元测试,它的行为将得到很好的理解。
当我尝试测试原始类时,我可以模拟它的行为,这样我就可以专注于测试该类的公共接口的行为,而不必测试公共接口的组合爆炸和所有行为它的私人方法。
我认为这类似于驾驶汽车。当我驾驶汽车时,我没有驾驶发动机罩,所以我可以看到发动机正在工作。我依靠汽车提供的接口,即转速计和速度表来了解发动机是否工作。当我按下油门踏板时,我依靠汽车实际移动的事实。如果我想测试引擎,我可以单独检查它。 :d
当然,如果您有遗留应用程序,直接测试私有方法可能是最后的手段,但我希望重构遗留代码以实现更好的测试。 Michael Feathers写了一本关于这个主题的好书。 http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052
答案 6 :(得分:17)
私人类型,内部和私人成员是由于某种原因,并且通常你不想直接搞砸他们。如果你这样做,很可能会在以后破解,因为无法保证创建这些程序集的人将保留私有/内部实现。
但是,有时,在对编译或第三方程序集进行一些黑客攻击/探索时,我最终想要使用私有或内部构造函数初始化私有类或类。或者,有时,在处理我无法更改的预编译遗留库时 - 我最终会针对私有方法编写一些测试。
因此诞生了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();
答案 7 :(得分:12)
您可以通过两种方式对私有方法进行单元测试
您可以创建PrivateObject
类的实例,语法如下
PrivateObject obj= new PrivateObject(PrivateClass);
//now with this obj you can call the private method of PrivateCalss.
obj.PrivateMethod("Parameters");
您可以使用反射。
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 });
答案 8 :(得分:10)
我还使用了InternalsVisibleToAttribute方法。值得一提的是,如果你为了达到这个目的而内部使用你以前的私人方法感到不舒服,那么也许他们不应该成为直接单元测试的主题。
毕竟,你正在测试你的类的行为,而不是它的特定实现 - 你可以改变后者而不改变前者,你的测试仍然应该通过。
答案 9 :(得分:9)
有两种类型的私有方法。静态私有方法和非静态私有方法(实例方法)。以下2篇文章解释了如何使用示例对私有方法进行单元测试。
答案 10 :(得分:8)
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;
}
答案 11 :(得分:5)
在CodeProject上,有一篇文章简要讨论了测试私有方法的优缺点。然后它提供了一些反映代码来访问私有方法(类似于Marcus上面提供的代码。)我在样本中发现的唯一问题是代码没有考虑重载方法。
你可以在这里找到这篇文章:
答案 12 :(得分:4)
我倾向于不使用编译器指令,因为它们很快就会混乱。如果你真的需要它们的一种缓解方法是将它们放在一个部分类中,让你的构建在制作生产版本时忽略该.cs文件。
答案 13 :(得分:4)
您不应该首先测试代码的私有方法。您应该测试公共界面&#39;或API,您班级的公共事物。 API是您向外部呼叫者公开的所有公共方法。
原因是,一旦开始测试类的私有方法和内部,就会将类的实现(私有事物)与测试结合起来。这意味着当您决定更改实施细节时,您还必须更改测试。
因此,您应该避免使用InternalsVisibleToAtrribute。
以下是Ian Cooper的精彩演讲,内容涵盖了这一主题:Ian Cooper: TDD, where did it all go wrong
答案 14 :(得分:4)
声明他们internal
,然后使用InternalsVisibleToAttribute
允许您的单元测试程序集查看它们。
答案 15 :(得分:3)
我很惊讶没有人说过这个,但我采用的解决方案是在类中创建一个静态方法来测试自己。这使您可以访问公共和私人测试的所有内容。
此外,在脚本语言(具有OO功能,如Python,Ruby和PHP)中,您可以在运行时使文件自行测试。确保您的更改不会破坏任何内容的快捷方式。这显然是一个可扩展的解决方案来测试你的所有类:只需运行它们。 (您也可以使用其他语言执行此操作,其中void main也始终运行其测试。)
答案 16 :(得分:3)
对于任何想要运行私有方法的人而言,没有所有的烦恼和混乱。这适用于任何单元测试框架,只使用旧的反射。
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;
}
}
然后在您的实际测试中,您可以执行以下操作:
Assert.AreEqual(
ReflectionTools.InvokePrivate(
typeof(StaticClassOfMethod),
"PrivateMethod"),
"Expected Result");
Assert.AreEqual(
ReflectionTools.InvokePrivate(
new ClassOfMethod(),
"PrivateMethod"),
"Expected Result");
答案 17 :(得分:3)
我想在这里创建一个清晰的代码示例,您可以在任何要测试私有方法的类上使用它。
在您的测试用例类中,只需包含这些方法,然后按指示使用它们。
/**
*
* @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-&gt; _callMethod('_ someFunctionName',array(param1,param2,param3));
只需按照它们在原始私有函数中出现的顺序发出参数
答案 18 :(得分:3)
有时候,测试私有声明会很好。 从根本上说,编译器只有一个公共方法:Compile(string outputFileName,params string [] sourceSFileNames)。我相信你明白,如果不测试每个“隐藏”的声明,就很难测试这样的方法!
这就是为什么我们创建了Visual T#:以便更轻松地进行测试。它是一种免费的.NET编程语言(兼容C#v2.0)。
我们添加了'.-'运算符。它只是表现得像'。'运算符,除了您还可以从测试中访问任何隐藏的声明而不更改测试项目中的任何内容。
请查看我们的网站:download 免费。
答案 19 :(得分:2)
我使用PrivateObject课程。但如前所述,最好避免测试私有方法。
<?php
include 'main.cpp';
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
echo "Check string";
$obj = new item();
echo "string";
$obj->DBconnect();
echo "string";
?>
答案 20 :(得分:2)
关于私有方法的单元测试,这是很好的article。但是我不确定什么更好,让你的应用程序专门用于测试(它就像创建测试只用于测试)或使用反射进行测试。 我们大多数人都会选择第二种方式。
答案 21 :(得分:2)
MbUnit为这个名为Reflector的包装器提供了一个很好的包装器。
Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);
您还可以设置属性
并获取值dogReflector.GetProperty("Age");
关于“测试私人”我同意......在完美的世界里。进行私人单元测试是没有意义的。但在现实世界中,您最终可能希望编写私有测试而不是重构代码。
答案 22 :(得分:2)
CC -Dprivate=public
“CC”是我使用的系统上的命令行编译器。 -Dfoo=bar
相当于#define foo bar
。因此,此编译选项有效地将所有私有内容更改为公共。
答案 23 :(得分:1)
这是一个例子,首先是方法签名:
private string[] SplitInternal()
{
return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
.Cast<Match>()
.Select(m => m.Value)
.Where(s => !string.IsNullOrEmpty(s))
.ToArray();
}
以下是测试:
/// <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);
}
答案 24 :(得分:1)
执行此操作的方法是使用您的方法protected
并编写一个继承您要测试的类的测试夹具。这样,您也不会转换方法public
,但启用了测试。
答案 25 :(得分:1)
1)如果您有遗留代码,那么测试私有方法的唯一方法是通过反射。
2)如果是新代码,那么您有以下选项:
我更喜欢注释方法,最简单,最简单。唯一的问题是我们提高了知名度,我认为这不是一个大问题。 我们应该始终对接口进行编码,因此如果我们有一个接口MyService和一个实现MyServiceImpl,那么我们就可以拥有相应的测试类,即MyServiceTest(测试接口方法)和MyServiceImplTest(测试私有方法)。无论如何,所有客户端都应该使用接口,所以即使私有方法的可见性已经增加,它也应该无关紧要。
答案 26 :(得分:1)
在调试模式下构建时,您也可以将其声明为public或internal(使用InternalsVisibleToAttribute):
/// <summary>
/// This Method is private.
/// </summary>
#if DEBUG
public
#else
private
#endif
static string MyPrivateMethod()
{
return "false";
}
它使代码膨胀,但在发布版本中它将是private
。
答案 27 :(得分:1)
在我看来,你应该只测试你的classe的公共API。
将方法公之于众,为了对其进行单元测试,打破了封装暴露实现细节。
良好的公共API解决了客户端代码的直接目标,并完全解决了这一目标。
答案 28 :(得分:0)
适用于JAVA语言
在这里,您可以使用模拟行为覆盖测试类的特定方法。
以下代码:
public class ClassToTest
{
public void methodToTest()
{
Integer integerInstance = new Integer(0);
boolean returnValue= methodToMock(integerInstance);
if(returnValue)
{
System.out.println("methodToMock returned true");
}
else
{
System.out.println("methodToMock returned true");
}
System.out.println();
}
private boolean methodToMock(int value)
{
return true;
}
}
测试课将是:
public class ClassToTestTest{
@Test
public void testMethodToTest(){
new Mockup<ClassToTest>(){
@Mock
private boolean methodToMock(int value){
return true;
}
};
....
}
}
答案 29 :(得分:0)
另请注意,InternalsVisibleToAtrribute要求您的程序集为strong named,如果您正在使用之前没有该要求的解决方案,则会产生一系列问题。我使用访问器来测试私有方法。有关此示例,请参阅this question。
答案 30 :(得分:0)
您可以从Visual Studio 2008生成私有方法的测试方法。当您为私有方法创建单元测试时,Test References文件夹将添加到您的测试项目中,并且会将访问者添加到该文件夹中。访问器也在单元测试方法的逻辑中被引用。此访问器允许您的单元测试在您正在测试的代码中调用私有方法。 有关详细信息,请查看