我一直在进行单元测试。在进行TDD时,我对设计和实现细节之间的界限感到有些困惑。
例如,我有两个接口,即服务和适配器,用于处理员工信息(添加,获取,删除......)
public interface IEmployeeService
{
Employee GetEmployeeById(int id)
}
public interface IEmployeeAdapter
{
private IEmployeeService _service
Employee GetEmployeeById(int id)
}
通过设计,服务从数据库,文件系统或Web服务等存储中读取数据,适配器使用服务来获取某些信息。
在我开始为适配器编写单元测试之前,这种设计看起来很好。
问题是我需要知道adapter.GetEmployeeById(id)
是否会调用service.GetEmployeeById(id)
(或其他方法)来确定我是否需要在测试方法中模拟服务。这让我觉得在编写单元测试时我会考虑实现细节。有什么不对吗?
答案 0 :(得分:2)
单元测试是white-box测试,因此您完全了解被测系统内部的情况。使用这些信息来帮助确定要嘲笑的内容没有任何问题。是的,这是一个实施细节,它可以让你的测试变得脆弱"在您的底层实现发生变化时需要更改它。但是在这样的情况下,我想要知道,当我调用adapter.foo()时,它会调用underlyingService.foo(),而mocks非常适合这个。
答案 1 :(得分:2)
我可以建议的最好的经验法则是:尝试仅在合同的一部分使用行为设置/验证。如果您测试行为但实际上您感兴趣的是状态,那么测试往往会更频繁地破坏,因为行为实际上是一个实现细节。
在您的示例中,如果没有人关心服务和适配器之间的确切边界,请随意在适配器类上使用状态验证。但是,如果您的适配器应该将特定的消息调用模式转换为另一组明确定义的消息,那么您可能希望使用行为验证。换句话说,如果adapter.GetEmployeeById(id)
需要转换为service.GetEmployeeById(id)
,那么它不是实现细节。