我熟悉TDD的基本原理,是:
- 编写测试,这些测试将因未实现而失败
- 编写基本实现以使测试通过
- 重构代码
但是,我对接口和实现的位置有点困惑。我正在业余时间创建一个 Spring Web 应用程序,而不是大发雷霆,我想了解如何更好地测试接口/实现,以我在这里创建的这个简单示例代码为例:
public class RunMe
{
public static void main(String[] args)
{
// Using a dummy service now, but would have a real implementation later (fetch from DB etc.)
UserService userService = new DummyUserService();
System.out.println(userService.getUserById(1));
}
}
interface UserService
{
public String getUserById(Integer id);
}
class DummyUserService implements UserService
{
@Override
public String getUserById(Integer id)
{
return "James";
}
}
我已经创建了UserService
接口,最终将有一个真正的实现来查询数据库,但是为了使应用程序启动,我替换了一个DummyUserService
实现,它现在只返回一些静态数据。
问题 : 如何实现上述测试策略?
我可以创建一个名为 DummyUserServiceTest
的测试类并测试当我调用 getUserById()
时它会返回 James
,如果不是浪费时间(?
随后,我还可以创建一个测试类RealUserService
,用于测试从数据库中返回用户名getUserById()
。这是让我有点困惑的部分,这样做,这是否本质上没有超越单元测试的边界,而变得更像是一个集成测试(在数据库上命中(?
问题(改进了一点(:当使用虚拟/存根接口和实际实现时,哪些部分应该进行单元测试,哪些部分可以安全地不进行测试?
昨晚我花了几个小时在谷歌上搜索这个话题,主要找到了关于TDD是什么的教程,或者如何使用JUnit的示例,但没有建议实际应该测试什么。不过,完全有可能,我没有足够努力地搜索或没有寻找正确的东西......
不要测试虚拟实现:它们不会在生产中使用。测试它们没有真正的意义。
如果真正的 UserService 实现只执行任何其他操作,只需转到数据库并按其 ID 获取用户名,则测试应测试它是否执行此操作并正确执行。如果需要,可以将其称为集成测试,但它仍然是一个应该编写和自动化的测试。
通常的策略是在测试的@Before注释方法中使用最少的测试数据填充数据库,并让测试方法检查数据库中存在的 ID 是否返回相应的用户名。
我建议你先读这本书:Steve Freemand 和 Nat Pryce 的《Growing Object-Oriented Software Guided by Tests》。它回答了您的问题和许多其他与TDD相关的问题。
在您的特定情况下,您应该使用 DB 适配器配置您的RealUserService
,这将进行真正的数据库查询。服务本身将提供服务,而不是数据持久性。阅读这本书,它会有很大帮助:)
JB 的答案很好,我想我会抛弃我使用过的另一种技术。
在开发原始测试时,首先不要费心去掉UserService
。事实上,继续写真实的东西。按照肯特贝克的 3 条规则继续。
1(让它工作。 2(做对。 3(让它快速。
您的代码将具有测试,然后验证按 id 查找是否有效。正如 JB 所说,此时您的测试将被视为集成测试。一旦它们通过,我们就成功地完成了第 1 步。现在,看看设计。对吗?调整任何设计气味,并从列表中检查步骤 2。
对于第 3 步,我们需要快速进行此测试。我们都知道,集成测试在所有事务管理和数据库设置中都很慢且容易出错。一旦我们知道代码有效,我通常不会打扰集成测试。此时,您可以引入虚拟服务,从而有效地将集成测试转换为单元测试。现在它不会以任何方式接触数据库,我们可以从列表中检查步骤 3,因为此测试现在很快。
那么,这种方法有什么问题呢?好吧,很多人会说我仍然需要对数据库支持的UserService
进行测试.我通常不会在我的项目中保留集成测试。我的观点是,这些类型的测试缓慢、脆弱,并且在大多数项目中没有捕获足够的逻辑错误来收回成本。
希望对您有所帮助!
布兰登