单元测试如何测试任何东西?

How is unit testing testing anything?

我不明白我是如何用单元测试来测试任何东西的。

假设我正在测试我的存储库 class 是否可以正确地从数据库中检索值。执行此操作的正确方法是实际调用真实数据库并检索和检查这些值。

但是单元测试背后的想法是它应该独立完成,而连接到 运行 数据库并不是孤立的。所以通常做的是mock或者stub数据库。

但是,为什么在具有硬编码数据和硬编码 return 值的假数据库上进行测试甚至可以测试任何东西?这似乎是同义反复和浪费时间。

还是我不明白如何正确进行单元测试?

偶数单元测试数据库调用吗?

我觉得你的担心是有道理的。对我来说,TDD 更像是一种进化设计实践,而不是单元测试实践,但我将把它留到另一个讨论中。

在您的示例中,我们真正测试的是您个人 类 中包含的逻辑是否合理。通过对来自数据库的数据进行存根,您拥有一个可控场景,您可以确保您的代码适用于该特定场景。这使得确保所有数据场景的完整测试覆盖变得更加容易。你是对的,这确实没有测试整个系统端到端,但重点是降低总体测试维护成本并实现更快的反馈。

我的方法是在单元测试级别模拟大多数协作者,然后在集成测试级别编写验收测试,使用真实数据验证您的系统。因为使用模拟数据的单元测试允许您测试各种数据场景,所以您只需使用集成测试测试其中的一些场景,就可以确信您的代码将按预期执行。

I don't understand how I'm testing anything with unit testing.

简短回答:您正在测试逻辑,并忽略了副作用

您没有测试一切;但你正在测试一些东西。

此外,如果您牢记您没有真正测试带有副作用的代码,那么您就有动力安排您的代码,以便实际依赖的部分副作用很小。大块其实不在乎数据从哪里来,要那些容易测试。

所以“某事”可以是“大多数事情”。

阻抗问题 -- 如果您的测试替身不能充分模拟生产原件,那么您的某些测试结果将不准确。

my philosophy is to test as little as possible to reach a given level of confidence

Kent Beck, 2008

想象“尽可能少”的一种方法是从成本的角度来考虑——我们的目标是给定的置信水平,因此我们希望使用便宜的设备来获得尽可能多的置信度测试,然后用更昂贵的技术弥补差异。

Cory Benfield 的演讲 Building Protocol Libraries the Right Way 描述了我们在此讨论的那种分离的示例。如何解析 HTTP 消息的逻辑与读取字节的问题是分开的。如果你让复杂的部分容易测试,而难测试的部分简单到不会失败,你成功的机会就很大。

您可以单独针对实际数据库测试您的代码。只需为每个测试创建新的数据库实例,或者一个接一个地同步执行测试并在下一次测试之前清理数据库。

但是使用实际的数据库会使你的测试变慢,这会减慢你的工作,因为你想要对你正在做的事情有快速的反馈。

不要测试每个 class - 测试主要功能逻辑,它可以使用许多不同的 classes 和 mock/stub 唯一的依赖项,这会使测试变慢。

找到您的应用程序边界并在不模拟的情况下测试它们之间的逻辑。
例如在简单的网络中 api 应用边界可以是:
- 控制器动作 -> 请求(输入)
- 控制器动作 -> 响应(输出)
- 数据库 -> 收到请求的副作用。

假设我们生活在完美的世界中,新的数据库和网络服务器设置只需几毫秒。然后您将测试应用程序的整个管道:
1. 为测试配置数据库
2.向网络api服务器
发送请求 3.断言响应包含预期数据
4. 断言数据库状态已按预期更改

但在当今世界,您的边界将是控制器操作和抽象的数据库访问点。这使您的测试如下所示:
1.配置模拟数据库访问点(存储库)
2. 使用给定参数调用控制器动作
3.断言该操作returns预期结果
4. 可能断言模拟存储库收到了预期的更新参数。

如果您的应用程序没有逻辑,仅 read/update 来自数据库的数据 - 使用实际数据库进行测试,或者如果您的数据库框架允许,则使用内存中的数据库。