用Mock Object进行独立单元测试

编程技术  /  houtizong 发布于 3年前   64

转自:https://compdoc2cn.dev.java.net/mock/html/mock.cn.html

 

声明:文章之前,先做一下声明。本博客中所有转摘文章,转摘地址都尽可能找到『原创出处』,但不幸的是很多情形找不到或者不明确是否『原创出处』,有可能是多次『转摘』后的地址。如对您的权利带来伤害,请联系我,我会及时作出处理

 

Abstract
单独测试一个类或方法里的代码,而不测试里面调用的其他类或方法的代码。即假定调用的其他类或方法都正常执行。

1. 使用Mock Object的场合

  • 实际对象的行为还不确定。
  • 实际的对象创建和初始化非常复杂。
  • 实际对象中存在很难执行到的行为(如网络异常等)。
  • 实际的对象运行起来非常的慢。
  • 实际对象是用户界面程序。
  • 实际对象还没有编写,只有接口等。

2. 简单分析用Mock Object的原因

假定我们有对象A,它内部频繁调用了B中的方法。但是除了调用B的部分还存在自己的逻辑代码。这时,我们只想写A的单体测试,而不想测试B(因为B可能还没编写,只写了实现的接口或者B运行太慢了影响我们的测试效率)。这样的话,我们就需要创建B的Mock对象。

换言之,我们现在不关心B,我们假定B的行为都能正常运行。我们的目标是假定B运行正常的情况下,来对A进行单体测试。

Mock对象就是我们先不用关心的对象,但是我们的关注对象对它有调用,所以我们给关注对象准备个假货让它用。

3. 具体举例

现在我们写好了类AccountService,具体如下

public class AccountService {    private AccountManager accountManager;    public void setAccountManager(AccountManager manager) {        this.accountManager = manager;    }    public void transfer(String senderId, String beneficiaryId, long amount) {        Account sender = this.accountManager.findAccountForUser(senderId);        Account beneficiary =            this.accountManager.findAccountForUser(beneficiaryId);        sender.debit(amount);        beneficiary.credit(amount);        this.accountManager.updateAccount(sender);        this.accountManager.updateAccount(beneficiary);    }}

现在我们想测试transfer方法,它内部调用的AccountManager的两个方法。但是对于AccountManager来说,它只是个接口,如下:

public interface AccountManager {    Account findAccountForUser(String userId);    void updateAccount(Account account);}

所以现在我们必须些个MockAccountManager对象。而且里面的方法体都是非常简单的,就是假定它就返回某某值。

public class MockAccountManager implements AccountManager {    private Hashtable accounts = new Hashtable();    public void addAccount(Account account) {        this.accounts.put(account.getAccountId(), account);    }    /*     *     */    public Account findAccountForUser(String userId) {        return (Account)accounts.get(userId);    }    /*     *     */    public void updateAccount(Account account) {    }}

我们这里还有Account类

public class Account {    private String accountId;    private long balance;    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;    }    public String getAccountId() {        return accountId;    }}

由于这里的Account类非常的简单,以至于我们在测试AccountService的时候,创不创建Account的Mock对象都一样,那么我们为什么不用真实的对象测试呢。这时我们可以回想一下我们使用Mock对象的那些种场合。

下面是对AccountService的transfer方法的测试

public class AccountService1Tests extends TestCase {    public void testTransfer(){        AccountService as = new AccountService();        MockAccountManager mockAccountManager = new MockAccountManager();        Account accountA = new Account("A",3000);        Account accountB = new Account("B",2000);        mockAccountManager.addAccount(accountA);        mockAccountManager.addAccount(accountB);        as.setAccountManager(mockAccountManager);        as.transfer("A","B",1005);        assertEquals(accountA.getBalance(),1995);        assertEquals(accountB.getBalance(),3005);    }}

这里我们在假定AccountManager方法都工作正常的情况下,完成了对transfer方法的测试。
4. 动态Mock对象(EasyMock和jMock)

