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



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