用Mock Object进行独立单元测试(Testing in isolation with mock objects)

用Mock Object进行独立单元测试(Testing in isolation with mock objects)

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

  • 使用Mock Object的场合
    1. 实际对象的行为还不确定。
    2. 实际的对象创建和初始化非常复杂。
    3. 实际对象中存在很难执行到的行为(如网络异常等)。
    4. 实际的对象运行起来非常的慢。
    5. 实际对象是用户界面程序。
    6. 实际对象还没有编写,只有接口等。
  • 简单分析用Mock Object的原因

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

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

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

  • 具体举例

    现在我们写好了类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对象。而且里面的方法体都是非常简单的,就是假定它就返回某某值。

    我们这里还有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方法的测试。

  • 动态Mock对象(EasyMock和jMock)

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

    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对象了。

    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更容易理解一点。

  • 参考资料
    1. Manning - JUnit in Action
    2. http://www.easymock.org/Documentation.html
    3. http://www.jmock.org/getting-started.html


  • 只有注册用户登录后才能发表评论。
    网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


    Copyright © fred