74

我在做 TDD 时经常使用 Assert.Fail。我通常一次只做一个测试,但是当我对以后想要实现的东西有想法时,我会快速编写一个空测试,其中测试方法的名称表明我想要作为待办事项列表来实现的内容。为了确保我不会忘记我在正文中放了一个 Assert.Fail() 。

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


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

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

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

@Mitch Wheat 这就是我想要的。似乎它被遗漏了以防止它以另一种方式被滥用,我滥用它。

4

14 回答 14

54

对于这种情况,我不调用 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 鼓励开发人员寻找和使用更明确的替代方案,并在必要时支持创建新断言。

于 2008-09-23T12:32:30.690 回答
17

它被故意遗漏了。这是 Brad Wilson 关于为什么没有 Assert.Fail() 的回复:

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

于 2008-09-23T12:32:17.493 回答
11

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

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

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

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

于 2008-09-23T12:41:53.273 回答
5

我使用 MbUnit 进行单元测试。他们可以选择忽略测试,这些测试在测试套件中显示为橙色(而不是绿色或红色)。也许 xUnit 有类似的东西,这意味着您甚至不必在方法中添加任何断言,因为它会以令人讨厌的不同颜色显示,因此很难错过?

编辑:

在 MbUnit 中是这样的:

[Test]
[Ignore]
public void YourTest()
{ } 
于 2008-09-23T12:30:57.853 回答
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 块更健壮。

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

于 2009-06-26T11:28:12.663 回答
3

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

话虽如此,我自己曾经使用过这种方法,尽管现在我发现这样做会导致我走上一条预先编写太多测试的道路,这以一种奇怪的方式就像根本不编写测试的相反问题:恕我直言,您最终做出有关设计的决定还为时过早。

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

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

于 2008-09-23T12:38:31.657 回答
2

大胆猜测:保留 Assert.Fail 旨在阻止您认为编写测试代码的好方法是一大堆意大利面条,导致在坏情况下出现 Assert.Fail。[编辑添加:其他人的答案大致证实了这一点,但引用了]

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

或者他们只是认为它是如此罕见且如此不正交以至于没有必要。

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

无论是失败,还是没有实现(引发链接器错误),或者是一个无法编译的宏,都可以根据您当前的偏好进行更改。例如,当你想运行一些已经完成的东西时,你想要一个失败。当你坐下来摆脱它们时,你可能想要一个编译错误。

于 2008-09-23T12:45:26.570 回答
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")
}
于 2008-09-23T14:01:00.790 回答
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 函数的结果如何,测试都将始终通过。同样,当且仅当它抛出异常时,测试才会失败。

于 2014-08-28T10:49:54.840 回答
0

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

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

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

于 2008-09-23T12:31:09.883 回答
0

MS Test 有Assert.Fail()但它也有Assert.Inconclusive()。我认为 Assert.Fail() 最合适的用途是,如果你有一些内联逻辑,放在断言中会很尴尬,尽管我什至想不出任何好的例子。在大多数情况下,如果测试框架支持 Assert.Fail() 以外的东西,那么就使用它。

于 2008-09-23T12:36:58.637 回答
0

你为什么Assert.Fail要说应该抛出异常?那是不必要的。为什么不直接使用ExpectedException属性?

于 2008-10-25T02:53:56.553 回答
0

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

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

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

现在,如果您进行前期 TDD,您希望执行所有测试,包括 NYI 部分。如果:1) 所有实现的东西都成功了 2) 所有 NYI 的东西都失败了,那么你的总测试运行的结果就通过了

毕竟,如果您的单元测试成功而没有实现,那将是单元测试的省略,不是吗?

您希望收到一封持续集成测试的邮件,检查所有已实现和未实现的代码,并在任何已实现的代码失败或任何未实现的代码成功时发送。两者都是不希望的结果。

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

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

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

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

尽管如此,如果 NYI 部门的所有测试都未通过邮件,那么 CI 构建并不容易和直截了当。

于 2017-10-07T12:06:00.853 回答
0

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

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

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

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

我们所做的是向单元测试项目添加一个配置值,这样当第一次请求数据库连接时,我们可以调用Assert.Fail("Database encrypted");

Assert.Fail() 全局作用,甚至在不同的库中。因此,这可以作为所有单元测试的包罗万象。

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

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

于 2020-05-14T17:06:16.237 回答