当项目做大的时候,分清楚单元测试和集成测试是必要的。在单元测试的阶段,分清楚stub和mock的概念,有助于我们使用最合适的工具,最小的代价实现我们的测试目标和外部依赖之间的关联。
从概念上说stub是外部依赖的一个简单实现,它暴露出测试所需要的方法,以及需要验证的状态。单元测试的时候,它的这些方法会被调用到,而其内部状态也会发生改变,我们就可以对这个内部状态进行验证。它的理论上,如果我们测试目标如我们期望的那样工作,那么依赖服务的状态必然如我们期望的那样改变。
相比较而言,mock关心的是调用的行为,它也会为一个接口创建一个对象(无须编码),这个对象它只关心这个对象的输入参数和返回值。mock的可以设定期望的调用行为,并记录实际的调用行为,以便于最后进行验证。它的理论是,如果依赖对象输入输出满足期望,那么我们就能测试我们的目标是否如我们期望的那样工作。与stub不同在于,mock时,我们最终验证的是目标对象的状态,而不是依赖对象的状态。
举个例子,假设我们有一个Service是计算银行的贷款利息的,AccountService,它有一个方法
double calculate(total, length,credit_level,ID)
在这个方法内部,它需要调用BankInfoService来获取利率,资质等信息,进行复杂的运算,并最终为申请人建立一个贷款帐户。
为了测试AccountService,我们可以实现BankInfoService的stub,并最终验证贷款帐户是否建立。
另一方面我们也可以实现一个mock,并为所有的方法设定输入,输出的期望(所谓record),然后进行调用(replay),最后我们可以验证两个方面,一是是否所有的方法都被调用,二是最终结果(调用BankInfoService的参数)是否符合期望。
目前实现stub需要自己编码实现,mock可以有软件支持,mockito用起来不错。实际应用时stub和mock可能会互相转换。如果mock的对象输入参数是一个比较大集合,并且输入输出的映射并不是一个简单的映射关系,那么我们可能需要写很多个mock的expected return。此时用stub可能更合适些。相反的方向,如果stub的方法数不多,并且输入输出都很简单,那么mock方法也可以考虑。当然stub如果有自己的内部状态,mock就不容易取代了。