EasyMock和jMock都是Open Source,前者在sourceforge,后者在codehaus。对这些OSS的应用可以简化我们对Mock对象的时候,大部分情况下不用手动创建Mock对象。
4.1. EasyMock

现在我们还是要测试AccountService的transfer方法,我们写了AccountService2Tests类。

public class AccountService2Tests extends TestCase{    public void testTransfer(){        // setup        //创建MockControl对象,关联到AccountManager        MockControl control = MockControl.createControl(AccountManager.class);        //得到一个运行中的mock对象        AccountManager mockAccountManager =(AccountManager) control.getMock();        AccountService as = new AccountService();        as.setAccountManager(mockAccountManager);        //expect        /* 设定Mock对象的行为 */        Account accountA = new Account("A",3000);        Account accountB = new Account("B",2000);        //设定在运行中执行方法findAccountForUser("A"),并且让它返回accountA对象。        mockAccountManager.findAccountForUser("A");        control.setReturnValue(accountA);        //设定在运行中执行方法findAccountForUser("B"),并且让它返回accountB对象。        mockAccountManager.findAccountForUser("B");        control.setReturnValue(accountB);        //设定在运行中执行方法updateAccount(accountA)。        mockAccountManager.updateAccount(accountA);        mockAccountManager.updateAccount(accountB);        /* 带Mock对象运行测试 */        control.replay();        as.transfer("A","B",1005);        assertEquals(accountA.getBalance() , 1995);        assertEquals(accountB.getBalance() , 3005);        control.verify();    }}

这里我们没有使用MockAccountManager对象,而是用EasyMock创建的动态Mock对象。所以我们就不用手动编写Mock对象了。
4.2. jMock

下面是用jMock写的测试AccountService的transfer方法的AccountService3Tests单体测试

public class AccountService3Tests extends MockObjectTestCase {    public void testTransfer(){        Mock mock = new Mock(AccountManager.class);        AccountManager mockAccountManager =(AccountManager)mock.proxy();        AccountService as = new AccountService();        as.setAccountManager(mockAccountManager);        Account accountA = new Account("A",3000);        Account accountB = new Account("B",2000);        // expectations        mock.expects(once()).method("findAccountForUser")            .with(same("A")).will(returnValue(accountA));        mock.expects(once()).method("findAccountForUser")            .with(same("B")).will(returnValue(accountB));        mock.expects(once()).method("updateAccount")            .with(same(accountA)).isVoid();        mock.expects(once()).method("updateAccount")            .with(same(accountB)).isVoid();        //excute        as.transfer("A","B",1005);        assertEquals(accountA.getBalance() , 1995);        assertEquals(accountB.getBalance() , 3005);        verify();    }}

从上面的测试可以看到,在使用jMock做测试的时候,必须继承的是MockObjectTestCase。因为要使用once,same,returnValue等方法。其他的和EasyMock基本上相似,只是jMock更容易理解一点。
5. 参考资料

Manning - JUnit in Action

http://www.easymock.org/Documentation.html

http://www.jmock.org/getting-started.html

 

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!

留言需要登陆哦

技术博客集 - 网站简介:
前后端技术:
后端基于Hyperf2.1框架开发,前端使用Bootstrap可视化布局系统生成

网站主要作用:
1.编程技术分享及讨论交流,内置聊天系统;
2.测试交流框架问题,比如:Hyperf、Laravel、TP、beego;
3.本站数据是基于大数据采集等爬虫技术为基础助力分享知识,如有侵权请发邮件到站长邮箱,站长会尽快处理;
4.站长邮箱:[email protected];

      订阅博客周刊 去订阅

文章归档

文章标签

友情链接

Auther ·HouTiZong
侯体宗的博客
© 2020 zongscan.com
版权所有ICP证 : 粤ICP备20027696号
PHP交流群 也可以扫右边的二维码
侯体宗的博客