使用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);
}
有什么很酷的简化方法吗?唯一不同的是访问器名称和类型,而测试(即测试方法)是相同的。
尝试使用反射来查找和调用您正在寻找的属性。
下面是一个简单的示例,它大致完成了您要做的事情:
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);