如何在NUnit中基于另一个测试来忽略一个测试?

13

我正在为数据库操作编写一些 NUnit 测试。显然,如果 Add() 失败,则 Get() 也将失败。但是,当 Add()Get() 都失败时,看起来会产生两个问题而不是一个。

有没有一种方法可以指定测试运行的“顺序”,即如果第一个测试失败,则忽略后续测试?

同样,在单元测试类中是否有一种排序方式?例如,我想先运行基本数据库操作的测试,然后再运行从 UI 回传数据的测试。

注意: 这与测试之间存在依赖关系有点不同,更像是确保某些东西在运行一堆测试之前首先能工作。例如,如果您无法首先连接到数据库,则运行大量数据库操作是浪费时间的。

编辑: 看起来有些人错过了重点。我做的不是这个:

[Test]
public void AddTest()
{
    db.Add(someData);
}

[Test]
public void GetTest()
{
    db.Get(someData);
    Assert.That(data was retrieved successfully);
}

相反,我正在这样做:

[Test]
public void AddTest()
{
    db.Add(someData);
}

[Test]
public void GetTest()
{
    // need some way here to ensure that db.Add() can actually be performed successfully
    db.Add(someData);
    db.Get(somedata);
    Assert.That(data was retrieved successfully);
}

换句话说,我想确保能够首先添加数据,然后再测试是否可以检索数据。人们认为我在使用第一次测试的数据来通过第二次测试,但实际情况并非如此。我正在尝试确保在尝试依赖于它的另一个操作之前,某个操作是可行的

正如我已经提到的,您需要确保在运行数据库操作之前可以连接到数据库。或在执行文件操作之前可以打开文件。或在测试 API 调用之前连接到服务器。或者...你懂的。


1
被接受的答案是错误的!请查看评论。 - Graviton
7个回答

19

NUnit支持使用"Assume.That"语法来验证设置。这是Theory的一部分(感谢clairestreb)。在NUnit.Framework命名空间中有一个Assume类。引用文档如下:

/// Provides static methods to express the assumptions
/// that must be met for a test to give a meaningful
/// result. If an assumption is not met, the test
/// should produce an inconclusive result.

所以在上下文中:
public void TestGet() {
    MyList sut = new MyList()
    Object expecting = new Object();
    sut.Put(expecting);
    Assume.That(sut.size(), Is(1));
    Assert.That(sut.Get(), Is(expecting));
}

2
这应该是被接受的答案,声明依赖项的方式很好,并且“不确定”恰好是正确的结果,在大多数测试框架中最终会呈现橙色:意味着还需要完成某些工作。 - Abel
1
7年过去了,但我仍然不断回到这篇文章,因此这里是文档链接:http://nunit.org/index.php?p=theory&r=2.5 - clairestreb

3

测试永远不应该彼此依赖。你刚刚发现了原因所在。依赖于彼此的测试本质上是脆弱的。如果你需要数据库中的数据用于Get()测试,应该在设置步骤中把它放在那里。


除非它是脆弱的。你没有抓住重点。在进行测试之前,您需要确保某些内容可用。在我的情况下,我需要确保Add()有效,因为如果没有它,Get()将无法获取任何数据。无论它是否在设置中都无所谓,因为我在我的Get()测试中使用了Add(),但是在运行测试之前,我需要确保Add()实际起作用。 - Daniel T.
4
这就是模拟和存根的用途。在Get()的测试中,您应该能够提供虚拟数据以让Get()正常工作,从而可以在不运行Add()的情况下进行测试。这可能意味着传递一个Add()的子类,该子类不执行任何可能导致其失败的操作。关键在于,单元测试应仅测试所涉及的单元。应该有一种方法可以测试Get()而不测试Add()。如果您获得所请求的修复,如果Add()出现问题,您将无法知道Get()是否有效。这意味着您无法获得系统的可靠快照。 - jcdyer
@Daniel T. 你没有理解重点。测试应该始终相互独立。这意味着在 Get() 的测试中使用 Add() 不是一个好主意。如果 Add() 出现问题,Get() 的测试也会出现问题。你的测试不是隔离的。谁说你需要使用 Add() 来准备你的数据库以进行 Get()... - EricSchaefer
@EricSchaefer:你还有什么其他方法可以将数据存入数据库呢?当然,你可以模拟和存根所有内容,但此时你已经不再测试数据库了。我所要求的正是解决你所描述情况的方法:一种在Add()失败时不运行Get()的方式。 - Daniel T.
2
@EricSchaefer:这个(测试应该是独立的)在单元测试方面是正确的。Nunit也可以用于更高级别的测试,这些测试可以是依赖的。此外,在测试驱动开发中,当基本功能失败时,您可能会决定不运行一些复杂的测试。 - PPC

