• 【原创】通过DLR创建私有成员访问器进行白盒测试


    在编写NUnit测试的时候有时候需要对非公有有成员进行测试,每个方法都通过反射还是比较麻烦而且不美观。在4.0中可以借助DLR创建一个访问器Accessor动态获取私有成员直接调用。

    动态语言运行时 (DLR) 是一种新运行时环境,它将一组适用于动态语言的服务添加到 CLR。借助于 DLR,可以更轻松地开发要在 .NET Framework 上运行的动态语言,而且向静态类型化语言添加动态功能也会更容易。

    假设有一个类Class1

     public class Class1
    {
    private int Add(int a, int b)
    {
    return a + b;
    }
    }

    我希望能够这样编写测试

    [Test]
    public void Class1AddTest()
    {
    dynamic class1 = new Accessor(typeof(Class1));
    int result = class1.Add(1, 2);
    Assert.That(result, Is.EqualTo(3));
    }

    现在我设计了一个Accessor类来完成这个工作 .

    下面是我的实现方式:

    首先要继承动态行为基类DynamicObject,先定义成员包括被代理的实例、实例包含的方法表和属性表

    /// <summary>
    /// 实例访问器
    /// </summary>
    public sealed class Accessor : DynamicObject
    {
    #region 成员
    /// <summary>
    /// 实例
    /// </summary>
    private readonly object instance;
    /// <summary>
    /// 方法表
    /// </summary>
    private readonly IDictionary<string, IGrouping<string, MethodInfo>> methodInfos;
    /// <summary>
    /// 属性表
    /// </summary>
    private readonly IDictionary<string, PropertyInfo> propertyInfos;
    #endregion
    }


    为了实现成员访问必须实现对方法Method、属性Property,和索引器Index的访问

    首先是方法Method,

    方法调用有一个重要的问题就是方法重载overload的解决

    使用方法名作为Key用 Dictionary<string, IGrouping<string, MethodInfo>>来分组保存重载的方法

    方法表和属性表通过instance的Type反射出来,不过要注意包含私有方法。

     public Accessor(object instance)
    {
    this.instance = instance;
    const BindingFlags flag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
    var type = instance.GetType();

    this.methodInfos = type.GetMethods(flag)
    .GroupBy(p => p.Name)
    .ToDictionary(p => p.Key);
    this.propertyInfos = type.GetProperties(flag)
    .ToDictionary(p => p.Name);
    }


    查找重载方法主要通过对比参数表实现,具体如下:

    private static bool CheckOverLoadParameters(MethodInfo method, object[] args)
    {
    var parms = method.GetParameters();
    var length = parms.Length;
    if (length == 0 && args == null) return true;
    if (length != args.Length) return false;

    for (int i = 0; i < length; i++)
    {
    if (parms[0].ParameterType != args[0].GetType())
    return false;
    }
    return true;
    }


    下面要做的就是重写DynamicObject的访问方法bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) 返回值true表示此方法可用。

    查询到对于的MethodInfo,通过反射调用即可,具体实现如下:

     public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
    IGrouping<string, MethodInfo> methods;
    bool success = methodInfos.TryGetValue(binder.Name, out methods)
    if (success)
    {
    var method = methods.Where(p => CheckOverLoadParameters(p, args)).FirstOrDefault();
    if (method != null)
    {
    result = method.Invoke(instance, args);
    return true;
    }
    }
    return base.TryInvokeMember(binder, args, out result);
    }

    属性不能重名就比较简单了,直接从属性表获取即可

    属性Get操作需要重写DynamicObject的访问方法TryGetMember,Set操作需要重写DynamicObject的访问方法TrySetMember。

    我们都知道XXX的Get操作实际上是调用get_XXX(),所以反射调用的时候需要从propertyInfo的GetGetMethod获取Get操作的MethodInfo

    Get操作的具体实现如下,Set操作只是将GetGetMethod换成GetSetMethod。

     public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
    PropertyInfo propertyInfo;
    bool success = propertyInfos.TryGetValue(binder.Name, out propertyInfo);
    if (success)
    {
    var method = propertyInfo.GetGetMethod(true);
    if (method != null)
    {
    result = method.Invoke(instance, null);
    return true;
    }
    }
    return base.TryGetMember(binder, out result);
    }


    最后是索引器了,了解CLR的都知道索引器其实是get_Item属性,那么在属性表查询属性名为"Item"的即可。

    索引器Get操作需要重写DynamicObject的访问方法TryGetIndex,Set操作需要重写DynamicObject的访问方法TrySetIndex。

    代码和属性操作差不多,只是反射的时候直接调用propertyInfo的SetValue和GetValue。

    索引器Get操作具体实现如下:

      public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
    {
    PropertyInfo propertyInfo;
    bool success = propertyInfos.TryGetValue("Item", out propertyInfo);
    if (success)
    {
    result = propertyInfo.GetValue(instance, indexes);
    return true;
    }
    return base.TryGetIndex(binder, indexes, out result);
    }

    好了这样就可以调用了,测试一下:

    public class Class1
    {
    private int Add(int a, int b)
    {
    return a + b;
    }

    private int Add(int a, int b, int c)
    {
    return a + b + c;
    }

    private int X { get; set; }

    public int this[int i]
    {
    get { return tmp[i]; }
    set { tmp[i] = value; }
    }

    private readonly int[] tmp = new int[100];
    }

    [TestFixture]
    public class Class1Test
    {

    [Test]
    public void AddTest()
    {
    dynamic class1 = new Accessor(typeof(Class1));
    int result = class1.Add(1, 2);
    Assert.That(result, Is.EqualTo(3));
    int result = class1.Add(1, 2, 3);
    Assert.That(result, Is.EqualTo(6));
    }

    [Test]
    public void XTest()
    {
    dynamic class1 = new Accessor(typeof(Class1));
    class1.X = 1;
    Assert.That(class1.X, Is.EqualTo(1));
    }

    [Test]
    public void IndexIntTest()
    {
    dynamic class1 = new Accessor(typeof(Class1));
    class1[0] = 1;
    Assert.That(class1[0], Is.EqualTo(1));
    }
    }

    这样测试私有成员就方便多了。


    Accessor完整代码如下:

    View Code
        /// <summary>
    /// 实例访问器
    /// </summary>
    public sealed class Accessor : DynamicObject
    {
    #region 成员
    /// <summary>
    /// 实例
    /// </summary>
    private readonly object instance;
    /// <summary>
    /// 方法表
    /// </summary>
    private readonly IDictionary<string, IGrouping<string, MethodInfo>> methodInfos;
    /// <summary>
    /// 属性表
    /// </summary>
    private readonly IDictionary<string, PropertyInfo> propertyInfos;
    #endregion

    #region 构造函数
    /// <summary>
    /// 通过实例创建实例访问器
    /// </summary>
    /// <param name="instance">实例对象</param>
    public Accessor(object instance)
    {
    this.instance = instance;
    const BindingFlags flag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
    var type = instance.GetType();

    this.methodInfos = type.GetMethods(flag)
    .GroupBy(p => p.Name)
    .ToDictionary(p => p.Key);
    this.propertyInfos = type.GetProperties(flag)
    .ToDictionary(p => p.Name);
    }

    /// <summary>
    /// 通过指定实例类型的默认构造创建实例访问器
    /// </summary>
    /// <param name="type">实例类型</param>
    public Accessor(Type type)
    : this(Activator.CreateInstance(type))
    {
    }

    /// <summary>
    /// 通过指定实例类型创建实例访问器
    /// </summary>
    /// <param name="type">实例类型</param>
    /// <param name="args">参数</param>
    public Accessor(Type type, params object[] args)
    : this(Activator.CreateInstance(type, args))
    {
    }



    /// <summary>
    /// 通过指定实例类型名创建实例访问器
    /// </summary>
    /// <param name="assemblyName">程序集名称</param>
    /// <param name="fullTypeName">实例类型全名</param>
    /// <param name="args">参数</param>
    public Accessor(string assemblyName, string fullTypeName, params object[] args)
    : this(Activator.CreateInstance(
    assemblyName, fullTypeName, false,
    BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic,
    null, args, null, null).Unwrap())
    {

    }

    /// <summary>
    /// 通过指定实例类型名的默认构造函数创建实例访问器
    /// </summary>
    /// <param name="assemblyName">程序集名称</param>
    /// <param name="fullTypeName">实例类型全名</param>
    public Accessor(string assemblyName, string fullTypeName)
    : this(assemblyName, fullTypeName, null)
    {

    }

    /// <summary>
    /// 检查方法重载
    /// </summary>
    /// <param name="method"></param>
    /// <param name="args"></param>
    /// <returns></returns>
    private static bool CheckOverLoadParameters(MethodInfo method, object[] args)
    {
    var parms = method.GetParameters();
    var length = parms.Length;
    if (length == 0 && args == null) return true;
    if (length != args.Length) return false;

    for (int i = 0; i < length; i++)
    {
    if (parms[0].ParameterType != args[0].GetType())
    return false;
    }
    return true;
    }
    #endregion

    #region DynamicObject 访问方法重写
    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
    IGrouping<string, MethodInfo> methods;
    bool success = methodInfos.TryGetValue(binder.Name, out methods);
    if (success)
    {
    var method = methods.Where(p => CheckOverLoadParameters(p, args)).FirstOrDefault();
    if (method != null)
    {
    result = method.Invoke(instance, args);
    return true;
    }
    }
    return base.TryInvokeMember(binder, args, out result);
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
    PropertyInfo propertyInfo;
    bool success = propertyInfos.TryGetValue(binder.Name, out propertyInfo);
    if (success)
    {
    var method = propertyInfo.GetGetMethod(true);
    if (method != null)
    {
    result = method.Invoke(instance, null);
    return true;
    }
    }
    return base.TryGetMember(binder, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
    PropertyInfo propertyInfo;
    bool success = propertyInfos.TryGetValue(binder.Name, out propertyInfo);
    if (success)
    {
    var method = propertyInfo.GetSetMethod(true);
    if (method != null)
    {
    method.Invoke(instance, new[] { value });
    return true;
    }
    }
    return base.TrySetMember(binder, value);
    }

    public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
    {
    PropertyInfo propertyInfo;
    bool success = propertyInfos.TryGetValue("Item", out propertyInfo);
    if (success)
    {
    result = propertyInfo.GetValue(instance, indexes);
    return true;
    }
    return base.TryGetIndex(binder, indexes, out result);
    }

    public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
    {
    PropertyInfo propertyInfo;
    bool success = propertyInfos.TryGetValue("Item", out propertyInfo);
    if (success)
    {
    propertyInfo.SetValue(instance, value, indexes);
    return true;
    }
    return base.TrySetIndex(binder, indexes, value);
    }
    #endregion


    }




  • 相关阅读:
    Maven 梳理
    Maven 梳理
    Maven 梳理-自动创建Maven项目(非web)
    Maven 梳理-手动创建Maven项目(非web),使用Maven编译、测试、打包、安装、引用
    Maven 梳理 -目录结构
    Maven 梳理-安装配置
    Spring 梳理-数据访问-DB
    JNDI数据源的配置
    Spring 梳理-JdbcTemplate简介
    Docker常用命令
  • 原文地址:https://www.cnblogs.com/kiminozo/p/2287803.html
Copyright © 2020-2023  润新知