有时候我们写好的类库中,某些类的属性和方法不应该暴露出来,那么如何对这些非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的方法和属性的单元测试的更多相关文章

  1. TDD学习笔记【三】---是否需针对非public方法进行测试?

    前言 在Visual Studio 2012 中,针对Unit Test 的部分,有一个重要的变动: 原本针对「测试对象非public 的部分」,开发人员可通过Visual Studio 2010 自 ...

  2. 非静态的字段、方法或属性“System.Web.UI.Page.ClientScript...”要求对象引用 (封装注册脚本)

    在写项目时想对asp.net的注册前台脚本事件进行封装,就添加了一个BasePage.cs页面,但一直报错‘非静态的字段.方法或属性“System.Web.UI.Page.ClientScript.. ...

  3. C#变量初始化问题:字段初始值无法引用非静态字段、方法或属性

    http://www.cnblogs.com/bluestorm/p/3432190.html 问题:字段初始值设定项无法引用非静态字段.方法或属性的问题 下面代码出错的原因,在类中定义的字段为什么不 ...

  4. C# static 字段初始值设定项无法引用非静态字段、方法或属性

    问题:字段或属性的问题字段初始值设定项无法引用非静态字段.方法 下面代码出错的原因,在类中定义的字段为什么不能用? public string text = test(); //提示 字段或属性的问题 ...

  5. eclipse 中main()函数中的String[] args如何使用?通过String[] args验证账号密码的登录类?静态的主方法怎样才能调用非static的方法——通过生成对象?在类中制作一个方法——能够修改对象的属性值?

    eclipse 中main()函数中的String[] args如何使用? 右击你的项目,选择run as中选择 run configuration,选择arguments总的program argu ...

  6. C# 字段初始值无法引用非静态字段、方法或属性( 类内部变量初始化)

    问题:字段初始值设定项无法引用非静态字段.方法或属性的问题 在类中  变量赋值其他变量报错? public class TestClass{  public TestClass()  {  }  pu ...

  7. 错误 10 非静态的字段、方法或属性“Test10.Program.a”要求对象引用

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Test ...

  8. 非静态的字段、方法或属性“System.Web.UI.Page.ClientScript.get”要求对象引用

    解决Response.Write("<script>alert('修改失败,请稍后再试!');</script>");布局错误的问题 在后台CS代码(不是C ...

  9. 【JVM虚拟机】(8)--深入理解Class中--方法、属性表集合

    #[JVM虚拟机](8)--深入理解Class中--方法.属性表集合 之前有关class文件已经写了两篇博客: 1.[JVM虚拟机](5)---深入理解JVM-Class中常量池 2.[JVM虚拟机] ...

随机推荐

  1. openstack trove,使pylint忽略错误

    一.什么是pylint Pylint 是一个 Python 代码分析工具,它分析 Python 代码中的错误,查找不符合代码风格标准和有潜在问题的代码. Pylint 是一个 Python 工具,除了 ...

  2. SQLServer批量更新

    两种写法,暂未知道区别 1. UPDATE aSET a.resblockid = ( SELECT b.resblockId FROM yyy.dbo.yyy b WHERE a.unitId = ...

  3. 问题记录1:The VMware Authorization Service is not running.

    问题 VMware Workstation cannot connect to the virtual machine. Make sure you have rights to run the pr ...

  4. Linux的一些简单命令(三)

    1.解压缩算法:使用gzip算法进行解压缩,   压缩语法:gzip filename   解压语法:gzip -dv filename 2.解压缩算法:使用bzip2算法进行解压缩, 压缩语法:bz ...

  5. centos7配置开启无线网卡,重启防火墙

    centos7配置无线网卡: 在虚拟机为nat的网络连接下(就是默认的那个),centos7默认网卡未激活. 可以设置 文件 /etc/sysconfig/network-scripts/ifcfg- ...

  6. Android之HandlerThread

    HandlerThread详解 1 HandlerThread基本原理 HandlerThread继承自Thread,它是一种可以使用Handler的Thread.它的实现很简单,就是在run方法中通 ...

  7. USACO 3.2 Magic Squares

    Magic SquaresIOI'96 Following the success of the magic cube, Mr. Rubik invented its planar version, ...

  8. 浙大pat1009题解

    1009. Product of Polynomials (25) 时间限制 400 ms 内存限制 32000 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yu ...

  9. H5移动端页面设计心得分享(转载)

    去年JDC出了不少优秀的武媚娘…不,H5呢,大家都很拼,同时当然也积累了一些经验和教训,今天结合咱们的实战案例,从字体,排版,动效,音效,适配性,想法这几个方面好好聊一聊关于H5的设计,希望对同学们有 ...

  10. openwrt的编译环境

    安装centos7 ,以最小的方式安装在 vmware 的虚拟机了.(yum 更新系统就不提了.下面是没有yum更新的情况下的记录和总结) 安装后,发现 ifconfig 命令不好用,得用 ip ad ...