强制执行单元测试的执行命令是不好的做法吗?


84

我正在为一个包含多个子模块的项目编写测试。我编写的每个测试用例彼此独立运行,并且清除测试之间的所有数据。

尽管测试是独立运行的,但由于某些情况需要多个子模块,因此我正在考虑执行执行顺序。例如,一个子模块正在生成数据,而另一个正在对数据运行查询。如果生成数据的子模块包含错误,则即使子模块本身工作正常,查询子模块的测试也将失败。

我无法使用虚拟数据,因为我正在测试的主要功能是与黑匣子远程服务器的连接,该服务器仅从第一个子模块获取数据。

在这种情况下,可以强制执行测试的执行顺序还是不好的做法?我感觉此设置中有异味,但找不到更好的方法。

编辑:问题来自于如何构建测试,其中一个测试是另一测试的设置?因为“先前”测试不是设置,而是测试执行设置的代码。



123
如果要测试到远程服务器的连接,那么按照定义,它们不是单元测试。
Telastyn '18 -4-3

9
第一个答案在这里让我感到困惑,因为您在标题中说“这是不好的做法吗?” 在您的问题摘要中,您写道“可以吗?” 任何回答是或否的人都会混淆其中之一!
Liath

8
听起来您正在创建一组集成测试。即使是这样,一个测试也不应依赖其他测试。
低飞鹈鹕

17
如果顺序很重要,那么您可能做错了。
马克·罗杰斯

Answers:


236

我无法使用虚拟数据,因为我正在测试的主要功能是与黑匣子远程服务器的连接,该服务器仅从第一个子模块获取数据。

这是我的关键部分。您可以谈论“单元测试”,它们“彼此独立运行”,但是听起来它们都依赖于此远程服务器,并且依赖于“第一个子模块”。因此,一切听起来都紧密耦合,并取决于外部状态。因此,您实际上是在编写集成测试。按特定顺序运行这些测试是很正常的,因为它们高度依赖于外部因素。有序的测试运行,如果出现问题,可以选择提前退出测试,这对于集成测试是完全可以接受的。

但是,也有必要重新审视应用程序的结构。能够模拟出第一个子模块和外部服务器,可能会允许您为所有其他子模块编写真实的单元测试。


14
更不用说某些测试必须专门检查当远程服务器不可用时是否发生了预期的行为。
亚历山大

2
或者,也许您实际上是打算编写集成测试,因此模拟数据并不能达到您要通过这些测试要实现的目标。
Guy Schalnat

10
问题很可能是Junit名称中包含“ unit”的意思。
托尔比约恩Ravn的安徒生

7
@ThorbjørnRavnAndersen确实如此。人们自然而然地编写集成测试,而不是编写单元测试,因为集成测试比“真实的”单元测试更有用,编写难度也更小。但是,由于流行的测试框架是以单元测试的概念命名的,因此该术语已被采用,并且在现代术语中意为“任何自动化测试”。
梅森惠勒

1
@MasonWheeler甚至由非技术经理选拔来表示验收测试。
TKK

32

是的,这是一个坏习惯。

通常,单元测试旨在测试单个代码单元(例如,基于已知状态的单个功能)。

当您要测试可能在野外发生的一系列事件时,您需要其他测试样式,例如集成测试。如果您依赖第三方服务,则更是如此。

为了对此类事情进行单元测试,您需要找出一种注入伪数据的方法,例如,实现一个数据服务接口,该接口镜像Web请求,但从本地伪数据文件返回已知数据。


8
同意 我认为这种混淆源于以下事实:许多人都认为集成测试必须是端到端的,并使用“单元测试”来指代仅测试一层的任何测试。
autophage

8
@autophage:绝对同意。其实我同意这么多,我经常发现自己陷入同样的陷阱,尽管同意,这是一个陷阱 😂
亮度种族在轨道

16

您建议的强制执行顺序仅在第一次失败后也中止测试运行时才有意义。

