Assert.Fail()是否被视为不良做法?


71

我在进行TDD时经常使用Assert.Fail。我通常一次只进行一个测试,但是当我对以后要实现的事情有想法时,我会迅速编写一个空测试,其中测试方法的名称表示要实现的待办事项列表。为了确保我不会忘记,我将Assert.Fail()放在主体中。

尝试xUnit.Net时,我发现他们尚未实现Assert.Fail。当然,您始终可以Assert.IsTrue(false),但这也无法传达我的意图。我给人的印象是Assert.Fail不是故意实现的。这被认为是不良做法吗?如果可以,为什么?


@Martin Meredith这不是我所做的。我确实先编写了一个测试,然后实现代码以使其正常工作。通常我一次想到几个测试。或者,我考虑在做其他事情时要编写的测试。那是我编写一个空的失败测试要记住的时候。等我开始编写测试时,我会先做好测试工作。

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

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

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


8
关于堆栈溢出的第一个xUnit.net问题。我想我有点cho。:)
布拉德·威尔逊

2
我能说什么..我喜欢这个项目。除了缺少Assert.Fail :-)
Mendelt

Answers:


53

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

[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鼓励开发人员查找和使用更明确的替代方案,并在必要时支持创建新的断言。


我第二次“抛出新的NotImplementedException()”。
亚伦·曼帕

4
throws方法允许使用lamdbas,我真的很喜欢它:MyException ex = Assert.Throws <MyException>(()=> action()); Assert.Equal(“ foobar”,ex.Message);
Jim Burger

就我个人而言,在“记录将来的测试”的情况下,我将编写测试并让其通过,但是它的空正文提醒我我想要在这里进行测试。或者,我什至只将测试名称/说明作为注释保留在测试文件中。但是,失败在这里会很糟糕。
布拉德·威尔逊

毫无疑问,未实现的测试会失败是正确的,因为根据定义,如果您尚未编写测试,但您尚未编写代码。如果测试失败破坏了CI,您也可以在其上放置[Ignore]属性。
马特·豪威尔斯

3
唐完全正确。失败的“未来测试占位符”扼杀了TDD节奏。一次一件事……将来的测试占位符是在提醒以后,而不是现在要做的事情。
布拉德·威尔逊

17

它被故意遗漏了。这是布拉德·威尔逊(Brad Wilson)关于为什么没有Assert.Fail()的答复:

实际上,我们并没有忽略这一点。我发现Assert.Fail是一个拐杖,这意味着可能缺少断言。有时,这只是测试的结构方式,有时是因为Assert可以使用另一个断言。


10

我一直使用Assert.Fail()处理一些案例,在这些案例中,您发现测试应该通过简单的值比较之外的逻辑失败。举个例子:

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

因此,在我的框架中缺少Assert.Fail()似乎是一个错误。我建议修补Assert类以使其包含Fail()方法,然后将修补程序以及添加该修补程序的理由提交给框架开发人员。

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


对于此示例,我可能会使用大多数框架通用的ExpectedExceptionAttribute来节省尝试捕获中的嵌套,尽管如果您要测试使用了正确的异常消息,则可以使用此方法。
Jim Burger

顺便说一句,我认为Brad Wilson会亲自取消任何试图向Assert类添加Fail方法的补丁程序。;)
Jim Burger

我在jUnit中做了很多事情。对我来说似乎很整洁优雅。NUnit中的ExpectedExceptionAttribute似乎消除了对Assert.Fail()的这种特殊需要,但是我们发现一个有效的用法这一事实表明可能还有其他用途。
苗条的

1
根据他的解释,没关系,这就是他的代码。如果他礼貌地给您“谢谢,但是不,这就是原因”,那就更好了。至少他得到的反馈是,有人对他的工作足够在意,希望对其进行改进,即使改进不是他想要的。
Craig Trader

3
是的,我们不愿意包含Fail()。就其价值而言,在使用该框架的18个月中(以及其公开发布的13个月),没有人要求Fail(),也没有人提交过补丁。我认为我们的推理是正确的。
布拉德·威尔逊

5

我将MbUnit用于单元测试。他们有一个选项可以忽略测试,该测试在测试套件中显示为橙色(而不是绿色或红色)。也许xUnit具有类似的含义,并且意味着您甚至不必在该方法中添加任何断言,因为它会以令人讨厌的不同颜色显示,从而使其难以错过?

