本文需要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 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:
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