首 页 | 新 闻 | 技术中心 | 第二书店 | 《程序员》 | 《开发高手》 | 社 区 | 黄 页 | 人 才
移 动专 题SUNIBM微 软微 创精 华Donews人 邮
我的技术中心 
我的分类 我的文档
全部文章 发表文章
专栏管理 使用说明



 RSS 订阅 
最新文档列表
Windows/.NET
.NET  (rss)    
Visual C++  (rss)    
Delphi  (rss)    
Visual Basic  (rss)    
ASP  (rss)    
JavaScript  (rss)    
Java/Linux
Java  (rss)    
Perl  (rss)    
综合
其他开发语言  (rss)    
文件格式  (rss)    
企业开发
游戏开发  (rss)    
网站制作技术  (rss)    
数据库
数据库开发  (rss)    
软件工程
其他  (rss)    

积极原创作者 
iiprogram (83)
nizhigang2000 (3)
hongbo781202 (81)
Kendiv (113)
TechnoFantasy (52)
feifei1018 (19)
coofucoo (108)
qingrun (67)
btbtd (83)
longrujun (64)
CSDN - 文档中心 - Java 阅读:452   评论: 0    参与评论
标题   [原创]用Mock Object进行独立单元测试(Testing in isolation with mock objects)      选择自 sunfmin 的 Blog
关键字   [原创]用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
    2. 现在我们还是要测试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对象了。

    3. jMock
    4. 下面是用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

    相关文章
    对该文的评论