是Assert.Fail()被认为是不好的做法?

时间:2008-09-23 12:27:17

标签: unit-testing xunit.net

我在使用Assert.Tail做了很多TDD。我通常一次只进行一次测试,但是当我得到想要实现的东西的想法时,我会快速写一个空测试,其中测试方法的名称表明我想要实现的todo-list。为了确保我不忘记我在主体中放置了Assert.Fail()。

在尝试xUnit.Net时,我发现他们没有实现Assert.Fail。当然你总是可以Assert.IsTrue(false),但这并没有表达我的意图。我得到的印象是Assert.Fail没有故意实施。这被认为是不好的做法吗?如果是这样的话?


@Martin Meredith 这不完全是我做的。我先写一个测试,然后实现代码使它工作。通常我会同时考虑几个测试。或者我想在我正在做其他事情的时候写一个测试。那是我写一个空洞的失败测试来记住的时候。当我开始编写测试时,我整齐地尝试测试。

@Jimmeh 这看起来是个好主意。忽略的测试不会失败,但它们仍会显示在单独的列表中。必须尝试一下。

@Matt Howells 很好的主意。 NotImplementedException在这种情况下比assert.Fail()更好地传达意图

@Mitch Wheat 这就是我想要的。它似乎被排除在外,以防止它以另一种滥用它的方式被滥用。

14 个答案:

答案 0 :(得分:51)

对于这种情况,我不是调用Assert.Fail,而是执行以下操作(在C#/ NUnit中)

[Test]
public void MyClassDoesSomething()
{
    throw new NotImplementedException();
}

它比Assert.Fail更明确。

似乎普遍认为最好使用比Assert.Fail()更明确的断言。大多数框架都必须包含它,因为它们不提供更好的替代方案。例如,NUnit(和其他)提供ExpectedExceptionAttribute来测试某些代码是否抛出特定的异常类。但是,为了测试异常上的属性是否设置为特定值,无法使用它。相反,你必须求助于Assert.Fail:

[Test]
public void ThrowsExceptionCorrectly()
{
    const string BAD_INPUT = "bad input";
    try
    {
        new MyClass().DoSomething(BAD_INPUT);
        Assert.Fail("No exception was thrown");
    }
    catch (MyCustomException ex)
    {
         Assert.AreEqual(BAD_INPUT, ex.InputString); 
    }
}

xUnit.Net方法Assert.Throws使得它更加整洁,而不需要Assert.Fail方法。通过不包括Assert.Fail()方法,xUnit.Net鼓励开发人员查找和使用更明确的替代方案,并在必要时支持创建新的断言。

答案 1 :(得分:17)

故意被遗漏。这是Brad Wilson的答复,为什么没有Assert.Fail():

  

实际上,我们并没有忽视这一点。一世   找到Assert.Fail是一个拐杖   意味着可能有一个   断言失踪。有时它只是   测试结构的方式,和   有时这是因为Assert可以   使用另一个断言。

答案 2 :(得分:9)

我总是使用Assert.Fail()来处理你已经检测到测试应该通过简单值比较之外的逻辑失败的情况。举个例子:

try 
{
  // Some code that should throw ExceptionX
  Assert.Fail("ExceptionX should be thrown")
} 
catch ( ExceptionX ex ) 
{
  // test passed
}

因此,框架中缺少Assert.Fail()对我来说是个错误。我建议修补Assert类以包含Fail()方法,然后将修补程序提交给框架开发人员,以及添加它的原因。

至于你在工作区中创建故意失败的测试的做法,提醒自己在提交之前实现它们,这对我来说似乎是一种很好的做法。

答案 3 :(得分:5)

我使用MbUnit进行单元测试。他们可以选择忽略测试,在测试套件中显示为橙色(而不是绿色或红色)。也许xUnit有类似的东西,并且意味着你甚至不必将任何断言放入方法中,因为它会以一种令人烦恼的不同颜色显示出来,这很难被错过?

编辑:

在MbUnit中,它采用以下方式:

[Test]
[Ignore]
public void YourTest()
{ } 

答案 4 :(得分:4)

这是我在编写测试代码时使用的模式,我希望通过设计抛出异常:

[TestMethod]
public void TestForException()
{
    Exception _Exception = null;

    try
    {
        //Code that I expect to throw the exception.
        MyClass _MyClass = null;
        _MyClass.SomeMethod();
        //Code that I expect to throw the exception.
    }
    catch(Exception _ThrownException)
    {   
        _Exception = _ThrownException
    }
    finally
    {
        Assert.IsNotNull(_Exception);
        //Replace NullReferenceException with expected exception.
        Assert.IsInstanceOfType(_Exception, typeof(NullReferenceException));
    }
}

恕我直言,这是使用Assert.Fail()测试异常的更好方法。这样做的原因是我不仅要测试抛出的异常,还要测试异常类型。我意识到这与Matt Howells的回答相似,但是使用finally块的IMHO更加强大。

显然,仍然可以包含其他Assert方法来测试异常输入字符串等。我将非常感谢您对我的模式的评论和观点。

答案 5 :(得分:3)

就个人而言,使用测试套件作为这样的待办事项列表没有问题,只要您最终在之前编写测试,即可实现要传递的代码。

话虽如此,我过去常常使用这种方法,虽然现在我发现这样做会让我走上前面写太多测试的道路,这很奇怪就像不编写测试的反向问题根本就是:你最终做出的设计决定有点太早恕我直言。

顺便提一下,在MSTest中,标准测试模板在其样本的末尾使用Assert.Inconclusive。

AFAIK xUnit.NET框架旨在非常轻量级,是的,他们确实故意削减了Fail,以鼓励开发人员使用明确的失败条件。

答案 6 :(得分:2)

狂野猜测:隐瞒Assert.Fail旨在阻止您认为编写测试代码的好方法就像是一堆巨大的意大利面条,在坏情况下会导致Assert.Fail。 [编辑添加:其他人的答案广泛证实了这一点,但引用了]

由于这不是你正在做的事情,xUnit.Net可能过度保护。

或者也许他们只是认为它是如此罕见而且非正交而不必要。

我更喜欢实现一个名为ThisCodeHasNotBeenWrittenYet的函数(为了便于输入,实际上更短一些)。无法更明确地传达意图,并且您有一个精确的搜索词。

是否失败,或未实现(引发链接器错误),或者是否为无法编译的宏,可以更改为适合您当前的首选项。例如,当您想运行 完成的内容时,您希望失败。当你坐下来全部摆脱它们时,你可能想要编译错误。

答案 7 :(得分:2)

使用我通常做的好代码:

void goodCode() {
     // TODO void goodCode()
     throw new NotSupportedOperationException("void goodCode()");
}

使用测试代码我通常会这样做:

@Test
void testSomething() {
     // TODO void test Something
     Assert.assert("Some descriptive text about what to test")
}

如果使用JUnit,并且不想得到失败,但错误,那么我通常会这样做:

@Test
void testSomething() {
     // TODO void test Something
     throw new NotSupportedOperationException("Some descriptive text about what to test")
}

答案 8 :(得分:2)

谨防Assert.Fail及其腐败影响,使开发人员编写愚蠢或破坏的测试。例如:

[TestMethod]
public void TestWork()
{
    try {
        Work();
    }
    catch {
        Assert.Fail();
    }
}

这很愚蠢,因为try-catch是多余的。如果它抛出异常,则测试失败。

另外

[TestMethod]
public void TestDivide()
{
    try {
        Divide(5,0);
        Assert.Fail();
    } catch { }
}

这已经破了,测试将始终通过Divide函数的任何结果。同样,当且仅当它抛出异常时,测试才会失败。

答案 9 :(得分:1)

为什么要使用Assert.Fail来表示应该抛出异常?这是不必要的。为什么不直接使用ExpectedException属性?

答案 10 :(得分:0)

如果您正在编写一个失败的测试,然后为其编写代码,那么编写测试。这不是测试驱动开发。

从技术上讲,如果您正确使用测试驱动开发,则不需要Assert.fail()。

您是否考虑过使用Todo列表或将GTD方法应用于您的工作?

答案 11 :(得分:0)

MS Test有 Assert.Fail(),但它也有 Assert.Inconclusive()。我认为对Assert.Fail()最合适的用法是,如果你有一些内联逻辑很难放入断言,尽管我甚至想不出任何好的例子。在大多数情况下,如果测试框架支持Assert.Fail()之外的其他内容,则使用它。

答案 12 :(得分:0)

我认为你应该问自己(前期)测试应该做什么。

首先,您编写一组(一组)测试而没有实现。 也许,也是下雨天的情景。

所有这些测试都必须失败,才能进行正确的测试: 所以你想要实现两件事: 1)验证您的实施是否正确; 2)验证您的单元测试是否正确。

