testng + mockito + powerMock
单元测试
添加pom依赖
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.8</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19 </version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.6.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-testng</artifactId>
<version>1.6.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>1.6.5</version>
<scope>test</scope>
</dependency>
插件安装(IDEA自带,直接跳过)
TestNG
- 下载Eclipse-TestNG插件地址: http://dl.bintray.com/testng-team/testng-eclipse-release/updatesites/ 选择一个版本下载,完成后将features和plugins内容分别复制到eclipse对应目录,重启验证安装
- 验证安装:
在Preference输入test,看到
或者,在任意项目右键运行可以看到(debug as 也有可用于调试)
测试覆盖分析工具EclEmma安装
- Eclipse 下载地址 下载安装
- 验证安装,在任意项目点右键打开菜单,能找到Coverage As
或在windows->Show View –>Other窗口,java菜单下能找到Coverage
Test-NG简介
TestNG是一个设计用来简化广泛的测试需求的测试框架,提供从单元测试到集成测试的支持(以下来自官网介绍)
注解
@BeforeSuite 运行suite中所有测试之前执行。
@AfterSuite 运行suite中所有测试之后执行。
@BeforeClass 运行当前类中所有测试之前执行。
@AfterClass 运行当前类中所有测试之后执行。
@BeforeTest 运行<test>
标签内的所有测试之前运行。
@AfterTest 运行<test>
标签内的所有测试之后运行。
@BeforeGroups 组内所有测试之前执行。
@AfterGroups 组内所有测试之后执行。
@BeforeMethod 每个测试方法之前执行。
@AfterMethod 每个测试方法之后执行。
@DataProvider 提供数据的一个测试方法。注解的方法必须返回一个Object[] []
,其中每个对象[]都可以做为测试方法的参数。
@Factory 作为一个工厂,返回TestNG的测试类的对象将被用于标记的方法。该方法必须返回Object[]
。
@Listeners 定义一个测试类的监听器。
@Parameters 介绍如何将参数传递给@Test方法。
@Test 标记一个类或方法作为测试的一部分。
Mockito详解
Mock(模拟,不真实的)测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。
- 不容易构造:如HttpServletRequest必须在Servlet容器中才能构造出来
- 比较复杂的对象:如JDBC中的ResultSet对象
简单实例
import static org.mockito.Mockito.*;
import java.util.List;
import org.testng.Assert;
import org.testng.annotations.Test;
public class SimpleMockTest {
@Test
public void testSimple(){
//创建mock对象,参数可以是类,也可以是接口
List<String> list = mock(List.class);
//设置方法的预期返回值
when(list.get(0)).thenReturn("hello mock");
String result = list.get(0);
//验证方法调用(是否调用了get(0))
verify(list).get(0);
//测试
Assert.assertEquals("hello mock", result);
}
}
Stubbing & 连续Stubbing
Stubbing(打桩):人为介入,为Mock对象指定行为或行为发生的结果
@Test
public void testMockStubbing() {
// You can mock concrete classes, not just interfaces
LinkedList mockedList = mock(LinkedList.class);
// stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
// 连续stubbing
when(mockedList.get(0)).thenReturn("first").thenReturn("second").thenReturn("Third");
// following prints "first"
System.out.println(mockedList.get(0));
// following throws runtime exception
System.out.println(mockedList.get(1));
// following prints "null" because get(999) was not stubbed
// 默认情况下,对于所有有返回值且没有stub过的方法,mockito会返回相应的默认值。
// 对于内置类型会返回默认值,如int会返回0,布尔值返回false。对于其他type会返回null。
System.out.println(mockedList.get(999));
// Although it is possible to verify a stubbed invocation, usually it's just redundant
// If your code cares what get(0) returns, then something else breaks (often even before verify() gets
// executed).
// If your code doesn't care what get(0) returns, then it should not be stubbed. Not convinced? See here.
verify(mockedList).get(0);
// 重复stub两次,则以第二次为准。如下将返回"second":
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(0)).thenReturn("second");
// 如果是下面这种形式,则表示第一次调用时返回“first”,第二次调用时返回“second”。可以写n多个.
when(mockedList.get(0)).thenReturn("first").thenReturn("second");
// 但是,如果实际调用的次数超过了stub过的次数,则会一直返回最后一次stub的值。
// 如上例,第三次调用get(0)时,则会返回"second".
}
参数匹配
@Test
public void testArgumentMatcher() {
List mockedList = mock(List.class);
// stubbing using built-in anyInt() argument matcher
when(mockedList.get(anyInt())).thenReturn("element");
// stubbing using custom matcher (let's say MyMatcher() returns your own matcher implementation):
when(mockedList.contains(argThat(new MyMatcher()))).thenReturn(true);
mockedList.contains("element");
// following prints "element"
System.out.println(mockedList.get(999));
// you can also verify using an argument matcher
verify(mockedList).get(anyInt());
// argument matchers can also be written as Java 8 Lambdas
// verify(mockedList).add(someString -> someString.length() > 5);
File mock = mock(File.class); // 首先mock File类。
// 注意new AnyFiles()并不是一个matcher,需要调用argThat(new IsAnyFiles()))才返回一个matcher。
// 下句中stub:当调用renameTo方法时,返回false。该方法参数可以是任意file对象。
when(mock.renameTo(argThat(new AnyFiles()))).thenReturn(false);
mock.renameTo(new File("test"));
// 下句verify renameTo方法被调用了一次,同时输入参数是任意file。
verify(mock).renameTo(argThat(new AnyFiles()));
}
class MyMatcher extends ArgumentMatcher<Object> {
public boolean matches(Object argument) {
if (argument != null && "element".equals(argument.toString())) {
return true;
}
return false;
}
}
class AnyFiles extends ArgumentMatcher<File> {
public boolean matches(Object file) {
return file.getClass() == File.class;
}
}
其他ArgumentMatcher
除了anyInt()之外,还有很多可匹配的参数,如
- anyBoolean()
- isNull()
- anyList()
- booleanThat()
- isA(): //Object argument that implements the given class.
- eq(boolean value): //boolean argument that is equal to the given value
- endsWith(String suffix)
- same(T value)
注意事项
如果在调用方法时需要传入多个参数,其中一个参数使用了argument matcher,那么所有的参数必须都是matcher。不可以matcher和实际的参数混着用
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
//above is correct - eq() is also an argument matcher
verify(mock).someMethod(anyInt(), anyString(), "third argument");
//above is incorrect - exception will be thrown because third argument is given without an argument matcher
do…when形式的stubbing
doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod()
@Test(expectedExceptions = RuntimeException.class)
public void testDoWhen() {
List mockedList = mock(List.class);
doThrow(new RuntimeException()).when(mockedList).clear();
// following throws RuntimeException:
mockedList.clear();
}
Stubing抛出异常
@Test(expectedExceptions = RuntimeException.class)
public void testThrowException(){
List mockedList = mock(List.class);
doThrow(new RuntimeException()).when(mockedList).clear();
//following throws RuntimeException:
mockedList.clear();
}
PowerMock 实例
模拟void方法、静态方法、私有方法、final方法、屏蔽静态块运行、屏蔽类加载器加载特定类,忽略异常(PowerMockIgnore),使用构造方法Mock对象(whenNew)
- 待覆盖类
package xxx;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import xxx.dao.CodeDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
public class SimpleClass {
private static final Logger LOGGER = LoggerFactory.getLogger(CustLevelServiceImpl.class);
private HttpServletRequest httpServletRequest;
@Autowired
private CodeDao codeDao;
/**
* 调用dao层处理业务
*
* @param code key
* @return code实体toString
*/
public String queryCodeString(String code) throws Exception {
// mock httpServletRequest,模拟返回
String req = httpServletRequest.getParameter(code);
// 2个分支都需要覆盖
if (req == null || req.length() == 0) {
return null;
}
// mock 带有返回值的静态方法
String configValue = PropertyUtil.getConfig("1", "1");
if ("1".equals(configValue)) {
return "0000";
}
// mock codeStringDao,模拟返回
String value = codeDao.queryCode(code, code).toString();
// mock void返回值的静态方法
try {
PropertyUtil.setConfig(code, value);
} catch (Exception e) {
throw e;
}
return value;
}
}
- 工具类
package xxx;
public class PropertyUtil {
/**
* 静态块
*/
static {
System.out.println("do some init");
// 阻止其运行
System.out.println(1 / 0);
}
public static String getConfig(String key, String defaultValue) {
return "1";
}
public static void setConfig(String code, String value) throws Exception {
if (code == null) {
throw new Exception();
}
}
}
- 单元测试类
package xxx;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import xxx.dao.CodeDao;
import xxxx.CodeEntity;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
import org.testng.IObjectFactory;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.ObjectFactory;
import org.testng.annotations.Test;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
@PrepareForTest(PropertyUtil.class)
@SuppressStaticInitializationFor("xxx.PropertyUtil")
@PowerMockIgnore("java.lang.*")
public class MockTest {
@InjectMocks
SimpleClass simpleClass;
@Mock
CodeDao codeDao;
@Mock
HttpServletRequest httpServletRequest;
@Mock
Properties properties;
@BeforeClass
public void beforeClass() {
MockitoAnnotations.initMocks(this);
}
@ObjectFactory
public IObjectFactory getObjectFactory() {
return new org.powermock.modules.testng.PowerMockObjectFactory();
}
@Test
public void testQueryCode() {
// mock httpServletRequest 获取参数
when(httpServletRequest.getParameter(anyString())).thenReturn(null).thenReturn("req");
// mock静态方法,有返回值
PowerMockito.mockStatic(PropertyUtil.class);
PowerMockito.when(PropertyUtil.getConfig(eq("1"), eq("1"))).thenReturn("1").thenReturn("0");
// mock dao,模拟数据库返回
when(codeDao.queryCode(anyString(), anyString())).thenReturn(new CodeEntity());
// mock静态方法,无返回值
try {
PowerMockito.doNothing()
.when(PropertyUtil.class, "setConfig", anyString(), anyString());
} catch (Exception e) {
// 忽略
}
// return null
try {
simpleClass.queryCodeString("code");
} catch (Exception e) {
}
// return "0000"
try {
simpleClass.queryCodeString("code");
} catch (Exception e) {
}
// return null
try {
simpleClass.queryCodeString("code");
} catch (Exception e) {
}
// return value
try {
simpleClass.queryCodeString("code");
} catch (Exception e) {
}
}
@Test
public void testQueryCodeStringExp() {
PowerMockito.mockStatic(PropertyUtil.class);
// mock静态方法,抛出异常
try {
PowerMockito.doThrow(new RuntimeException())
.when(PropertyUtil.class, "setConfig", anyString(), anyString());
} catch (Exception e) {
e.printStackTrace();
}
// return null runtimeException
try {
simpleClass.queryCodeString("code");
} catch (Exception e) {
}
}
}
发散
- 抽象类中方法覆盖
可以写一个子类继承抽象类,然后写子类的单测,从而覆盖父类 - 有些变量无法mock
通过反射重置变量,再mock - 大量实体类的getter\setter方法
通过反射,扫描包路径,自动调用
计划
生成单测的自动化工具
参考
推荐阅读
-
testng + mockito + powerMock
-
powermock
-
SpringBoot配置Jacoco生成测试覆盖率报告(包括Powermock、Mockito用例)
-
JUnit + Mockito 单元测试(一)
-
TestNG:一个超越JUnit和NUnit的新测试框架 博客分类: 技术总结其他开源框架 TestNGJUnitNUnit测试框架
-
testng报错:Listener org.uncommons.reportng.HTMLReporter was not found in project's classpath
-
java 与testng利用XML做数据源的数据驱动示例详解
-
Java中用enum结合testng实现数据驱动的方法示例
-
java 与testng利用XML做数据源的数据驱动示例详解
-
Java中用enum结合testng实现数据驱动的方法示例