一、前言
1、單元測試
單元測試是指對軟件中的最小可測試單元進行檢查和驗證。
2、JUnit與Mockito
JUnit 是單元測試框架。
Mockito 與 JUnit 不同,並不是單元測試框架(這方面 JUnit 已經足夠好了),它是用於生成模擬對象或者直接點說,就是”假對象“的工具。
二、環境搭建
<mockito.version>1.9.0</mockito.version>
<powermock.version>1.4.12</powermock.version>
<junit.version>4.11</junit.version>
<assertj.version>1.6.1</assertj.version>
<!-- test begin -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- assertj -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- test end -->
依賴說明:
1、junit:單元測試框架
2、assertj:斷言工具
3、mockito、powermock:擴展插件
三、創建一個簡單的單元測試
測試方法的書寫就是四個步驟:
(1)參數賦值
(2)寫出期望值
(3)獲取實際值
(4)斷言--比較期望值和實際值
下面寫一個簡單的單元測試:
1、待測試的業務類:
public class Calculator
{
private static int result; // 靜態變量,用於存儲運行結果
public void add(int n) {
result += n;
}
public void substract(int n) {
result -= n;
}
public void multiply(int n) {
result *= n;
}
public void divide(int n) {
result /= n;
}
public void square(int n) {
result = n * n;
}
public void clear() {// 將結果清零
result = 0;
}
public int getResult() {
return result;
}
}
2、創建JUnit Case:
public class TestSample
{
private Calculator calculator = new Calculator();
@Before
public void setUp() throws Exception
{
calculator.clear();
}
@Test
public void testAdd()
{
calculator.add(1);
calculator.add(2);
int result = calculator.getResult();
Assertions.assertThat(result).isEqualTo(3); //斷言
}
@Ignore
@Test
public void testSubstract()
{
calculator.add(10);
calculator.substract(2);
int result = calculator.getResult();
Assertions.assertThat(result).isEqualTo(8); //斷言
}
@Repeat(3)
@Test(timeout=3000)
public void testDivide()
{
calculator.add(10);
calculator.divide(2);
int result = calculator.getResult();
Assertions.assertThat(result).isEqualTo(5);
}
}
1)@Before:測試用例執行前調用的方法(初始化對象、環境變量、Mock等);
2)@Test:測試用例方法。timeout定義超時時長,單位ms;
3)@Ignore:忽略的測試用例方法;
4)@Repeat:定義用例執行次數;
5)Assertions.assertThat:測試結果斷言,用於判定測試通過與否。
四、隔離測試
隔離測試也是我們常用的一個防止多類之間依賴的測試。最基礎的就是Service層對Dao層的依賴。測試Service層時,我們不可能還要跑Dao層,這樣的話就不是單元測試。
那麼我們怎麼來解決這個問題呢?我們不需要跑Dao層,但是又需要Dao層的返回值。隔離測試就幫助我們解決了這個問題。
如何進行隔離測試呢?選用Mockito來進行隔離測試。其實說白了,隔離測試,就是一個Mock(模擬)的功能。當我們依賴其他類時,不需要真實調用,只需模擬出該類即可。
1、使用Mockito進行Service層單元測試
public class ComBuyerServiceTest {
@InjectMocks
private ComBuyerService comBuyerService;
@Mock
private ComBuyerMapper comBuyerMapper;
@Before
public void setUP() {
MockitoAnnotations.initMocks(this);
//構建環境變量
Long comId = 520L;
CbCompanyInfo cbCompany = new CbCompanyInfo();
cbCompany.setCbComId(comId);
User user = new User();
user.setComId(comId);
user.setUserName("JUnitTester");
user.setOperatorNo("00");
CbAppContextHolder.setAppContext(new CbAppContext());
CbAppContextHolder.getAppContext().setCurrentLogonCbCompany(cbCompany);
CbAppContextHolder.getAppContext().setCurrentLogonMicUser(user);
//預設調試數據
when(comBuyerMapper.selectByCreditCondition(any(Map.class))).thenAnswer(new Answer<List>() {
@Override
public List answer(InvocationOnMock invocation) throws Throwable {
List<ComBuyer> comBuyerList = new ArrayList<ComBuyer>();
comBuyerList.add(new ComBuyer());
return comBuyerList;
}
});
}
@Test
public void findHomePageComBuyers() {
Long comId = CbAppContextHolder.getAppContext().getCurrentLogonCbCompany().getCbComId();
List<ComBuyer> comBuyerList = comBuyerService.findHomePageComBuyers(comId);
assertThat(comBuyerList).hasSize(3);
assertThat(comBuyerList.get(0).getCreditStatus()).isEqualTo(Integer.parseInt(BuyerCreditStatus.VALID.getKey()));
assertThat(comBuyerList.get(1).getCreditStatus()).isEqualTo(Integer.parseInt(BuyerCreditStatus.OUT_OF_DATE.getKey()));
assertThat(comBuyerList.get(2).getCreditStatus()).isEqualTo(Integer.parseInt(BuyerCreditStatus.VALID.getKey()));
}
@After
public void tearDown() {
}
}
1)@InjectMocks:註解單元測試的對象;
2)@Mock:註解測試對象的依賴,Mockito框架會模擬該依賴;
3)MockitoAnnotations.initMocks(this)
初始化Mock,使用MockitoJUnitRunner進行單元測試。與測試類註解@RunWith(MockitoJUnitRunner.class)效果相同。
4)org.mockito.Mockito.when:模擬Mock對象的方法
comBuyerMapper.selectByCreditCondition是單元測試方法findHomePageComBuyers中依賴的Dao層方法,這裏使用Mock模擬返回值。
2、如何模擬static、final、private?
Mockito可以幫助模擬對象依賴,但是有些特殊場景無法模擬,比如靜態變量、靜態方法、私有方法。PowerMock是對Mockito的擴展,可以幫助我們解決這些問題。
例如下面這個對象中有2個私有靜態變量ADD_TRACKING_URL、TOKEN,在進行單元測試時,我們需要模擬這2個值:
@Service
public class DobaOrderRPC
{
protected Log logger = LogFactory.getLog("for_api");
private static String ADD_TRACKING_URL = StringUtils.join(new String[]{System.getProperty("doba.api.url"), "orders/%s/add-tracking"});
private static String TOKEN = System.getProperty("doba.api.token");
...
}
在JUnit Case中模擬ADD_TRACKING_URL、TOKEN:public class DobaOrderRPCTest
{
@InjectMocks
private DobaOrderRPC dobaOrderRPC;
@Before
public void setUP() {
MockitoAnnotations.initMocks(this);
Whitebox.setInternalState(DobaOrderRPC.class, "ADD_TRACKING_URL", "https://sandbox-api.doba.com/v1/orders/%s/add-tracking");
Whitebox.setInternalState(DobaOrderRPC.class, "TOKEN", "b804caab601bc6867573c70fc5f4c8a7db53161a");
}
...
}
如上使用Whitebox.setInternalState()方法模擬私有靜態變量的值。除此,還可以使用MemberModifier.field()方法進行模擬:
MemberModifier.field(DobaOrderRPC.class, "ADD_TRACKING_URL").set(dobaOrderRPC , "https://sandbox-api.doba.com/v1/orders/%s/add-tracking");
五、與Spring整合進行單元測試
我們的項目大多整合了Spring框架,那麼如何在Spring環境下進行單元測試呢?
當我們單元測試Dao層,進行數據庫層面的單元測試,如何進行事務控制,讓單元測試結束自動回滾測試數據?
下面用一個Spring-test示例來解答:
@DirtiesContext
@ContextConfiguration(locations = {"/applicationContext-dal-cb-test.xml"})
public class ComBuyerMapperTest extends AbstractTransactionalJUnit4SpringContextTests {
@Autowired
private ComBuyerMapper comBuyerMapper;
@Before
public void setUP() {
MockitoAnnotations.initMocks(this);
}
@Test
public void findComUserById(){
Long comId = CbAppContextHolder.getAppContext().getCurrentLogonCbCompany().getCbComId();
ComBuyer comBuyer = insertComUser();
Long comBuyerId = comBuyer.getComBuyerId();
comBuyer = comBuyerMapper.findComUserById(comId, comBuyerId);
Assertions.assertThat(comBuyer).isNotNull();
}
private ComBuyer insertComUser(){
Long comId = CbAppContextHolder.getAppContext().getCurrentLogonCbCompany().getCbComId();
ComBuyer comBuyer = new ComBuyer();
comBuyer.setComId(comId);
comBuyer.setBuyerId(0L);
comBuyer.setBuyerNameEn("buyer name");
comBuyer.setStatus(Integer.valueOf(BuyerCreditStatus.VALID.getKey()));
comBuyer.setDeleteFlag(Short.valueOf(DeleteFlagConstants.NOT_DELETE.getKey()));
comBuyer.setCountry("China");
comBuyer.setAddress("beijing");
comBuyer.setContactName("laoli");
comBuyer.setAddInfo();
comBuyer.setUpdateInfo();
comBuyerMapper.insertComUser(comBuyer);
return comBuyer;
}
}
1、@ContextConfiguration(locations = {"/applicationContext-dal-cb-test.xml"})
構建Spring環境,比如數據源、持久層框架等配置。
2、@DirtiesContext
聲明該註解後,在每次單元測試結束清理Spring環境,防止單元測試環境有緩存數據。
3、AbstractTransactionalJUnit4SpringContextTests
JUnit Case繼承該父類,單元測試用例將運行在事務中。出現異常或運行結束,用例中的寫操作都將回滾,不會對數據庫中數據產生影響。
參考:
http://www.ibm.com/developerworks/cn/java/j-lo-springunitest/
http://blog.csdn.net/zhangxin09/article/details/42422643
http://my.oschina.net/u/551903/blog/176559?fromerr=WW9HpGkt
http://blog.csdn.net/sunliduan/article/details/42026509
http://jh108020.iteye.com/blog/1462494