如何避免重新实现功能以避免双重测试

时间:2017-10-25 10:04:50

标签: unit-testing tdd

我们用TDD开发了一个组件。该组件具有持久性后端。 我们选择使用SQLite实现它。 后端可以Save()和Load()一组项目。 因此,为了编写测试Load()函数,我们在测试中使用SQL代码填充了SQLite数据库,因此我们不使用Save()函数,因此我们不会在Load()测试中对其进行测试。 所以基本上我们从我们的组件中的测试中重新实现了代码。 就像我说的那样,我们这样做只是为了测试孤立的函数。

这有一种(非常)酸味。

  • 重新实施生产代码
  • 测试取决于实施细节SQLite

我的问题是,为避免一起测试我们的Load()和Save()函数是否值得这样做?

我们的stackoverflow成员是否使用过其他方法?

3 个答案:

答案 0 :(得分:1)

归根结底,总有一个权衡取舍。我的个人想法......

对于单元测试,可能值得将这些作为两个单独的测试用例(单一责任)。这使得测试更易于阅读和维护,并且还有助于隔离测试失败,即您将知道在加载或保存期间是否失败。 虽然通过测试预期的行为而不是单个方法,但这可能提供了不同的视角

它是一个单元测试,然后避免实际写入数据库,也许看看使用内存中的版本。

在集成或系统测试中使用真实数据库 - 这些可以是更高级别,您可以将读取和写入测试组合到单个功能或方案中(测试金字塔)。

如果您的测试代码中存在重复,请使用测试数据构建器来删除它(DRY原则),而不是可以重新使用它来设置测试。

还有一点,想想你的测试实际测试的内容,即听起来你可能正在测试SQLite是否可以读写数据,而你可能需要测试出来的数据是否正确,因此使用Unit /集成测试组合,你可以在单元测试级别从数据库中提取出来 - 所以测试预期的行为而不是实现。

答案 1 :(得分:1)

  

所以基本上我们从我们的测试中重新实现了代码   也在我们的组件中

从我的观点来看,这不是真的。

虽然“持久性后端”必须是通用的(即可以为每个客户保存数据),但测试中的sql代码不能是通用的。测试代码中有一个具体的例子。

testLoadExistingCustomer() {
  sql.exec("delete from customers where id=1");

  sql.exec("Insert into Customers(id,name) values(1, 'Smith')");

  Customer cust = repository.loadByid(1);
  assert(.....)
}

testSaveNewCustomer() {
  sql.exec("delete from customers where id=1");

  Customer cust = new Customer(1, "Smith");
  repository.save(customer);

  int count = sql.exec("select count(*) from Customers where id=1 and name='Smith'");

  assert(1,count)
}

[评论后更新]

我的回答是关于代码重复的原始问题的白盒测试。这是黑盒测试的另一种选择

我通常通过猜测两个反函数save + load得到相同的内容来测试我的持久性/序列化。由于我还实现了一个toString方法来调试这样的测试,所以测试成为:

testSavePlusLoadCustomer() {
  Customer cust = new Customer(1, "Smith");
  repository.save(customer);

  Customer loadedCustomer = repository.loadByid(1);
  assertEquals(cust.toString(), loadedCustomer.toString());
}

这不干净,因为这个测试(像许多其他整合测试一样)有几个失败或误报的原因(保存或加载不正常,testdata缺少一个属性集,toString()缺少一个属性输出)但它作为一个回归测试是好的(使之前工作的代码没有被破坏)

答案 2 :(得分:1)

  

我的问题是,为避免一起测试我们的Load()和Save()函数是否值得这样做?

可能不是。

分离负载和保存测试有不同的,更便宜的方法。

最常见的是将测试的重点从数据更改为业务流程。因此,您将使用测试双重代表您的数据存储,当单元测试加载时,您将注意正确的消息是否被发送到测试双,而不必担心存储时应发送的消息数据。同样,当单元测试存储时,您将关注是否发送这些消息,忽略加载消息。

这是使用端口和适配器设计时的常用方法。你安排你的代码,以便难以测试的部分“如此简单以至于显然没有缺陷”,然后用 易于测试的测试双重替换该部分。

稍后,当您进行集成或端到端测试时,您可以连接到真正的数据存储,并确保您真正可以读出您正在编写的数据。

但是,如果这没有意义,或者仍然过于昂贵,或者在其他地方需要缺乏权衡,那么就不要这样做。