C# 非public的方法和属性的单元测试
有时候我们写好的类库中,某些类的属性和方法不应该暴露出来,那么如何对这些非public的方法和属性进行单元测试?
MS为我们提供了PrivateObject类,可以解决这个问题,可以去MSDN的说明文档中查看这个类的介绍。本文也试举一例,说明其用法。
首先给出被测试类,为了比较全面,我们在其中包含了public属性、protected属性、private属性等等,如下
/// <summary>
/// Implementation of a class with non-public members.
/// </summary>
public class Testee
{
#region Public Properties /// <summary>
/// Gets / sets the value of a public property.
/// </summary>
public string PublicProperty { get; set; } #endregion Public Properties #region Protected Properties /// <summary>
/// Gets / sets the value of a protected property.
/// </summary>
protected string ProtectedProperty { get; set; } #endregion Protected Properties #region Private Properties /// <summary>
/// Gets / sets the value of a private property.
/// </summary>
private string PrivateProperty { get; set; } #endregion Private Properties #region Private Static Properties /// <summary>
/// Gets / sets the value of a private static property.
/// </summary>
private static string PrivateStaticProperty { get; set; } #endregion Private Static Properties #region Protected Methods /// <summary>
/// A simple protected method with a parameter.
/// </summary>
/// <param name="parameter">The parameter</param>
/// <returns>Another string.</returns>
protected string ProtectedMethod(string parameter)
{
Console.WriteLine("Parameter: >{0}<", parameter); return (parameter + " processed");
} #endregion Protected Methods
}
然后,我们需要一个类,命名为PrivateAccessor,这个类提供访问非public属性和非public方法的方式。PrivateAccessor类中包含一个PrivateObject类对象,其实本质就是通过这个PrivateObject对象实现访问非public属性以及非public方法,如下所示代码
class PrivateAccessor
{
// other members
private PrivateObject PrivateObject { get; set; }
}
因为我们要访问被测试类Testee的非public属性和非public方法,故需要传入此类的实例。另外,由于被测试类中含有静态的非public属性,在包装类中则需要一个对应的静态属性信息集合,用于保存被测试类Testee中所有的静态非public属性。
现在,代码变为如下
class PrivateAccessor<T>
{
// other members
private PrivateObject PrivateObject { get; set; }
/// <summary>
/// Gets / sets the declared proporties for later use, in case static properties should be accessed.
/// </summary>
private static IEnumerable<PropertyInfo> DeclaredProperties { get; set; }
/// <summary>
/// Static initialization.
/// </summary>
static PrivateAccessor()
{
// Get the declared properties for later use, in case static properties should be accessed.
PrivateAccessor<T>.DeclaredProperties = typeof(T).GetProperties(BindingFlags.NonPublic | BindingFlags.Static);
} internal PrivateAccessor(T instance)
{
PrivateObject = new PrivateObject(instance);
}
}
到此,我们知道,PrivateAccessor类中,还缺少访问Testee类中非public方法和属性的辅助方法,不过先不急,先看看这个PrivateAccessor类,这是一个泛型类,泛型参数为T,并非我们这里所要测试的类Testee。
我们可以考虑再新增一个类,继承这个PrivateAccessor<T>泛型类,并提供泛型参数Testee,另外再给出访问被测试类Testee中的各个非public方法和属性的入口,在这些入口中调用PrivateAccessor中的那些辅助方法,虽然这些方法还未实现。
代码如下
class MembersAccessWrapper : PrivateAccessor<Testee>
{
#region Interal Constructors
internal MembersAccessWrapper()
: base(new Testee())
{
}
#endregion Interal Constructors #region Internal Properties /// <summary>
/// Gives get / set access to the property "ProtecedProperty".
/// </summary>
internal string ProtectedProperty
{
[MethodImpl(MethodImplOptions.NoInlining)]
get { return (GetPropertyValue<string>()); }
[MethodImpl(MethodImplOptions.NoInlining)]
set { SetPropertyValue(value); }
} /// <summary>
/// Gives get / set access to the property "PrivateProperty".
/// </summary>
internal string PrivateProperty
{
[MethodImpl(MethodImplOptions.NoInlining)]
get { return (GetPropertyValue<string>()); }
[MethodImpl(MethodImplOptions.NoInlining)]
set { SetPropertyValue(value); }
} #endregion Internal Properties #region Internal Static Properties /// <summary>
/// Gives get / set access to the static property "PrivateStaticProperty".
/// </summary>
internal static string PrivateStaticProperty
{
[MethodImpl(MethodImplOptions.NoInlining)]
get { return (PrivateAccessor<Testee>.GetStaticPropertyValue<string>()); }
[MethodImpl(MethodImplOptions.NoInlining)]
set { PrivateAccessor<Testee>.SetStaticPropertyValue(value); }
} #endregion Internal Static Properties #region Internal Methods
/// <summary>
/// Calls the protected method ProtectedMethod
/// </summary>
/// <param name="parameter">The parameter</param>
/// <returns>The return value of the called method.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
internal string ProtectedMethod(string parameter)
{
// Use the base class to invoke the correct method.
return (Invoke<string>(parameter));
}
#endregion Internal Methods
}
现在,剩下的工作就是实现PrivateAccessor<T>类中的那些辅助方法,如下代码所示
class PrivateAccessor<T>
{
// other members
/// <summary>
/// Keeps the prefix of property getter method names.
/// </summary>
private const string GetterPrefix = "get_";
/// <summary>
/// Keeps the prefix of property setter method names.
/// </summary>
private const string SetterPrefix = "set_";
#region Protected Methods
/// <summary>
/// Returns the value of a property. The name of the property is taken from the call stack (for .NET version < 4.5).
/// </summary>
/// <typeparam name="T">Type of the return value.</typeparam>
/// <returns>The property value.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
protected TReturnValue GetPropertyValue<TReturnValue>()
{
// Need to remove the "get_" prefix from the caller's name
string propertyName = new StackFrame(, true).GetMethod().Name.Replace(PrivateAccessorBase<T>.GetterPrefix, String.Empty);
return ((TReturnValue)PrivateObject.GetProperty(propertyName, null));
}
/// <summary>
/// Sets the value of a property. The name of the property is taken from the call stack (for .NET version < 4.5).
/// </summary>
/// <param name="value">The value to be set.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
protected void SetPropertyValue
(
object value
)
{
// Need to remove the "set_" prefix from the caller's name
string propertyName = new StackFrame(, true).GetMethod().Name.Replace(PrivateAccessorBase<T>.SetterPrefix, String.Empty);
PrivateObject.SetProperty(propertyName, value, new object[] { });
}
/// <summary>
/// Invokes a method with parameter an return value.
/// </summary>
/// <typeparam name="TReturnValue">The type of the return value.</typeparam>
/// <param name="parameter">The parameter to be passed.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
protected TReturnValue Invoke<TReturnValue>
(
params object[] parameter
)
{
// Take the caller's name as method name
return ((TReturnValue)PrivateObject.Invoke(new StackFrame(, true).GetMethod().Name, parameter));
}
#endregion Protected Methods
#region Protected Static Methods
/// <summary>
/// Returns the value of a static property. The name of the property is taken from the call stack (for .NET version < 4.5).
/// </summary>
/// <typeparam name="T">Type of the return value.</typeparam>
/// <returns>The property value.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
protected static TReturnValue GetStaticPropertyValue<TReturnValue>()
{
// Need to remove the "get_" prefix from the caller's name
string propertyName = new StackFrame(, true).GetMethod().Name.Replace(PrivateAccessorBase<T>.GetterPrefix, String.Empty);
// Find the property having the matching name and get the value
return ((TReturnValue)PrivateAccessorBase<T>.DeclaredProperties.Single(info => info.Name.Equals(propertyName)).GetValue(null, null));
}
/// <summary>
/// Sets the value of a static property. The name of the property is taken from the call stack (for .NET version < 4.5).
/// </summary>
/// <param name="value">The value to be set.</param>
/// <returns>The property value.</returns>
[MethodImpl(MethodImplOptions.NoInlining)]
protected static void SetStaticPropertyValue(object value)
{
// Need to remove the "set_" prefix from the caller's name
string propertyName = new StackFrame(, true).GetMethod().Name.Replace(PrivateAccessorBase<T>.SetterPrefix, String.Empty);
// Find the property having the matching name and set the value
PrivateAccessorBase<T>.DeclaredProperties.Single(info => info.Name.Equals(propertyName)).SetValue(null, value, null);
}
#endregion Protected Static Methods
}
如此就实现了访问非public的方法和属性。
最后,给出调用代码
[TestClass()]
public class Tester
{
public TestContext TestContext {get; set;}
//You can use the following additional attributes as you write your tests:
//Use ClassInitialize to run code before running the first test in the class
[ClassInitialize()]
public static void MyClassInitialize(TestContext testContext)
{
} //Use ClassCleanup to run code after all tests in a class have run
[ClassCleanup()]
public static void MyClassCleanup()
{
} //Use TestInitialize to run code before running each test
[TestInitialize()]
public void MyTestInitialize()
{
} //Use TestCleanup to run code after each test has run
[TestCleanup()]
public void MyTestCleanup()
{
}
[TestMethod]
public void TestProtectedProperty()
{
var accessor = new MembersAccessWrapper();
accessor.ProtectedProperty = "Test String";
// Write it out for tracing ablilities
Debug.WriteLine("ProtectedProperty: >{0}<{1}", accessor.ProtectedProperty, Environment.NewLine); // Test the value (means call the getter)
Assert.AreEqual("Test String", accessor.ProtectedProperty, "Property has wrong value");
}
}
这段测试代码仅测试了protected属性,其他属性和方法的测试类似,不再重复写出。
.Net Framework4.5之后,对属性的动态访问作了一个简单的改变。此外上面的讨论未涉及到异步任务的非public访问,我们也一并考虑。首先被测试类增加非public的异步方法
class Testee
{
// other members /// <summary>
/// Gets / sets the value of a private property.
/// </summary>
private static string PrivateStaticProperty { get; set; } #region Private Methods /// <summary>
/// A private async method with two input parameters and a return value.
/// </summary>
/// <param name="millisecondsDelay">Delaytime in milliseconds..</param>
/// <param name="outputText">Output text.</param>
/// <returns>Delaytime in seconds.</returns>
private async Task<double> PrivateMethodWithReturnValueAsync(int millisecondsDelay, string outputText)
{
// First wait.
await Task.Delay(millisecondsDelay); // Write the output
Console.WriteLine(">{0}< milliseconds delayed. Output: >{1}<", millisecondsDelay, outputText); // Need to cast to make sure the all values will be handled as double, not as int.
return ((double)millisecondsDelay / (double));
} #endregion Private Methods #region Private Static Methods /// <summary>
/// A private static async method with two input parameters and no return value.
/// </summary>
/// <param name="millisecondsDelay">Delaytime in milliseconds..</param>
/// <param name="outputText">Output text.</param>
private static async Task PrivateStaticVoidMethodAsync(int millisecondsDelay, string outputText)
{
// First wait.
await Task.Delay(millisecondsDelay); // Do something that can be unit tested
PrivateStaticProperty = outputText; // Write the output
Console.WriteLine(">{0}< milliseconds delayed. Output: >{1}<", millisecondsDelay, outputText);
} #endregion Private Static Methods
}
PrivateAccessor<T>类中增加属性信息的集合和方法信息的集合,用于保存被测试类Testee中的所有属性信息和方法信息,由于可能存在静态属性和静态方法,故这两个集合在类的静态构造函数中初始化。
class PrivateAccessor<T>
{
// other members /// <summary>
/// Gets / sets the declared proporties for later use, in case static properties should be accessed.
/// </summary>
private static IEnumerable<PropertyInfo> DeclaredProperties { get; set; } /// <summary>
/// Gets / sets the declared methods for later use, in case static methods should be accessed.
/// </summary>
private static IEnumerable<MethodInfo> DeclaredMethods { get; set; } static PrivateAccessor()
{
TypeInfo typeInfo = typeof(T).GetTypeInfo(); // Get the declared properties for later use, in case static properties should be accessed.
PrivateAccessorBase<T>.DeclaredProperties = typeInfo.DeclaredProperties; // Get the declared methods for later use, in case static methods should be accessed.
PrivateAccessorBase<T>.DeclaredMethods = typeInfo.DeclaredMethods;
}
}
然后修改PrivateAccessor<T>类中那些辅助方法的实现
class PrivateAccessor<T>
{
// other members #region Protected Methods /// <summary>
/// Returns the value of a property. The name of the property is passed by using <see cref="CallerMemberName"/> (for .NET version >= 4.5).
/// </summary>
/// <typeparam name="TReturnValue">Type of the return value.</typeparam>
/// <param name="callerName">Name of the calling method.</param>
/// <returns>The property value.</returns>
protected TReturnValue GetPropertyValue<TReturnValue>([CallerMemberName] string callerName = null)
{
// Take the caller's name as property name
return ((TReturnValue)PrivateObject.GetProperty(callerName, null));
} /// <summary>
/// Sets the value of a property. The name of the property is passed by using <see cref="CallerMemberName"/> (for .NET version >= 4.5).
/// </summary>
/// <param name="value">The value to be set.</param>
/// <param name="callerName">Name of the calling method.</param>
protected void SetPropertyValue(object value, [CallerMemberName] string callerName = null)
{
// Take the caller's name as property name
PrivateObject.SetProperty(callerName, value, new object[] { });
} /// <summary>
/// Invokes a method with parameter an return value.
/// </summary>
/// <typeparam name="TReturnValue">The type of the result.</typeparam>
/// <param name="callerName">Name of the calling method.</param>
/// <param name="parameter">The parameter to be passed.</param>
protected TReturnValue Invoke<TReturnValue>([CallerMemberName] string callerName = null, params object[] parameter)
{
// Take the caller's name as method name
return ((TReturnValue)PrivateObject.Invoke(callerName, parameter));
} /// <summary>
/// Invokes a async method with parameters and return value.
/// </summary>
/// <typeparam name="TReturnValue">The type of the result.</typeparam>
/// <param name="callerName">Name of the calling method.</param>
/// <param name="parameter">The parameter to be passed.</param>
protected async Task<TReturnValue> InvokeAsync<TReturnValue>([CallerMemberName] string callerName = null, params object[] parameter)
{
// Take the caller's name as method name
TReturnValue returnValue = await (Task<TReturnValue>)PrivateObject.Invoke(callerName, parameter); // return the result
return (returnValue);
} #endregion Protected Methods #region Protected Static Methods /// <summary>
/// Returns the value of a static property. The name of the property is passed by using <see cref="CallerMemberName"/> (for .NET version >= 4.5).
/// </summary>
/// <typeparam name="TReturnValue">Type of the return value.</typeparam>
/// <param name="callerName">Name of the calling method.</param>
/// <returns>The property value.</returns>
protected static TReturnValue GetStaticPropertyValue<TReturnValue>([CallerMemberName] string callerName = null)
{
// Find the property having the matching name and get the value
return ((TReturnValue)PrivateAccessor<T>.DeclaredProperties.Single(info => info.Name.Equals(callerName)).GetValue(null));
} /// <summary>
/// Sets the value of a static property. The name of the property is passed by using <see cref="CallerMemberName"/> (for .NET version >= 4.5).
/// </summary>
/// <param name="value">The value to be set.</param>
/// <param name="callerName">Name of the calling method.</param>
protected static void SetStaticPropertyValue(object value, [CallerMemberName] string callerName = null)
{
// Find the property having the matching name and set the value
PrivateAccessor<T>.DeclaredProperties.Single(info => info.Name.Equals(callerName)).SetValue(null, value);
} /// <summary>
/// Invokes a static async method with parameters and return no value.
/// </summary>
/// <param name="callerName">Name of the calling method.</param>
/// <param name="parameter">The parameter to be passed.</param>
protected static async Task InvokeStaticAsync([CallerMemberName] string callerName = null, params object[] parameter)
{
// Take the caller's name as method name
await (Task) PrivateAccessor<T>.DeclaredMethods.Single(info => info.Name.Equals(callerName)).Invoke(null, parameter);
} #endregion Protected Static Methods
}
此时,提供访问非public方法和属性的接入点的包装类MembersAccessWrapper修改如下:
class MembersAccessWrapper : PrivateAccessor<Testee>
{
// other members #region Internal Properties /// <summary>
/// Gives get / set access to the property "ProtecedProperty".
/// </summary>
internal string ProtectedProperty
{
get { return (GetPropertyValue<string>()); }
set { SetPropertyValue(value); }
} /// <summary>
/// Gives get / set access to the property "PrivateProperty".
/// </summary>
internal string PrivateProperty
{
get { return (GetPropertyValue<string>()); }
set { SetPropertyValue(value); }
} #endregion Internal Properties #region Internal Static Properties /// <summary>
/// Gives get / set access to the static property "PrivateStaticProperty".
/// </summary>
internal static string PrivateStaticProperty
{
get { return (GetStaticPropertyValue<string>()); }
set { SetStaticPropertyValue(value); }
} #endregion Internal Static Properties #region Internal Methods /// <summary>
/// Calls the protected method ProtectedMethod
/// </summary>
/// <param name="parameter">The parameter</param>
/// <returns>The return value of the called method.</returns>
internal string ProtectedMethod(string parameter)
{
// Use the base class to invoke the correct method.
// Need to name the parameter, otherwise the first parameter will be interpreted as caller's name.
return (Invoke<string>(parameter: parameter));
} /// <summary>
/// Calls the private method PrivateMethodWithReturnValueAsync
/// </summary>
/// <param name="millisecondsDelay">Delaytime in milliseconds..</param>
/// <param name="outputText">Output text.</param>
/// <returns>Delaytime in seconds.</returns>
internal async Task<double> PrivateMethodWithReturnValueAsync(int millisecondsDelay, string outputText)
{
// Invoke the method
double returnValue = await InvokeAsync<double>(parameter: new object[] { millisecondsDelay, outputText }); // Return the result.
return (returnValue);
} #endregion Internal Methods #region Internal Static Methods /// <summary>
/// Calls the private method PrivateMethodWithReturnValueAsync
/// </summary>
/// <param name="millisecondsDelay">Delaytime in milliseconds..</param>
/// <param name="outputText">Output text.</param>
/// <returns>Delaytime in seconds.</returns>
internal static async Task PrivateStaticVoidMethodAsync(int millisecondsDelay, string outputText)
{
// Invoke the method
await InvokeStaticAsync(parameter: new object[] { millisecondsDelay, outputText });
} #endregion Internal Static Methods
}
测试代码略。
C# 非public的方法和属性的单元测试的更多相关文章
- TDD学习笔记【三】---是否需针对非public方法进行测试?
前言 在Visual Studio 2012 中,针对Unit Test 的部分,有一个重要的变动: 原本针对「测试对象非public 的部分」,开发人员可通过Visual Studio 2010 自 ...
- 非静态的字段、方法或属性“System.Web.UI.Page.ClientScript...”要求对象引用 (封装注册脚本)
在写项目时想对asp.net的注册前台脚本事件进行封装,就添加了一个BasePage.cs页面,但一直报错‘非静态的字段.方法或属性“System.Web.UI.Page.ClientScript.. ...
- C#变量初始化问题:字段初始值无法引用非静态字段、方法或属性
http://www.cnblogs.com/bluestorm/p/3432190.html 问题:字段初始值设定项无法引用非静态字段.方法或属性的问题 下面代码出错的原因,在类中定义的字段为什么不 ...
- C# static 字段初始值设定项无法引用非静态字段、方法或属性
问题:字段或属性的问题字段初始值设定项无法引用非静态字段.方法 下面代码出错的原因,在类中定义的字段为什么不能用? public string text = test(); //提示 字段或属性的问题 ...
- eclipse 中main()函数中的String[] args如何使用?通过String[] args验证账号密码的登录类?静态的主方法怎样才能调用非static的方法——通过生成对象?在类中制作一个方法——能够修改对象的属性值?
eclipse 中main()函数中的String[] args如何使用? 右击你的项目,选择run as中选择 run configuration,选择arguments总的program argu ...
- C# 字段初始值无法引用非静态字段、方法或属性( 类内部变量初始化)
问题:字段初始值设定项无法引用非静态字段.方法或属性的问题 在类中 变量赋值其他变量报错? public class TestClass{ public TestClass() { } pu ...
- 错误 10 非静态的字段、方法或属性“Test10.Program.a”要求对象引用
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Test ...
- 非静态的字段、方法或属性“System.Web.UI.Page.ClientScript.get”要求对象引用
解决Response.Write("<script>alert('修改失败,请稍后再试!');</script>");布局错误的问题 在后台CS代码(不是C ...
- 【JVM虚拟机】(8)--深入理解Class中--方法、属性表集合
#[JVM虚拟机](8)--深入理解Class中--方法.属性表集合 之前有关class文件已经写了两篇博客: 1.[JVM虚拟机](5)---深入理解JVM-Class中常量池 2.[JVM虚拟机] ...
随机推荐
- 关于一条定制长按Power键弹出Dialog的需求
如题,需要定制长按Power键弹出的Dialog,UI上的大致效果是:全屏,中间下拉按钮“Swipe Down To Power Off”下拉关机,底部左右两侧“Reboot”,“Cancel”按钮, ...
- MVC JsonResult
public JsonResult GetJson() { var res = new JsonResult(); res.Data = new {isSucceed = true, returnMs ...
- winform自动更新并实现文件的批量异步下载
public partial class update : Form { private WebClient client; int downfilenum = 0; ...
- java四种xml解析区别
1.DOM解析 dom解析是根据树形结构解析,将整个文档加载到内存中,因此对内存的要求较高,所以可以对该文档数据进行多次操作(增,删,改,查). 2.SAX解析 SAX解析是根据事件模型解析,边读取文 ...
- UIKit
UIAlertView UIAlertView 调用创建好对象的[testObject show]的show方法即可弹出UIAlertView UILabel UILabel常见属性 text:显示文 ...
- android之控件与布局
基本控件:TextViewButtonEditTextImageViewAlertDialog.BubliderProgressDialog 四种基本布局的特殊属性: LinerLayout andr ...
- Js的Url中传递中文参数乱码的解决
一:Js的Url中传递中文参数乱码问题,重点:encodeURI编码,decodeURI解码: 1.传参页面Javascript代码: 2. 接收参数页面:test02.html 二:如何获取Url& ...
- Mirantis OpenStack 8.0 版本
作为 OpenStack 领域标杆性企业之一的 Mirantis 在2016年3月初发布了最新的 MOS 8.0 版本.本文试着基于公开资料进行一些归纳分析. 1. 版本概况 1.1 概况 社区版本: ...
- SQL如何实现远程数据库链接
利用sp_addlinkeserver 进行远程数据库连接可以把多个数据库的数据,放置到一个数据库中, 或者有设置操作权限的情况下,我们可以通过这种方式进行查询,备份数据等操作. 首先,我们要创建连接 ...
- [Q]复制授权了文件但仍显示“未注册”问题(安装在非默认目录或目录包含中文)
1. 注意要将解压后的文件复制到CAD批量打图精灵安装目录,而不要复制文件夹,复制是提示是否需要覆盖,要选择覆盖. 2. 若通过第1步操作仍然显示“未注册”,则可能是由于安装目录含有中文或者不是默认目 ...