关于单元测试的一些问题
当我们Javaweb项目中编写单元测试的时候,通常会面临一个普遍的问题:需要测试的类会有很多依赖,而这些依赖的类或者对象又会有很多别的依赖,导致我们在写单元测试的时候几乎需要把完整的业务体系代码编写出来,而在单元测试中将这这些个依赖完整的构建出来是一件很困难的事情,通常这个时候,我们会想到把里面一些需要的依赖“Mock”出来。
Mock及使用Mockito
Mock,从单词层面来讲师仿制和模拟,在软件开发和测试中通常是指模拟对象。
简单地说就是对测试的类所依赖的其他类和对象,进行mock - 构建它们的一个假的对象,定义这些假对象上的行为,然后提供给被测试对象使用。被测试对象像使用真的对象一样使用它们。用这种方式,我们可以把测试的目标限定于被测试对象本身,就如同在被测试对象周围做了一个划断,形成了一个尽量小的被测试目标。
通过这样,我们可以在不编写大量依赖对象的前提下使用少量的代码即可完成整个业务流程的单元测试。具体的使用可以参考下面一个业务中的简单例子。
Mock的框架有很多,最为知名的一个是Mockito,这是一个开源项目,使用广泛。官网:http://site.mockito.org/。
使用实例
- 创建项目时引入单元测试包和mockito的包
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"><<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">dependency</span>></span>
<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"><<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">groupId</span>></span>org.mockito<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"></<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">groupId</span>></span>
<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"><<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">artifactId</span>></span>mockito-all<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"></<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">artifactId</span>></span>
<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"><<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">version</span>></span>2.0.2-beta<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"></<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">version</span>></span>
<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"><<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">scope</span>></span>test<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"></<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">scope</span>></span>
<span class="hljs-tag" style="box-sizing: border-box; color: navy; font-weight: 400;"></<span class="hljs-title" style="box-sizing: border-box; color: navy; font-weight: 400;">dependency</span>></span></code></pre>
- 新建一个账户对象类 Account
public class Account {
/*
* 账户ID
*/
private String accountId;
/**
* 收支
*/
private long balance;
/**
* 初始化账户ID和收支
* @param accountId
* @param initialBalance
*/
public Account(String accountId, long initialBalance){
this.accountId = accountId;
this.balance = initialBalance;
}
public void debit(long amount){
this.balance -= amount;
}
public void credit(long amount){
this.balance += amount;
}
public long getBalance(){
return this.balance;
}
}
- 创建一个账号管理接口,里面包含了一个查找账户对象的方法和一个更新账户信息的方法
/**
* 根据userId找到对应账号对象
* @param userId
* @return
*/
Account findAccountForUser(String userId);
/**
* 更新账户信息
* @param account
*/
void updateAccount(Account account);
}
- 创建账号管理接口的实现类MockAccountManager,并在其中新增一个账号管理的方法
public class MockAccountManager implements AccountManager {
private Hashtable accounts = new Hashtable();
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">/**
* 新增一个添加账户的方法
* <span class="hljs-doctag" style="box-sizing: border-box; color: #dd1144;">@param</span> userId
* <span class="hljs-doctag" style="box-sizing: border-box; color: #dd1144;">@param</span> account
*/</span>
<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">void</span> <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">addAccount</span><span class="hljs-params" style="box-sizing: border-box;">(String userId, Account account)</span> </span>{
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.accounts.put(userId, account);
}
<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> Account <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">findAccountForUser</span><span class="hljs-params" style="box-sizing: border-box;">(String userId)</span> </span>{
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">return</span> (Account) <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.accounts.get(userId);
}
<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">void</span> <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">updateAccount</span><span class="hljs-params" style="box-sizing: border-box;">(Account account)</span> </span>{
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">// do nothing</span>
}
}
- 创建一个服务类AccountService,通过该类来调用接口中的方法
public class AccountService {
private AccountManager accountManager;
// private MockAccountManager mockAccountManager;
<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">void</span> <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">setAccountManager</span><span class="hljs-params" style="box-sizing: border-box;">(MockAccountManager manager)</span> </span>{
accountManager = manager;
}
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">/**
*
* <span class="hljs-doctag" style="box-sizing: border-box; color: #dd1144;">@param</span> senderId 转账人的ID
* <span class="hljs-doctag" style="box-sizing: border-box; color: #dd1144;">@param</span> beneficiaryId 收款人ID
* <span class="hljs-doctag" style="box-sizing: border-box; color: #dd1144;">@param</span> amount 转账金额
*/</span>
<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">public</span> <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">void</span> <span class="hljs-title" style="box-sizing: border-box; color: #990000; font-weight: bold;">transfer</span><span class="hljs-params" style="box-sizing: border-box;">(String senderId, String beneficiaryId, <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">long</span> amount)</span> </span>{
Account sender = accountManager.findAccountForUser(senderId);
Account beneficiary = accountManager.findAccountForUser(beneficiaryId);
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//转账账户减少</span>
sender.debit(amount);
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//收款账户增加</span>
beneficiary.credit(amount);
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//更新转账账户信息</span>
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.accountManager.updateAccount(sender);
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//更新收款账户信息</span>
<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">this</span>.accountManager.updateAccount(beneficiary);
}
}
- 创建一个普通的单元测试类来测试这个业务流程
import org.junit.Test;
import junit.framework.TestCase;
public class TestAccountService extends TestCase{
<span class="hljs-annotation" style="box-sizing: border-box;">@Test</span>
public void testTransferOk(){
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//使用MockAccountManager</span>
<span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">MockAccountManager</span> mockAccountManager = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> <span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">MockAccountManager</span>();
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//初始化两个账户</span>
<span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">Account</span> senderAccount = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> <span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">Account</span>(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"1"</span>, <span class="hljs-number" style="box-sizing: border-box; color: teal;">200</span>);
<span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">Account</span> beneficiaryAccount = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> <span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">Account</span>(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"2"</span>, <span class="hljs-number" style="box-sizing: border-box; color: teal;">100</span>);
mockAccountManager.addAccount(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"1"</span>, senderAccount);
mockAccountManager.addAccount(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"2"</span>, beneficiaryAccount);
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//初始化AccountService,将MockManager对象传入</span>
<span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">AccountService</span> accountService = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> <span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">AccountService</span>();
accountService.setAccountManager(mockAccountManager);
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//转帐操作</span>
accountService.transfer(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"1"</span>, <span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"2"</span>, <span class="hljs-number" style="box-sizing: border-box; color: teal;">50</span>);
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//验证</span>
assertEquals(<span class="hljs-number" style="box-sizing: border-box; color: teal;">150</span>, senderAccount.getBalance());
assertEquals(<span class="hljs-number" style="box-sizing: border-box; color: teal;">150</span>, beneficiaryAccount.getBalance());
}
}
- 创建一个用Mockito进行测试的类
public class TestMockAccountService extends TestCase {
<span class="hljs-annotation" style="box-sizing: border-box;">@Test</span>
public void testTransferOk() {
<span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">Account</span> senderAccount = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> <span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">Account</span>(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"1"</span>, <span class="hljs-number" style="box-sizing: border-box; color: teal;">200</span>);
<span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">Account</span> beneficiaryAccount = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> <span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">Account</span>(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"2"</span>, <span class="hljs-number" style="box-sizing: border-box; color: teal;">100</span>);
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">// 使用mockito创建一个模拟对象</span>
<span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">MockAccountManager</span> mockAccountManager = <span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">Mockito</span>.mock(<span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">MockAccountManager</span>.<span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">class</span>);
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">//mockito自带的控制流写法</span>
when(mockAccountManager.findAccountForUser(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"1"</span>)).thenReturn(senderAccount);
when(mockAccountManager.findAccountForUser(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"2"</span>)).thenReturn(beneficiaryAccount);
<span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">AccountService</span> accountService = <span class="hljs-keyword" style="box-sizing: border-box; color: #333333; font-weight: bold;">new</span> <span class="hljs-type" style="box-sizing: border-box; color: #445588; font-weight: bold;">AccountService</span>();
accountService.setAccountManager(mockAccountManager);
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">// 转帐操作</span>
accountService.transfer(<span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"1"</span>, <span class="hljs-string" style="box-sizing: border-box; color: #dd1144;">"2"</span>, <span class="hljs-number" style="box-sizing: border-box; color: teal;">50</span>);
<span class="hljs-comment" style="box-sizing: border-box; color: #999988; font-style: italic;">// 验证</span>
assertEquals(<span class="hljs-number" style="box-sizing: border-box; color: teal;">150</span>, senderAccount.getBalance());
assertEquals(<span class="hljs-number" style="box-sizing: border-box; color: teal;">150</span>, beneficiaryAccount.getBalance());
}
}
以上。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于