现在,如果你做前期TDD,你想要执行所有测试,也就是NYI部分。 如果符合以下条件,则总测试运行的结果通过 1)所有实现的东西都成功了 2)所有NYI的东西都失败了

毕竟,如果你的单元测试成功而没有实现,那将是一个单元测试,不是吗?

您希望最终得到一些连续集成测试的邮件,它会检查所有已实现和未实现的代码,并在任何已实现的代码失败或任何未实现的代码成功时发送。两者都是不受欢迎的结果。

只是写一个[忽略]测试不会完成这项工作。 也没有,断言停止第一个断言失败,而不是在测试中运行其他测试行。

现在,如何实现这一目标呢? 我认为它需要更高级的测试组织。 并且它需要一些其他机制然后断言以实现这些目标。

我认为你必须拆分测试并创建一些完全运行但必须失败的测试,反之亦然。

想法是将测试分成多个程序集,使用测试分组(mstest中的有序测试可以完成这项工作)。

尽管如此,如果不是NYI部门的所有测试都失败的CI构建邮件也不容易且直截了当。

答案 13 :(得分:0)

这是我们用于 Assert.Fail()的用例。

我们的单元测试的一个重要目标是不要触摸数据库

有时,模拟无法正确进行,或者修改了应用程序代码,并且无意间进行了数据库调用。

这在调用堆栈中可能很深。可能会捕获到该异常,因此它不会冒泡,或者因为测试最初是在数据库中运行的,所以该调用将起作用。

我们要做的是向单元测试项目中添加一个配置值,以便在首次请求数据库连接时,我们可以调用 Assert.Fail(“ Database accessed”);

Assert.Fail()全局起作用,即使在不同的库中也是如此。因此,这是所有单元测试的全部内容。

如果其中任何一个在单元测试项目中命中数据库,则它们将失败。

因此,我们很快失败了。