让我的 Junit 测试与真实数据库交互是不好的做法吗?

Posted

技术标签:

【中文标题】让我的 Junit 测试与真实数据库交互是不好的做法吗?【英文标题】:Is it bad practice to allow my Junit tests to interact with a real DB? 【发布时间】:2019-01-29 11:12:22 【问题描述】:

我正在构建一个基本的 HTTP API 和一些操作,如 POST /users 在数据库中创建一个新的用户记录。

我知道我可以模拟这些调用,但在某种程度上我想知道让我的 Junit 测试针对真实(测试)数据库运行是否更容易?这是一个不好的做法吗?是否应该只针对真实数据库运行集成测试?

我正在使用flyway 来维护我的测试架构和构建的 maven,因此我可以让它在每次构建时使用正确的架构重新创建测试数据库。但我也担心我需要一些额外的开销来维护/清理每次测试之间的数据库状态,我不确定是否有一个好的方法来做到这一点。

【问题讨论】:

单元测试应该在受控环境中测试您的单元(您的代码)。所以使用模拟比使用真正的数据库更好 严格来说,如果你去外部数据库,它不再是一个单元测试。这并不意味着它在某些(罕见)情况下没有用。 为什么不想使用内存中(即H2)? 您的单元测试不能依赖于外部系统,因为如果您的数据库出现问题,即使您的代码可能正常工作,您的测试也会失败。 【参考方案1】:

通常情况下,这取决于。

在具有大量 JUnit 测试的大型项目中,性能开销可能是一个重点。此外,在数据库中设置测试数据所需的工作时间以及不干扰其他测试的测试数据的测试所需的概念,而并行执行 JUnit 测试是仅针对数据库进行测试的一个非常大的论据如果需要,否则将其嘲笑。

在小型项目中,这个问题可能更容易处理,因此您可以始终使用数据库,但我个人不会这样做,即使是在小型项目中。

【讨论】:

【参考方案2】:

单元测试用于测试单个代码单元。这意味着您通过编写仅测试方法的内容来编写单元测试。如果存在外部依赖项,那么您模拟它们而不是实际调用和使用这些依赖项。

因此,如果您编写代码并且它与真实数据库交互,那么它就不是单元测试。比如说,由于某种原因,您对 db 的调用失败,那么单元测试也会失败。单元测试的成功或失败不应取决于您的情况下的外部依赖项,例如 db 。您必须假设 db 调用成功,然后使用一些模拟框架 (Mockito) 对数据进行硬编码,然后使用该数据测试您的方法。

【讨论】:

谢谢!关于"if you write code and it interacts with the real database," 的问题 - 这是否意味着编写与 H2 之类的假(内存)数据库交互的代码仍然是合理的?我在这篇推荐中看到了其他一些人。我问的原因是它似乎与 any 数据库交互使它不是单元测试。这只是对单元测试定义的分裂吗?还是通常会找到针对内存数据库运行的单元测试? 恕我直言,当您可以模拟数据而不是使用内存数据库时,实际上并不需要。这将是开销,因为单元测试应该只关心测试功能而不关心外部依赖。您可以使用内存数据库,但我认为它不会为易于使用的模拟框架增加任何实际价值。【参考方案3】:

现代开发实践建议每个开发人员经常运行全套单元测试。单元测试应该是可靠的(如果代码没问题,应该不会失败)使用外部数据库可能会干扰那些 desiradata。

如果数据库是共享的,不同开发人员同时运行测试套件可能会相互干扰。 为每个测试设置和拆除数据库通常很昂贵,因此会使测试太慢而无法频繁执行。

但是,使用真实数据库进行集成测试是可以的。如果您使用内存数据库而不是完全真实的数据库,那么即使为每个集成测试设置和拆除数据库也可以很快。

【讨论】:

【参考方案4】:

正如其他几个答案所建议的那样,您应该创建单元测试来测试小段代码并模拟所有外部依赖项。

但有时(很多时候)值得测试整个功能。尤其是当您使用某种框架(如 Spring)时。或者你使用了很多注释。当您的类或方法上有注释时,这些注释的效果通常无法通过单元测试进行测试。您需要在测试期间运行整个框架,以确保它按预期工作。

在我们当前的项目中,集成测试几乎与单元测试一样多。我们使用 H2 in-memory DB 进行这些测试,这样我们可以避免由于连接问题而导致的失败,而且 Spring 的测试包可以将多个集成测试收集并运行到同一个测试上下文中,因此它只需要构建一次上下文用于多个测试,并且以这种方式运行这些测试并不太昂贵。

您还可以为项目的不同部分(具有不同的设置和数据库内容)创建单独的测试上下文,这样在不同上下文下运行的测试就不会相互干扰。

不要害怕使用大量集成测试。无论如何你都需要一些,如果你已经有一个测试上下文,那么在同一个上下文中添加更多测试并不是什么大问题。

还有很多情况需要花费大量精力才能用单元测试来覆盖(或者根本无法完全覆盖),但可以简单地通过集成测试来覆盖。

个人经历: 当我们从 Spring Boot 切换到 Spring Boot 2 时,我们的大量集成测试非常有用。

回到原来的问题: 单元测试不应该连接到真实的数据库,但可以随意使用更多的集成测试。 (使用内存数据库)

【讨论】:

【参考方案5】:

一种流行的选择是使用内存数据库来运行测试。这使得测试例如存储库方法和涉及数据库调用的业务逻辑变得容易。

选择“真实”数据库时,请确保每个开发人员都有自己的测试数据库以避免冲突。使用真实数据库的优势在于,这可以防止由于内存和真实数据库之间的行为略有不同而可能出现的问题。但是,在针对真实数据库运行大型测试套件时,测试执行性能可能会成为问题。

某些数据库可以以某种方式嵌入,甚至无需安装数据库即可执行测试。例如,有一个 SO thread 关于在 Spring Boot 测试中启动嵌入式 Postgres。

【讨论】:

以上是关于让我的 Junit 测试与真实数据库交互是不好的做法吗?的主要内容,如果未能解决你的问题,请参考以下文章

这是使用JUnit的服务方法的真实测试代码吗?

如何使用 JUnit 测试异步进程

Spring Hibernate H2 Junit 测试 - 如何在启动时加载模式

Junit单元测试

多个进程之间的单元测试交互

为啥我的测试一起运行时通过,但单独失败?