The secret of success is sincerity.once you can fake that you have you have got it made. ——Jean Giraudoux
本站内容:
- 介绍并展示mock objects
- 执行不同的重构
- 用mock objects检测API与合作类的约定
- 实践HTTP连接示例程序
mock objects简介
如果你的测试粒度比较大,那么一旦重构引入了一个Bug,就会有一系列的测试失败,结果就是告诉你有一个错误,但是你并不知道它在哪。而用细粒度测试,受到bug影响就比较小了,而且会提供精确的错误信息!
mock objects非常适合将部分代码逻辑的测试同其他的代码隔离开来,mocks替换了测试中与你的方法协作的对象,从而提供了隔离层。从这个意义上来说,它和stub类似。但是相似之处,仅限于此。mocks不实现任何的逻辑,我们只是使用了一种使测试能控制仿造类的所有业务逻辑方法行为的方法的空壳(????)。
定义 mock object——mock object是用来代替与你的代码协作的对象的对象。你的代码可以调用mock object的方法。mock objec会传递结果,结果是由你的测试代码设置好的。
体验mock objects:一个简单的例子
你想要从一个账户到另外一个账户的转帐操作。(看不到图的朋友点这里)
AccountService类提供有关账户的服务,使用AccountManager把数据持久化到数据库。使我们感兴趣的是AccountService.transfer方法,它执行了转帐。不用mocks,测试AccountService意味着建立一个数据库,预置测试数据,在容器中配置代码等等。虽然确保了程序完全正常执行,这个过程是必要的,但当你只要单元测试代码的逻辑时,这些工作就太多了!
Account.java
package junitbook.fine.tasting;
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;
}
}
AccountManager.java
package junitbook.fine.tasting; public interface AccountManager { Account findAccountForUser(String userId); void updateAccount(Account account); }
AccountService.java
package junitbook.fine.tasting; 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); } }
你希望能够对AccountService.transfer行为执行单元测试。出于这个目的,可以使用mock来执行AccountManager接口。这样做是因为tranfer方法使用了这个接口,你要隔离的测试它。
MockAccountManager.java
package junitbook.fine.tasting;
import java.util.Hashtable;
public class MockAccountManager implements AccountManager
{
private Hashtable accounts = new Hashtable();
public void addAccount(String userId, Account account)
{ ;addAccount方法用一个实例变量保存要返回的值。由于有多个想要返回的账户对象,所以将返回的
this.accounts.put(userId, account); ;Account对象存储在一个Hashtable中。这样使得Mock变得一般化,能够支持不同的mock case
} ;一个测试可以用一个账户建立mock,另一个测试可以用两个或更多的账户建立Mock,等等。
public Account findAccountForUser(String userId) ;addAccount告诉findAccountForUser方法调用时返回什么。
{
return (Account) this.accounts.get(userId);
}
public void updateAccount(Account account) ;UpdateAccount方法更新一个用户,但不返回什么值。被tranfer调用,不起什么作用
{
// do nothing
}
}
注:JUnit最佳实践:不要把任何商业逻辑写入mock object
编写一个mock时要考虑的最重要的一点就是mock不应该有任何的商业逻辑。它一定是一个傻对象,只能做测试要它做的事情,换句话就是,它只是由测试驱动。这种特征同各种包含逻辑的stub正好相反。这里有两和结论。第一,mock objects容易创建,在下面你将会看到。。第二,因为mock objects是空壳,他们简单的不能出错,所以不要去测试他们。
下面是用mock的典型测试。
package junitbook.fine.tasting; import junit.framework.TestCase; public class TestAccountService extends TestCase { public void testTransferOk() { MockAccountManager mockAccountManager = new MockAccountManager(); Account senderAccount = new Account("1", 200); Account beneficiaryAccount = new Account("2", 100); mockAccountManager.addAccount("1", senderAccount); mockAccountManager.addAccount("2", beneficiaryAccount); AccountService accountService = new AccountService(); accountService.setAccountManager(mockAccountManager); accountService.transfer("1", "2", 50); assertEquals(150, senderAccount.getBalance()); assertEquals(150, beneficiaryAccount.getBalance()); } }
通常一个测试分三个步骤:准备测试,测试执行和结果确认。在测试准备阶段,你创建MockAccountManager对象,定义了你要操纵的两个账户(付钱的和受益的账户)被调用时该返回什么。你已经成功的隔离了另一个区域的对象——AccountManager(在这种情况下不会出现,但是在真正的情况中是由JDBC实现的),对AccountService的代码进行了测试。
注:JUnit最佳实现:只测试可能的错误
也许你已经注意了没有mock Account类。原因在于,这种访问数据对象的方式不需要mock——它不取决于环境,它很简单。你的其他测试使用了Account对象,这样就间接的测试它了。如果它出现了操作错误,依赖于Account的测试会出现问题,同时会提醒你出问题了。
本站写道这里,我们就对mock是什么有了一个相当的理解了!