TDD:测试用例的适用性

TDD: suitability of a testcase

我开始使用 TDD 和 JUnit。查看教程和文档后,我有一些问题,如果我能得到一些最佳实践反馈,我会很高兴。

A) 我看到的所有示例都是针对具有某种 semantic/logic 的方法的。

输入 -> 逻辑 -> 输出

例如2 个数字 -> 相加 -> 结果

由于输入到输出的转换,测试用例检查逻辑。 我明白了,没关系。

如果没有这样的输入(或者对外部结果有很大的依赖性)怎么办?

例如

String getName (int id)
{
  // read the name of a staffmember out of the DB and return it
} 

我看不到真正的逻辑可以在 compile/deploy 时间检查上下文无关。

什么断言是有意义的,或者是一个没有测试公平的样本?

我认为只有对上下文独立输入的测试才有意义。外部数据库或网络请求的结果不是(我认为 - 你同意吗?)。

B)大家觉得"methods exist"和"methods with a testcase"的比例是多少?当然这取决于项目或主题,但我会对一些数字感兴趣。

首先,虽然大多数功能都可以进行测试,但并非所有功能都需要直接进行测试。有些通过调用代码的测试更好地测试。

其次,在处理依赖于状态的副作用或代码时,有一些方法可以创建测试特定场景所需的上下文。其中一种方法是使用 test doubles.

当然,我们需要对非纯函数的代码进行测试。您可以尽量减少非纯函数的代码量(例如,使用函数式编程),但如果不这样做,您的其余代码也需要进行测试。

最后,您所说的 "ratio",或者通常所说的 "test coverage",取决于您对测试套件的信心程度。最后,正是这种自信让你重构代码而不用担心破坏东西,这才是重点。

正如 deHaar 在他的评论中指出的那样,您需要涵盖许多边缘情况。测试数据库时,可以执行以下操作:

  • 您模拟数据库(在您的存储库的 Spring 项目中),并将其配置为 return / throw。然后在你的测试中你测试像这样的东西:givenNoCustomerInDB_thenNotFoundExceptionThrownIsWrappedToXXX()。在这里,您将测试调用数据库的服务方法是否捕获异常并相应地包装它。对于这种方法,您可能需要查找 Mockito,"de facto" Java 的模拟框架。
  • 另一种选择是在进行测试时使用内存数据库(例如 H2)。

请记住一件事:您有责任确保模拟(或 H2)的行为与您的真实数据库一样。 @Kraylog 建议将适配器写入 IO 设备的集成测试,以及确保模拟行为相同的合同测试。

在将测试对象与其通常环境隔离的测试中,您会经常看到 input -> logic -> output 模式,因为输入数据必须由环境提供,而测试 对象所经历的环境。

TDD 经常使用隔离测试;它们通常既快又并行,这意味着 运行 它们在设计阶段的机会成本很低。

String getName (int id)
{
    // read the name of a staffmember out of the DB and return it
} 

在这样的例子中,我们通常会被驱使到 "the" 数据库可配置的设计中,在我们的测试中,我们会提供预加载到正确状态的内存数据库。

// Copy input to database
// connect test subject to database
// invoke query, thereby retrieving the output

同样的图案,只是分割的方式不同。

许多情况下,我们可以在我们的设计中引入一些对数据库的抽象,并使该抽象而不是数据库成为配置的依赖项。因此,与其使用与数据库对话的抽象,我们可能有一个更简单的实现,它被硬编码为 return 一些值。

这种东西有时被称为 test double

// Use the input to initialize the test double
// connect test subject to test double
// invoke query, thereby retrieving the output

再次出现相同的模式,"logic" 的细节有所变化。

input -> logic -> outputlogic 不一定是您的生产代码。编写与外观集成的测试很常见,该外观协调测试对象及其(双重)依赖项之间的交互协议。

(测试,就像生产代码一样,有设计——现在投资于一个好的设计可能会在测试的整个生命周期内产生可观的利润)。