如何使用NUnit确保Debug.Assert正确触发

时间:2012-02-10 15:22:25

标签: c# c#-4.0 nunit

我一直试图解决这个问题:如何创建一个单元测试来测试一个函数是否因为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);
    }

3 个答案:

答案 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而不是您期望的空集合的语句之后。