使用c++风格的宏或函数指针解决重复的单元测试函数

本文关键字:函数 单元测试 解决 指针 c++ 风格 使用 | 更新日期: 2023-09-27 18:16:53

我正在编写测试代码来验证第三方API。该API由所有类型的命令组成,但是为了进行讨论,让我们看一下布尔类型的访问器,如下所示:

// The code is simplified to explain the problem.
// Assume "Enable" does complex items like talking
// to a database, or complex calculations.
Blah item = new Blah();
item.GroupA.Enable = true;
item.GroupB.Enable = true;
// etc, say we have A LOT of "groups".

我当前的单元测试看起来像这样:

public void GroupAEnable()
{
   bool val = true;
   mItem.GroupA.Enable = val;
   bool ret = mItem.GroupA.Enable;
   VerifyEqual(val, ret);
   val = false;
   item.GroupA.Enable = val;
   ret = mItem.GroupA.Enable;
   VerifyEqual(val, ret);
}
public void GroupBEnable()
{
   bool val = true;
   mItem.GroupB.Enable = val;
   bool ret = mItem.GroupB.Enable;
   VerifyEqual(val, ret);
   val = false;
   mItem.GroupB.Enable = val;
   ret = mItem.GroupB.Enable;
   VerifyEqual(val, ret);
}

我必须为数百个"组"重复此操作。

在c++中,我们可能会把它宏化,然后像这样做:

#define TEST_BOOL(cmd)       '
   bool val = true;          '
   mItem.##cmd## = val;      '
   bool ret = mItem.##cmd##; '
   VerifyEqual(val, ret);    '
   val = false;              '
   mItem.##cmd## = val;      '
   ret = mItem.##cmd##;      '
   VerifyEqual(val, ret)     
public void GroupAEnable()
{
   TEST_BOOL(GroupA.Enable);
}
public void GroupBEnable()
{
   TEST_BOOL(GroupB.Enable);
}

c#的一个解决方案是创建一个接受Action的TestBool函数,但是仍然需要为每个组输入大量的类型。

public void TestBool(Action setter, Action getter)
{
   ...
}
public void GroupAEnable()
{
   TestBool(x => mItem.GroupA.Enable = x,
            () => mItem.GroupA.Enable);
}

有什么很酷的简化方法吗?唯一不同的是访问器名称和类型,而测试(即测试方法)是相同的。

使用c++风格的宏或函数指针解决重复的单元测试函数

尝试使用反射来查找和调用您正在寻找的属性。

下面是一个简单的示例,它大致完成了您要做的事情:

public static class Program
{
    [STAThread]
    static void Main()
    {
        NeedsTesting target = new NeedsTesting();
        DoTest( target );
    }
    private static void DoTest(NeedsTesting target)
    {
        Type type = typeof( NeedsTesting );
        PropertyInfo[] properties;
        int count = 0;
        properties = type.GetProperties();
        foreach( PropertyInfo property in properties )
        {
            if( property.Name.StartsWith( "Group" ) )
            {
                count++;
                TestProperty( target, property );
            }
        }
        if( count != 5 )
        {
            VerifyEquals( false, true, "Did not find all 5 properties to test" );
        }
    }
    private static void TestProperty( NeedsTesting target, PropertyInfo property )
    {
        bool result;
        property.SetValue( target, true );
        result = (bool)property.GetValue( target );
        VerifyEquals( result, true, string.Format("Property '{0}' failed to retain a 'true' value.", property.Name ) );
        property.SetValue( target, false );
        result = (bool)property.GetValue( target );
        VerifyEquals( result, false, string.Format( "Property '{0}' failed to retain a 'false' value.", property.Name ) );
    }
    private static void VerifyEquals( bool left, bool right, string message )
    {
        if( left != right )
        {
            throw new Exception(
                 string.Format(
                    "Unit test failed - values were not equal:'r'n" +
                    "   left: {0}'r'n" +
                    "  right: {1}'r'n" +
                    "Message:'r'n" +
                    "{2}",
                    left,
                    right,
                    message
                )
            );
        }
    }
}
public class NeedsTesting
{
    private bool groupEValue;
    public bool GroupA { get; set; }
    public bool GroupB { get; set; }
    public bool GroupC { get; set; }
    public bool GroupD { get; set; }
    public bool GroupE
    {
        get
        {
            return this.groupEValue;
        }
        set
        {
            // Oops, this one is broken.
            value = false;
        }
    }
}

功能。根据您的要求,Sometype可能需要成为通用的:

void BoringTest(Sometype item)
{
   bool val = true;
   item.Enable = val;
   bool ret = item.Enable;
   VerifyEqual(val, ret);
   val = false;
   item.Enable = val;
   ret =item.Enable;
   VerifyEqual(val, ret);
}
public void GroupAEnable()
{
   BoringTest(mItem.GroupA);
}
public void GroupBEnable()
{
   BoringTest(mItem.GroupB);
}

另一个选择是使用反射,如果你不担心速度。相关:c# -设置带有反射的属性

dynamic使用反射,可能在这里帮助你,但我不完全确定。我已经很久没用过它了。请注意,动态在运行时而不是编译时失败。

void BoringTest(object i)
{
   dynamic item = i;
   bool val = true;
   item.Enable = val;
   bool ret = item.Enable;
   VerifyEqual(val, ret);
   val = false;
   item.Enable = val;
   ret =item.Enable;
   VerifyEqual(val, ret);
}
public void GroupAEnable()
{
   BoringTest(mItem.GroupA);
}
public void GroupBEnable()
{
   BoringTest(mItem.GroupB);
}

第三种选择是使用中介,例如xml和xslt,或者您公司喜欢的任何中介,来生成整个测试文件。

这是一个基于表达式的方法:

public static void VerifyMemberEqualsValueAfterSetting<TValue>(
    Expression<Func<TValue>> memberExpression,
    TValue value)
{
    var member = (MemberExpression) memberExpression.Body;
    var parameter = Expression.Parameter(typeof (TValue));
    var assignExpresison = Expression.Assign(member, parameter);
    Expression.Lambda<Action<TValue>>(assignExpresison, parameter).Compile()(value);
    var after = Expression.Lambda<Func<TValue>>(member, null).Compile()();
    VerifyEqual(value,after);
}

然后像这样使用:

VerifyMemberEqualsValueAfterSetting(()=> mItem.GroupA.Enabled, true);
VerifyMemberEqualsValueAfterSetting(()=> mItem.GroupA.Enabled, false);

或者更简单地说:

public static void VerifyMemberEqualsValueAfterSetting<TValue>(
    Expression<Func<TValue>> memberExpression,
    params TValue[] values)
{
    var member = (MemberExpression) memberExpression.Body;
    var parameter = Expression.Parameter(typeof (TValue));
    var assignExpresison = Expression.Assign(member, parameter);
    var setter =Expression.Lambda<Action<TValue>>(assignExpresison, parameter).Compile();
    var getter = Expression.Lambda<Func<TValue>>(member, null).Compile();
    foreach(var value in values)
    {
         setter(value);
         VerifyEqual(value,getter(value));
    }
}

那么可以只写

VerifyMemberEqualsValueAfterSetting(()=> mItem.GroupA.Enabled, true, false);
VerifyMemberEqualsValueAfterSetting(()=> whatEver.Count, 1, 2, 3);