在第一次失败时中止测试运行意味着每次测试只能发现一个问题,并且在解决所有先前的问题之前,它不会发现新问题。如果运行的第一个测试发现需要花费一个月才能解决的问题,则在该月中实际上将不执行任何测试。

如果您不中止第一次失败的测试运行,那么强制执行命令不会给您带来任何好处,因为无论如何,每个失败的测试都需要进行调查。即使只是为了确认查询子模块上的测试失败,也因为数据生成子模块上也已确定失败。

我能提供的最好建议是编写测试,以便易于识别依赖项故障何时导致测试失败。


7

您所指的气味是对测试使用了错误的约束和规则集。

单元测试经常与“自动测试”或“程序员的自动测试”混淆。

单元测试必须小巧,独立且快速。

某些人错误地将其理解为“程序员编写的自动化测试必须小而独立且快速”。但这只是意味着,如果您的测试不小,独立且快速,则它们不是单元测试,因此某些单元测试规则不应,不能或不能应用于您的测试。一个简单的例子:您应该在每次构建之后运行单元测试,对于速度不快的自动化测试,则不应这样做。

尽管您的测试不是单元测试,这意味着您可以跳过一个规则,并且允许测试之间有一些相互依存关系,但您还发现可能遗漏了其他规则,需要重新引入这些规则-这是另一个问题的范围。


6

如上所述,您所运行的似乎是一项集成测试,但是您声明:

例如,一个子模块正在生成数据,而另一个正在对数据运行查询。如果生成数据的子模块包含错误,则即使子模块本身工作正常,查询子模块的测试也将失败。

这可能是开始重构的好地方。对数据运行查询的模块不应依赖于第一个(数据生成)模块的具体实现。相反,最好注入一个包含获取该数据的方法的接口,然后可以将其模拟出来以测试查询。

例如

如果你有:

class Queries {

    int GetTheNumber() {
        var dataModule = new Submodule1();
        var data = dataModule.GetData();
        return ... run some query on data
    }
}

而是喜欢:

interface DataModule {
    Data GetData();
}


class Queries {

    IDataModule _dataModule;

    ctor(IDataModule dataModule) {
       _dataModule = dataModule;
    }

    int GetTheNumber() {
        var data = _dataModule.GetData();
        return ... run some query on data
    }
}

这消除了对数据源查询的依赖性,并允许您针对特定方案设置易于重复的单元测试。


6

其他答案提到排序测试是不好的(在大多数情况下是正确的),但是执行测试执行顺序有一个很好的理由:确保慢速测试(即集成测试)在更快的测试(测试)之后运行而不依赖其他外部资源)。这样可以确保您更快地执行更多测试,从而可以加快反馈循环。


2
我更倾向于调查为什么某个单元测试运行缓慢而不是强制执行命令。单元测试应该是快速的。
罗比·迪

OP说他无法在其中一些测试中使用虚拟数据。这意味着某种数据库命中率会减慢所有测试的速度(甚至包括一些自然而应该快速运行的真实单元测试)。如果他进行了其他不需要数据库命中的测试,则它们的运行速度将比需要磁盘或网络命中的任何东西快一个数量级。
Mike Holler

2
我认为你们俩都是对的。罗比是对的,因为单元测试应该小而又快速,并且与依赖关系隔离开,因此顺序无关紧要,而随机排序通常通过加强这种独立性来鼓励更好的设计。迈克(Mike)说的对,首先运行更快的测试对于集成测试非常非常好。就像上面的答案一样,部分问题是单元测试与集成测试的术语。
WillC

@MikeHoller然后,它们不是单元测试。关于什么是单元测试,确实应该没有混淆。
罗比·迪

@RobbieDee我只是在使用OP使用的术语。我知道这些不是真正的单元测试。如果您想争用术语,请使用OP提出。(因此,为什么我在之前的评论中用“真实的单元测试”进行了澄清”)
Mike Holler
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.