JUnit+Mockito單元測試

一、前言

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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章