2
我认为问题在于您正在使用NUnit来运行不同于NUnit的单元测试类型。
基本上,您希望AddTest在GetTest之前运行,并且如果AddTest失败,您希望NUnit停止执行测试。
问题是这与单元测试相悖 - 测试应该是完全独立的并以任何顺序运行。
标准的单元测试概念是,如果您在“添加”功能周围有一个测试,则可以在“获取”测试中使用“添加”功能而不必担心“添加”是否在“获取”测试中起作用。 您知道“添加”有效-您对其进行了测试。
“FIRST”原则(http://agileinaflash.blogspot.com/2009/02/first.html)描述了单元测试应该如何行事。 您想编写的测试违反了“I”(隔离)和“R”(可重复)两个原则。
如果你担心在两个测试之间数据库连接会中断,我建议你在测试过程中不要连接实际的数据库,而是使用某种数据接口。对于测试,你应该使用一个模拟接口。如果测试的目的是为了检测数据库连接,那么你可能正在使用错误的工具 - 这不是真正的单元测试。

你说的很有道理,但我仍然不太明白如何将 Get()Add() 分离。在我看来,这就像是一个空盒子。在你测试是否能取出东西之前,必须能够把东西放进盒子里。 - Daniel T.
它们非常隔离。如果您想要“Get()”,显然需要在同一测试(或设置中)中首先进行“Put()”。隔离是因为你的两个测试完全测试不同的事情-你的第一个测试“Put()”,第二个测试“Get()”。如果“Put()”失败,它们都会失败-但这没关系。没有单元测试规则说系统的一个区域故障时应该恰好中断一个测试-它只需要至少中断一个测试,希望其中之一特别测试该区域。 - Matt Poush
我和一位同事交谈,他说虽然理论上防止级联失败很好,但实际上很少有级联失败会真正妨碍任何事情的情况。换句话说,如果你有200多个测试,而用于检查是否可以从数据库中检索数据的测试失败了,那么忽略其余的测试将是很好的选择,因为它们也将自动失败。但由于测试通常运行得很快,并且应该根据你正在测试的层进行分组,所以很容易追踪问题。 - Daniel T.

1

我认为这是不可能的,除非进行定制开发。

无论如何,根据你所描述的测试类设计,将会使得测试代码非常脆弱。


我不是先运行“Add()”测试将数据添加到数据库中,然后运行“Get()”测试来获取数据。相反,我想确保“Add()”首先起作用,然后再运行“Get()”测试,就像说“好的,我能把数据添加到数据库吗?可以,现在添加这个测试数据并查看是否可以检索它。” - Daniel T.

0

MbUnit 似乎有一个 DependsOnAttribute,可以让您做想做的事情。

如果其他测试夹具或测试方法失败,则此测试将不会运行。此外,依赖性强制使此测试在其所依赖的测试之后运行。

不过我对 NUnit 一无所知。


谢谢,这正是我想要的。现在还早,如果我想的话,我可以切换到MbUnit,所以我会研究一下它。 - Daniel T.
答案中的 MbUnit 链接似乎已过时。新链接为 https://code.google.com/archive/p/mb-unit/ | https://github.com/Gallio/mbunit-v3。 - Pang

0

你不能假设测试夹具执行的任何顺序,因此必须在测试类中检查任何先决条件。

将Add测试分离到一个测试类中,例如AddTests,并将Get测试放入另一个测试类中,例如GetTests类。

在GetTests类的[TestFixtureSetUp]方法中,检查您是否具有工作数据库访问权限(例如,Add是否有效),如果没有,则Assert.Ignore或Inconclusive,视情况而定。

当GetTests的先决条件未满足时,这将中止GetTests测试夹具,并跳过尝试运行其中包含的任何单元测试。(我认为!我是nUnit新手。)


-2
创建一个全局变量,并在测试 Get 时返回,除非 Add 将其设置为 true(在 Add 的最后一行执行此操作):
public boolean addFailed = false;
public void testAdd () {
    try {
        ... old test code ...
    } catch (Throwable t) { // Catch all errors
        addFailed = true;
        throw t; // Don't forget to rethrow
    }
}
public void testGet () {
    if (addFailed) return;
    ... old test code ...
}

谢谢。虽然不太优雅,但看起来 NUnit 没有本地支持此功能。 - Daniel T.
1
你想要的是一个黑客技巧,所以解决方案也只有一个 ;) - Aaron Digulla
1
@mlk:这是一个hack。然而,当你已经知道测试会失败并且将失败的单一原因埋在一堆继承错误下面时,运行测试就没有意义了。 - Aaron Digulla
1
@mlk:如果测试框架有一种方式可以说“测试被跳过,因为它无论如何都会失败”,那就太好了。Python就是这样做的(例如,在Linux上运行套件时,Mac/Windows测试会以这种方式运行)。 - Aaron Digulla
5
这个解决方案存在极大的风险,因为它依赖于运行的顺序;目前nunit是按照测试名称的顺序运行的,所以这是可以的。但如果未来Nunit更改了运行顺序,那么testGet在testAdd之前运行怎么办?这种情况下,testAdd将被运行,但testGet则不会被运行! - Graviton
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接