我一直试图解决这个问题:如何创建一个单元测试来测试一个函数是否因为Debug.Assert(来自System.Diagnostics
)而失败,将它标记为传递如果没有,那就失败了。
我知道NUnit
有[ExpectedException(typeof( ArgumentException ) )]
功能,但我似乎无法从MSDN网站上找出它是什么类型的例外。 Intuition会说它可能类似于AssertionException,而且确实存在......但它是NUnit框架的一部分。我猜这是NUnit断言的例外。我可以通过使用:
[ExpectedException(typeof(Exception))]
但这会引起标准Windows调试窗口出现的问题。在我的搜索中,我遇到了一些方法来移除这个窗口,但是这就像把屠宰刀带到了通常使用手术刀的手术室。因为我希望能够在出现意外情况时看到这个窗口,当我执行我的程序时。
我想有一种解决方法是用Debug.Assert
对应方式替换NUnit
方法(我现在还在我的项目中,所以它不是一个太大的重构),但我认为很多程序员都坚持使用Debug.Assert
功能,因为它是.NET的标准功能。
因此,我想知道如何“断言”Debug.Assertion
失败,而不必从我的项目中“谋杀”Windows调试屏幕?
要在我的代码中有一个合同的具体示例,下面有一个示例。对于那些看起来很熟悉的东西,它是Warhammer 40K桌面战争游戏中的To-Wound表,作为一种功能。
static public int assaultToHit(int _attacker_WeaponSkill,
int _defender_WeaponSkill)
{
//Preconditions
Debug.Assert(_attacker_WeaponSkill >= 1 && _attacker_WeaponSkill <= 10,
"Weapon Skill stat must be in range [1,10]");
Debug.Assert(_defender_WeaponSkill >= 1 && _defender_WeaponSkill <= 10,
"Weapon Skill stat must be in range [1,10]");
int target;
if (_attacker_WeaponSkill > _defender_WeaponSkill)
{
target=3;
}
else if (_defender_WeaponSkill >= (_attacker_WeaponSkill + _attacker_WeaponSkill + 1))
{
target=5;
}
else
{
target=4;
}
//postconditions
Debug.Assert(target >= 3 && target <= 5,
"To hit target for assault must be in range [3,5]");
return target;
}
测试前置条件的函数将是这样的:
[TestCase(-1,2)]
[TestCase(1, -2)]
[TestCase(-1, -2)]
[TestCase(11, 2)]
[TestCase(1, 20)]
[TestCase(11, 20)]
[ExpectedException(typeof(Exception))]
public void testContract_AssaultToHit(int _attacker_weaponskill,
int _defender_weaponskill)
{
Warhammer40kRules.assaultToHit(_attacker_weaponskill,
_defender_weaponskill);
}
答案 0 :(得分:5)
如果出现错误,您似乎使用了错误的工具来控制应用程序流。 Debug.Assert()
不应用于驱动应用程序逻辑流程。
单元测试应该涵盖一个真实的测试用例,看起来你要么试图实现错误的测试用例,要么你需要抛出异常/等而不是使用Debug.Assert()
。您可以共享一些代码,以便为您提供一些具体的建议。
无论如何,您可以阅读有关如何添加自定义跟踪侦听器和拦截Assert
调用的MSDN。
有用的链接:
答案 1 :(得分:3)
从https://stackoverflow.com/a/117247/605538可以找到建议使用异常进行公共接口,同时使用断言来验证内部代码。当单元测试函数的前置条件(以及类似的思想可以应用于后置条件时,尽管我个人更愿意在那里使用断言),因此可以建议使用异常而不是使用断言。
使用异常代替会产生如下样本:
static public int assaultToHit(int _attacker_WeaponSkill,
int _defender_WeaponSkill)
{
//Preconditions
if(!(_attacker_WeaponSkill >= 1 && _attacker_WeaponSkill <= 10))
{
throw new ArgumentOutOfRangeException("Attackers WeaponSkill must be in range [1,10]");
}
if(!(_defender_WeaponSkill >= 1 && _defender_WeaponSkill <= 10))
{
throw new ArgumentOutOfRangeException("Defenders WeaponSkill must be in range [1,10]");
}
...
//rest unchanged
}
再加上以下NUnit测试:
[TestCase(-1,2)]
[TestCase(1, -2)]
[TestCase(-1, -2)]
[TestCase(11, 2)]
[TestCase(1, 20)]
[TestCase(11, 20)]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void testContract_AssaultToHit(int _attacker_weaponskill,
int _defender_weaponskill)
{
Warhammer40kRules.assaultToHit(_attacker_weaponskill,
_defender_weaponskill);
}
补充工具栏[2014年6月12日]: 我最近认为你不应该在设计合同的背景下测试先决条件违规。如果你在设计时考虑到DbC,你基本上陈述如下:“如果你调用一个方法并满足它的前提条件,你就可以保证某种行为。如果你不满足方法的前提条件,你可以期待未定义的行为。”从广义上讲,术语“未定义的行为”,这意味着你不能指望任何形式的状态。您可能会遇到异常,可能根本没有任何事情发生,也许您的硬盘会被格式化(嗯......您明白了;))。因此,您无法测试“未定义的行为”。
您可以测试的是,您的防御性编程可确保先决条件失败不会导致函数运行,例如抛出Exception
。这种行为是可以测试的。
答案 2 :(得分:1)
对于您的特定情况,我建议您查看Microsoft Code Contracts,因为您具体的断言案例的目的是检查您的函数的输入合同。
如果Code Contracts对您来说太大了,我建议您在不遵守隐式合同时让合同抛出异常,而不是断言。
然而,Debug.Asserts仍然很有价值,但是应该在不太可能发生的情况下使用,例如在使调用返回null而不是您期望的空集合的语句之后。