我已经做了一段时间的单元测试。在做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)
(或其他方法)来确定我是否需要在测试方法中模拟服务。这让我感觉我在编写单元测试时正在考虑实现细节。有什么问题吗?
单元测试是白盒测试,因此您完全了解被测系统内部发生的情况。 使用这些信息来帮助确定要嘲笑的内容并没有错。 是的,这是一个实现细节,它会使你的测试变得"脆弱",因为当你的基础实现发生变化时需要改变它。 但是在这种情况下,我想知道当我调用adapter.foo()时,它会调用underlyingService.foo(),而模拟非常适合此。
我能建议的最佳经验法则是:仅在行为设置/验证是合同的一部分时才尝试使用行为设置/验证。如果你测试了行为,但你真正感兴趣的是状态,测试往往会更频繁地中断,因为行为实际上是一个实现细节。
在您的示例中,如果没有人关心服务和适配器之间的确切边界,请随意对适配器类使用状态验证。但是,如果您的适配器应该将特定的消息调用模式转换为另一组明确定义的消息,则可能需要改用行为验证。换句话说,如果adapter.GetEmployeeById(id)
需要转换为service.GetEmployeeById(id)
那么它就不是实现细节。