使用Mockito进行Java的Mock测试

准备

本文需要Java和Junit基础,可参考:使用Junit进行单元测试教程,Java程序设计简介。

项目地址:https://github.com/mockito/mockito。

主页:  http://mockito.org/。

测试替身

测试替身用于消除单元测试的依赖。

  • dummy用于传递,不会真正使用,例如用于填充的方法的参数列表。

  • Fake有简单实现,但通常被简化,比如在内存数据库,而不是真正的数据库中使用。

  • Stub是接口或类中部分实现,测试时使用其实例,通常有回应,也可能记录调用信息。

  • Mock对象是接口或类假实现,定义某些方法的输出。

参考资料:http://martinfowler.com/articles/mocksArentStubs.html。
通常首选Mock。Mock框架让你在运行时创建模拟对象,并定义他们的行为。Python中的unittest、pytest、nose等测试框架mock功能做得相当强大。Java的mock功能较弱,Junit需要借助外部包来mock,mock功能也远不及pytest。Java知名的Mock框架有:

  • jMock(http://jmock.org/): 3年没有更新,对Java7,8支持不好,不支持安卓。

  • EasyMock(http://easymock.org/): 所有测试都要基于interface,额外增加工作量。支持安卓。

  • Mockito(https://github.com/mockito/mockito): 易学易用,支持安卓,但是不支持static等方法。

  • powermock(https://github.com/jayway/powermock)基于Mockitto和EasyMock,自定义类加载器和字节码处理,支持static、构建方法、 final类和方法等Mock。为Java Mock测试的首选。


安装

Eclipse jar包安装(不推荐)

Eclipse ADT安装Mockito的方法:在工程的libs目录中添加dexmaker-1.4.jar、dexmaker-dx-1.2.jar、dexmaker-mockito-1.4.jar、mockito-core-1.10.19.jar. 下载地址参考:
http://central.maven.org/maven2/com/google/dexmaker/dexmaker-dx/1.2/dexmaker-dx-1.2.jar
https://oss.sonatype.org/service/local/repositories/releases/content/com/crittercism/dexmaker/dexmaker/1.4/dexmaker-1.4.jar
https://oss.sonatype.org/service/local/repositories/releases/content/com/crittercism/dexmaker/dexmaker-mockito/1.4/dexmaker-mockito-1.4.jar
http://mockito.org/

非安卓系统的安装:
mockito-core-1.10.19.jar objenesis-2.1.jar

ItellliJ IDEA的安装:File - Project Settings - Modules:

 

使用Mockito进行Java的Mock测试_第1张图片

其他安装方式

Gradle中的设置如下:

repositories { jcenter() }
dependencies { testCompile "org.mockito:mockito-core:1.+" }

Maven中可以用依赖搞定,在http://search.maven.org/搜索"org.mockito"和"mockito-core"可以找到对应的POM入口。

 

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <version>1.10.19</version>
</dependency>

快速入门                  

Mockito支持用静态mock()创建mock对象。通过静态导入,您可以在指定类的情况下调用静态成员(方法和成员)。verify()方法用于确认模拟的方法已经调用。when(....).thenReturn(....)用来指定条件和对应的返回值。如果指定多个值,会依次返回。然后最后一个指定的值返回。anyString或anyInt实现了类似通配符的功能

 

下面例子,我们基于MyClass.java:

package first;

public class MyClass {

    public int multiply(int x, int y) {、
    
        // the following is just an example
        if (x > 999) {
          throw new IllegalArgumentException("X should be less than 1000");
        }
        return x / y;
    }
}

测试代码MyClassTest.java

package first;

import org.junit.Test;
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import java.util.Iterator;

public class MyClassTest {

    @Test(expected = IllegalArgumentException.class)    
    public void testExceptionIsThrown() {
    
        MyClass tester = new MyClass();
        tester.multiply(1000, 5);
    }
    
    @Test    
    public void testMultiply() {
    
        MyClass tester = new MyClass();
        assertEquals("10 x 5 must be 50", 2, tester.multiply(10, 5));
    }
    
    @Test    
    public void test1()  {
    
        //  create mock
        MyClass test = mock(MyClass.class);        
        // define return value for method getUniqueId()
        when(test.multiply(3, 3)).thenReturn(9);        
        // use mock in test....
        assertEquals(test.multiply(3,3), 9);
    }    
    
    // Demonstrates the return of multiple values    
    @Test    
    public void testMoreThanOneReturnValue()  {
        Iterator i= mock(Iterator.class);
        when(i.next()).thenReturn("Mockito").thenReturn("rocks");
        String result=i.next()+" "+i.next();        
        //assert
        assertEquals("Mockito rocks", result);
    }    
    
    // this test demonstrates how to return values based on the input    
    @Test    
    public void testReturnValueDependentOnMethodParameter()  {
    
        Comparable c= mock(Comparable.class);
        when(c.compareTo("Mockito")).thenReturn(1);
        when(c.compareTo("Eclipse")).thenReturn(2);        
        //assert
        assertEquals(1, c.compareTo("Mockito"));
    }
    
    // this test demonstrates how to return values independent of the input value
    @Test    
    public void testReturnValueInDependentOnMethodParameter2()  {
    
        Comparable c= mock(Comparable.class);
        when(c.compareTo(anyInt())).thenReturn(-1);        
        //assert
        assertEquals(-1 ,c.compareTo(9));
    }
}

方法testExceptionIsThrown和testMultiply是正常的Junit测试。test1为返回模拟值,testMoreThanOneReturnValue返回2个值,testReturnValueDependentOnMethodParameter根据输入返回值,testReturnValueInDependentOnMethodParameter使用了通配符。另外还可以基于条件mock:

@Test
public void testReturnValueInDependentOnMethodParameter()  {

  Comparable c= mock(Comparable.class);
  when(c.compareTo(isA(Todo.class))).thenReturn(0);
  //assert
  Todo todo = new Todo(5);
  assertEquals(todo ,c.compareTo(new Todo(1)));
}


doReturn(...).when(...).methodCall的工作方法类似,适用于void方法。

doThrow适合返回void的方法的异常处理:

    @Test(expected=IOException.class)
    public void testForIOException() throws IOException {
    
      // create an configure mock
      OutputStream mockStream = mock(OutputStream.class);
      doThrow(new IOException()).when(mockStream).close();
      
      // use mock
      OutputStreamWriter streamWriter= new OutputStreamWriter(mockStream);
      streamWriter.close();
    }

股票交易测试实例

下面实例买卖股票,类有:

下面实例买卖股票,类有: Stock、MarketWatcher、Portfolio和StockBroker。MarketWatcher返回当前股票价格、Portfolio统计利润。StockBroker的perform在获利10%以上卖出。测试需要portfolio总返回10,股价返回100。
使用的mock有几种方法:

1,使用完整的Mockito类调用。

import org.mockito.Mockito;

public class StockBrokerTest {
    MarketWatcher marketWatcher = Mockito.mock(MarketWatcher.class);
    Portfolio portfolio = Mockito.mock(Portfolio.class);
}

2,使用静态导入:

import static org.mockito.Mockito.mock;

public class StockBrokerTest {
    MarketWatcher marketWatcher = mock(MarketWatcher.class);
    Portfolio portfolio = mock(Portfolio.class);
}

3,注解类成员

import org.mockito.Mock;

public class StockBrokerTest {
    @Mock
    MarketWatcher marketWatcher;
    @Mock
    Portfolio portfolio;
}

有个前提:需要初始化mock。可以使用 MockitoAnnotations.initMocks(this)或者 MockitoJUnitRunner。示例分别如下:

import static org.junit.Assert.assertEquals;
import org.mockito.MockitoAnnotations;
public class StockBrokerTest {
    @Mock
    MarketWatcher marketWatcher;
    @Mock
    Portfolio portfolio;
    
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }
    
    @Test
    public void sanity() throws Exception {
        assertNotNull(marketWatcher);
        assertNotNull(portfolio);
    }
}
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class StockBrokerTest {
    @Mock
    MarketWatcher marketWatcher;
    @Mock
    Portfolio portfolio;
    
    @Test
    public void sanity() throws Exception {
        assertNotNull(marketWatcher);
        assertNotNull(portfolio);
    }
}

Mockito不能mock或spy final类和方法、static及private方法、enum、 equals()和hashCode()、原始类型和匿名类。PowerMockito解决了上述问题。一般很好的封装,不需要使用PowerMockito。

mock对象有默认值,Boolean返回false,对象返回null,数值返回零等。

when后面的使用方法:
• thenReturn(value to be returned):返回指定值。
• thenThrow(throwable to be thrown):抛出指定异常。
• thenAnswer(Answer answer) :返回指定逻辑,Answer是个接口。
• thenCallRealMethod(): 调用指定方法。


 verify()可以接收 org.mockito.internal.verification.Times作为参数。verify可以配合使用的方法如下:

• times(int wantedNumberOfInvocations)
• never()

• atLeastOnce()

• atLeast(int minNumberOfInvocations)

• atMost(int maxNumberOfInvocations)

• only(): 只调用该方法。

• timeout(int millis) : 

代码如下:

public class MarketWatcher {
    public Stock getQuote(Object symbol) {
        // TODO Auto-generated method stub
        return null;
    }
}
import java.math.BigDecimal;

public class Portfolio {

    public BigDecimal getAvgPrice(Stock stock) {
        // TODO Auto-generated method stub
        return null;
    }
    
    public void sell(Stock stock, int i) {
        // TODO Auto-generated method stub
        
    }
    
    public void buy(Stock stock) {
        // TODO Auto-generated method stub
        
    }
}
import java.math.BigDecimal;

public class Stock {

    public Stock(String string, String string2, BigDecimal bigDecimal) {
        // TODO Auto-generated constructor stub
    }
    
    public Object getSymbol() {
        // TODO Auto-generated method stub
        return null;
    }
    
    public BigDecimal getPrice() {
        // TODO Auto-generated method stub
        return BigDecimal.valueOf(100.48);
    }
}
import java.math.BigDecimal;

public class StockBroker {
    
    private final static BigDecimal LIMIT = new BigDecimal("0.10");
    
    private final MarketWatcher market;
    
    public StockBroker(MarketWatcher market) {
        this.market = market;
    }
        
    public void perform(Portfolio portfolio, Stock stock) {
        
        Stock liveStock = market.getQuote(stock.getSymbol());
        BigDecimal avgPrice = portfolio.getAvgPrice(stock);
        BigDecimal priceGained =
        liveStock.getPrice().subtract(avgPrice);
        BigDecimal percentGain = priceGained.divide(avgPrice);
        if(percentGain.compareTo(LIMIT) > 0) {
        portfolio.sell(stock, 10);
        }else if(percentGain.compareTo(LIMIT) < 0){
        portfolio.buy(stock);
        }
    }
}


import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import java.math.BigDecimal;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class StockBrokerTest {
    
    @Mock MarketWatcher marketWatcher;
    @Mock Portfolio portfolio;
    StockBroker broker;
    
    @Before 
    public void setUp() {
        broker = new StockBroker(marketWatcher);
    }
    
    @Test
    public void when_ten_percent_gain_then_the_stock_is_sold() {
    
        //Portfolio's getAvgPrice is stubbed to return $10.00
        when(portfolio.getAvgPrice(isA(Stock.class))).thenReturn(new BigDecimal("10.00"));
        //A stock object is created with current price $11.20
        Stock aCorp = new Stock("A", "A Corp", new BigDecimal("11.20"));
        //getQuote method is stubbed to return the stock
        when(marketWatcher.getQuote(anyString())).thenReturn(aCorp);
        
        //perform method is called, as the stock price increases
        // by 12% the broker should sell the stocks
        broker.perform(portfolio, aCorp);
        //verifying that the broker sold the stocks
        verify(portfolio).sell(aCorp,10);
    }
    
    @Test
    public void verify_zero_interaction() {
        verifyZeroInteractions(marketWatcher,portfolio);
    }    
    
    @Test(expected = IllegalStateException.class)
    public void throwsException() throws Exception {
        
        when(portfolio.getAvgPrice(isA(Stock.class))).thenThrow(
        new IllegalStateException("Database down"));
        portfolio.getAvgPrice(new Stock(null, null, null));
    }
    
}

verifyNoMoreInteractions(Object mocks),确认没有关系。

void方法的发送异常方式:doThrow(exception).when(mock).voidmethod(arguments);


参考资料:

http://www.vogella.com/tutorials/Mockito/article.html

书籍:Mockito for Spring - 2015

书籍:Test-Driven Development with Mockito

 

微博 http://weibo.com/cizhenshi 作者博客:http://www.cnblogs.com/pythontesting/ python测试开发精华群 291184506 PythonJava单元白盒测试 144081101




你可能感兴趣的:(使用Mockito进行Java的Mock测试)