编辑:

在MbUnit中,它是通过以下方式进行的:

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

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的回答,但是IMHO使用finally块更健壮。

显然,仍然可以包括其他Assert方法来测试异常输入字符串等。对于您对我的模式的评论和看法,我将不胜感激。


3

就个人而言,只要您最终能够开始编写测试,可以将测试套件用作这样的待办事项列表实现代码通过。

话虽如此,我曾经亲自使用这种方法,尽管现在我发现这样做使我走下了编写过多测试的道路,这很奇怪,就像完全不编写测试的反向问题:您最终过早做出恕我直言的设计决策。

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

AFAIK xUnit.NET框架旨在非常轻巧,是的,它们确实故意删除了Fail,以鼓励开发人员使用显式的失败条件。


我同意这样做过多会导致YAGNI问题:-)谢谢。
Mendelt

2

大胆的猜测:保留Assert.Fail旨在阻止您认为编写测试代码的一种好方法是将大量的意大利面条堆放到坏情况下导致Assert.Fail。[编辑添加:别人的回答大致上证实了这一点,但带有引号]

由于这不是您要执行的操作,因此xUnit.Net可能保护过度。

或者,也许他们只是认为它是如此罕见,如此不正交以至于不必要。

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

可以更改它是否失败,是否未实现(以引起链接器错误),或者是未编译的宏,以适合您当前的偏好。例如,当您要运行完成的操作时,您会失败。当您坐下来摆脱所有这些时,您可能需要编译错误。


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")
}

1

当心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函数的结果如何,测试将始终通过。同样,仅当抛出异常时,测试才会失败。


2
Smartarse:无法捕获某些异常(例如StackOverflowException)。
上校惊慌

0

如果您编写的测试只是失败了,然后为其编写代码,然后编写该测试。这不是测试驱动开发。

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

您是否考虑过使用待办事项清单或将GTD方法应用于您的工作?


如果您要测试不良行为怎么办?
本杰明·奥丁

您可以很容易地在Assert.IsTrue或Assert.IsFalse中将布尔表达式用于否定或肯定行为。
Jim Burger

1
发问者没有说他在完成测试之前正在编写代码。他说他在完成这些测试之前正在编写其他测试。这可能是也可能不是没有规律的,但我不认为这违反了TDD。
史蒂夫·杰索普

0

MS Test具有Assert.Fail()但也具有Assert.Inconclusive()。我认为,对于Assert.Fail()来说,最合适的用法是,如果您有一些内联逻辑,这些逻辑在声明时会很尴尬,尽管我什至都没有想到任何好的示例。在大多数情况下,如果测试框架支持Assert.Fail()以外的其他功能,则可以使用它。


0

我认为您应该问自己(先验)测试应该做什么。

首先,您编写一个(不包含)测试(一组)。也许还有下雨天的场景。

所有这些测试都必须失败才能成为正确的测试:因此,您要实现两件事:1)验证您的实现是否正确;2)确认您的单元测试正确。

现在,如果您进行前期TDD,则要执行所有测试以及NYI部分。如果满足以下条件,您的总测试运行就会通过:1)所有实施的东西都成功2)所有NYI的东西都失败

毕竟,如果在没有实现的情况下单元测试成功,那将是单元测试的目标,不是吗?

您希望最终获得持续集成测试的邮件,该邮件会检查所有已实现和未实现的代码,如果任何已实现的代码失败或任何未实现的代码成功,则将其发送。两者都是不希望的结果。

只是编写一个[ignore]测试将无法完成这项工作。这两个断言都不会停止第一个断言失败,而不会在测试中运行其他测试行。

现在,如何实现呢?我认为这需要对测试进行更高级的组织。并且需要其他一些机制才能断言达到这些目标。

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

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

尽管如此,如果不是在NYI部门中所有测试都失败的情况下,通过邮件发送的CI构建仍然不容易且直接。


0

您为什么Assert.Fail要说应该抛出异常?那是没有必要的。为什么不只使用ExpectedException属性?


我认为您误解了问题。我没想到会有例外。我将测试用作以后要编写的测试的占位符。我还没有测试任何东西,所以我提醒您测试失败。
Mendelt

0

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

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

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

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

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

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

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

因此,我们很快就会失败。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.