我正在建立一个具有一些公共和私有方法的类库。我希望能够对私有方法进行单元测试(主要是在开发过程中,但它对将来的重构很有用)。
正确的方法是什么?
我正在建立一个具有一些公共和私有方法的类库。我希望能够对私有方法进行单元测试(主要是在开发过程中,但它对将来的重构很有用)。
正确的方法是什么?
Answers:
如果使用的是.net,则应使用InternalsVisibleToAttribute。
#if DEBUG
该InternalsVisibleTo
属性使它不适用于发布代码吗?
#if RELEASE_TEST
周围InternalsVisibleTo
像迈克建议,并让你的发布版本配置的副本定义RELEASE_TEST
。您可以通过优化来测试发布代码,但是当您实际构建发布时,将省略测试。
如果要对专用方法进行单元测试,则可能出了一些问题。单元测试(通常而言)旨在测试类的接口,即其公共(和受保护)方法。您当然可以为此“破解”一个解决方案(即使只是通过公开方法),但您可能还需要考虑:
测试私有方法可能没有用。但是,有时我也喜欢从测试方法中调用私有方法。大多数时候为了防止代码重复以生成测试数据...
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[] {/* ... */} );
我不同意“您应该只对测试外部接口感兴趣”的理念。这有点像说汽车维修店应该只进行测试,以检查车轮是否转动。是的,最终我对外部行为感兴趣,但是我更喜欢自己的,私有的内部测试,更具体一点。是的,如果我进行重构,则可能必须更改一些测试,但是除非进行大规模重构,否则我只需要更改一些测试,而其他(未更改)内部测试仍然有效的事实就说明了这一点。重构已经成功。
您可以尝试仅使用公共接口来涵盖所有内部案例,并且理论上可以通过使用公共接口来全面测试每种内部方法(或至少重要的每种方法),但是您可能最终不得不站起来以实现这以及通过公共接口运行的测试用例与旨在测试的解决方案的内部之间的联系可能很难辨认或无法辨别。指出了,确保内部机器正常运行的单个测试非常值得重构带来的微小测试更改-至少这是我的经验。如果您必须在每次重构时都对测试进行重大更改,那么也许这没有意义,但是在那种情况下,也许您应该完全重新考虑设计。
FooService
必须要做的事情X
,那么您只需要关心的是它确实会X
在需要时执行。它怎么做并不重要。如果类中的问题无法通过接口辨别(不太可能),则该问题仍然有效FooService
。如果是通过界面可见的问题,则对公共成员的测试应该可以检测到它。关键在于,只要车轮正确旋转,就可以用作车轮。
PrivMethod
,应该检查PubMethod
哪个调用PrivMethod
可以暴露它?更改SimpleSmtpService
为a后会GmailService
怎样?突然之间,您的私人测试指向的是不再存在的代码,或者可能以不同的方式工作并且可能会失败,即使该应用程序可以按设计完美运行。如果对两个电子邮件发件人都需要进行复杂的处理,那么也许应该在一个EmailProcessor
可以供两个人使用并分别进行测试的文件中进行处理?
在极少数情况下,我想测试私有函数,我通常将它们修改为受保护,并且我编写了带有公共包装函数的子类。
班级:
...
protected void APrivateFunction()
{
...
}
...
测试子类:
...
[Test]
public void TestAPrivateFunction()
{
APrivateFunction();
//or whatever testing code you want here
}
...
我认为应该提出一个更基本的问题,即为什么首先要尝试测试私有方法。这是一种代码味道,您正在尝试通过该类的公共接口测试私有方法,而该方法是私有的,原因是它是实现细节。一个人只应该关注公共接口的行为,而不是它在幕后的实现方式。
如果我想通过使用通用重构来测试私有方法的行为,则可以将其代码提取到另一个类中(可能具有程序包级别的可见性,因此请确保它不属于公共API)。然后,我可以单独测试其行为。
重构的结果意味着私有方法现在是一个单独的类,已经成为原始类的协作者。通过自己的单元测试,它的行为将变得众所周知。
然后,当我尝试测试原始类时,我可以模拟其行为,以便可以专注于测试该类的公共接口的行为,而不必测试公共接口及其所有私有方法的行为的组合爆炸式增长。
我看到这类似于开车。当我开车时,我不会在不装上引擎盖的情况下开车,所以我可以看到发动机在运转。我依靠汽车提供的接口,即转速表和车速表来了解发动机是否在工作。我依靠的事实是,当我踩油门踏板时,汽车实际上会在行驶。如果要测试引擎,可以单独进行检查。:D
当然,如果您有旧版应用程序,那么直接测试私有方法可能是最后的选择,但我希望将旧版代码重构以实现更好的测试。迈克尔·费瑟斯(Michael Feathers)就这一主题写了一本很棒的书。http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052
私有类型,内部成员和私有成员之所以如此,是因为某些原因,并且通常您不想直接与它们混淆。而且,如果这样做,您很有可能会在以后休息,因为无法保证创建这些程序集的人会保留私有/内部实现。
但是,有时候,当进行一些hacked / explored或第三方程序集时,我自己最终想初始化一个私有类或一个带有私有或内部构造函数的类。或者,有时,在处理我无法更改的预编译遗留库时,我最终针对私有方法编写了一些测试。
因此诞生了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();
那么您可以通过两种方式对私有方法进行单元测试
您可以创建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 });
PrivateClass
first 的实例并使用它。stackoverflow.com/questions/9122708/…–
有两种类型的私有方法。静态私有方法和非静态私有方法(实例方法)。以下2篇文章通过示例说明如何对私有方法进行单元测试。
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;
}
在CodeProject上,有一篇文章简要讨论了测试私有方法的优缺点。然后,它提供了一些反射代码来访问私有方法(类似于Marcus上面提供的代码。)我在样本中发现的唯一问题是该代码未考虑重载的方法。
您可以在这里找到该文章:
声明它们internal
,然后使用InternalsVisibleToAttribute
允许您的单元测试程序集看到它们。
首先,您不应该测试代码的私有方法。您应该测试类的公共事物“公共接口”或API。API是您公开给外部调用者的所有公共方法。
原因是,一旦开始测试类的私有方法和内部,您就将类的实现(私有事物)耦合到测试中。这意味着,当您决定更改实现细节时,还必须更改测试。
因此,您应该避免使用InternalsVisibleToAtrribute。
这是Ian Cooper的精彩演讲,涵盖了这个主题:Ian Cooper:TDD,这哪里出错了?
有时,测试私有声明可能会很好。从根本上说,编译器只有一个公共方法:Compile(string outputFileName,params string [] sourceSFileNames)。我相信您知道如果不测试每个“隐藏”声明,将很难测试这种方法!
这就是为什么我们创建Visual T#:以便进行更轻松的测试的原因。它是一种免费的.NET编程语言(与C#v2.0兼容)。
我们添加了“ .-”运算符。它的行为就像“。” 运算符,除了您还可以访问测试中的任何隐藏声明而无需更改测试项目中的任何内容。
看看我们的网站:下载它的免费。
我很惊讶没有人说过这个,但是我采用的解决方案是在类内部创建一个静态方法来进行自我测试。这使您可以访问所有公共和私有内容以进行测试。
此外,使用脚本语言(具有OO功能,如Python,Ruby和PHP),您可以在运行文件时对其进行测试。不错的快速方法,可确保您所做的更改不会破坏任何内容。显然,这为测试所有类提供了可扩展的解决方案:只需全部运行它们。(您也可以在其他语言中使用void main来执行此操作,该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));
只需按参数在原始私有函数中出现的顺序发出
对于任何想要运行私有方法而又不费吹灰之力的人。这适用于任何单元测试框架,仅使用良好的旧反射功能。
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");
MbUnit为此提供了一个很好的包装,称为Reflector。
Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);
您还可以从属性设置和获取值
dogReflector.GetProperty("Age");
关于“测试私密”,我同意在完美世界中。进行私有单元测试毫无意义。但是在现实世界中,您可能最终想要编写私有测试而不是重构代码。
Reflector
已被Mirror
Gallio / MbUnit v3.2中功能更强大的功能所取代。(gallio.org/wiki/doku.php?id=mbunit:mirror)
我使用PrivateObject类。但是如前所述,最好避免测试私有方法。
Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);
这是一个示例,首先是方法签名:
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);
}
1)如果您有旧版代码,则测试私有方法的唯一方法是通过反射。
2)如果是新代码,则可以选择以下选项:
我更喜欢最简单和最不复杂的注释方法。唯一的问题是我们提高了知名度,我认为这不是一个大问题。我们应该始终对接口进行编码,因此,如果我们有一个接口MyService和一个实现MyServiceImpl,则可以有相应的测试类,分别是MyServiceTest(测试接口方法)和MyServiceImplTest(测试私有方法)。无论如何,所有客户端都应该使用该接口,因此,即使私有方法的可见性有所提高,它也并不重要。
您可以从Visual Studio 2008中为私有方法生成测试方法。为私有方法创建单元测试时,会将Test References文件夹添加到测试项目中,并将访问器添加到该文件夹中。访问器在单元测试方法的逻辑中也被称为。该访问器允许您的单元测试在要测试的代码中调用私有方法。详情请看
pre-historic
就Internet年而言,但是对私有方法的单元测试现在既简单又直接,Visual Studio在需要时生成必要的访问器类,片段预填充了测试逻辑,这简直太接近简单功能测试可能需要的片段了。参见例如。msdn.microsoft.com/zh-CN/library/ms184807%28VS.90%29.aspx