引入pom.xml
<properties>
<mockito.version>3.8.0mockito.version>
<powermock.version>2.0.9powermock.version>
properties>
<dependency>
<groupId>org.mockitogroupId>
<artifactId>mockito-inlineartifactId>
<version>${mockito.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.mockitogroupId>
<artifactId>mockito-coreartifactId>
<version>${mockito.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.powermockgroupId>
<artifactId>powermock-module-junit4artifactId>
<version>${powermock.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.powermockgroupId>
<artifactId>powermock-module-junit4-commonartifactId>
<version>${powermock.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.powermockgroupId>
<artifactId>powermock-api-mockito2artifactId>
<version>${powermock.version}version>
<scope>testscope>
<exclusions>
<exclusion>
<artifactId>mockito-coreartifactId>
<groupId>org.mockitogroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.powermockgroupId>
<artifactId>powermock-coreartifactId>
<version>${powermock.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-testartifactId>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>commons-beanutilsgroupId>
<artifactId>commons-beanutilsartifactId>
<version>1.9.4version>
dependency>
<build>
<finalName>demofinalName>
<plugins>
<plugin>
<groupId>org.jacocogroupId>
<artifactId>jacoco-maven-pluginartifactId>
<version>0.7.8version>
<executions>
<execution>
<goals>
<goal>prepare-agentgoal>
<goal>reportgoal>
goals>
execution>
executions>
plugin>
plugins>
build>
在代码里我们时常会有一些配置类需要提前加载,我们可以使用@TestConfiguration 注解 来配置一些配置类的bean
我们使用的rpc 服务也可以在这边进行注解生成 进行mock
例如
/**
相关配置类
*/
@TestConfiguration
public class PeiZhiTestConfig {
//配置一
@MockBean
private PeiZhiOneConfig peiZhiOneConfig;
//rpc服务的代理类
@MockBean
private RemoteRpcAgent remoteRpcAgent;
@Before
public void before() {
MockitoAnnotations.openMocks(PeiZhiOneConfig.class);
MockitoAnnotations.openMocks(remoteRpcAgent.class);
}
}
//以spring方式启动
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = ApplicationStarter.class)
//自动创建一个mvcmock上下文
@AutoConfigureMockMvc
//导入配置
@Import(value = { PeiZhiTestConfig.class})
//测试使用配置文件
@TestPropertySource(locations = {"classpath:application-dev.properties"})
// 启用环境名
@ActiveProfiles(profiles = "dev")
//测试使用事务以免污染数据
@Transactional
// 将这些类交给系统的类加载器加载.而不是powermock
@PowerMockIgnore({"org.apache.*", "javax.xml.*", "org.xml.sax.*", "org.w3c.dom.*", "org.apache.log4j.*", "org.slf4j.*", "javax.*", "java.lang.*", "com.sun.*"})
public class BaseJunitTest {
@Test
public void testInit() {
System.out.println("基础测试");
}
}
//以powerMockRunner启动
@RunWith(PowerMockRunner.class)
//powermock代理掉springjunit4 用于生成其他
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
//忽略掉以下包, 交给springjunitrunner执行
@PowerMockIgnore({"org.apache.*", "javax.xml.*", "org.xml.sax.*", "org.w3c.dom.*",
"org.apache.log4j.*", "org.slf4j.*", "javax.*", "java.lang.*", "com.sun.*"})
public class BaseJunitTest {
@Test
public void testInit() {
System.out.println("基础测试");
}
}
RemoteRpcAgent代码
用于代理RPC接口
@Slf4j
@Component
public class RemoteRpcAgent{
@Reference( version = "1.0")
private TestDubboService testDubboService;
/**
* 调用 doSomething RPC
*/
public BaseResponse<Boolean> doSomething(String test){
BaseResponse<Boolean> response;
try {
response = testDubboService.doSomething(ids);
if (Objects.isNull(response) || !response.isSuccess()) {
log.warn(LogUtils.msg2LogMessage("doSomething_fail||ids={}", JsonUtils.toJson(ids)).toString());
}
}catch (Exception e){log.error(LogUtils.msg2LogMessage(e.getMessage()).toString(), e);
e.printStackTrace();
throw new BizException("dosomething异常");
}
return response;
}
}
具体的测试类
public class ConstructTest extends BaseJunitTest {
//如果想调用Contronller 层的方法使用injectMocks
@InjectMocks
private TestController testController;
//想生成bean 方法用
@MockBean
private TestCall testCall;
@Resource
private MockMvc mockMvc;
@Resource
private RemoteRpcAgent remoteRpcAgent;
//加载桩
@Before
public void setUpData() {
System.out.println("before");
//意思,当调用remoteRpcAgent类的doSomething方法 的时候,传入any() 任何参数 ,都返回BaseResponse.true 这个结果
when(remoteRpcAgent.doSomething(any())).thenReturn(BaseResponse.build(true));
//丢出RuntimeException 当TestRepository执行testJob()方法的时候
doThrow(RuntimeException.class).when(TestRepository).testJob();
}
//期望获取异常
@Test(expected = TestException.class)
public void test_exceptedException() {
TestRepository.testJob();
}
/**
* 测试接口
*/
@Test
@SqlGroup(value = {
@Sql(scripts = {
"/test.sql",
})
})
public void test_testurl() throws Exception {
String jsonString="{\"name\":\"测试\"};
MvcResult mvcResult = mockMvc.perform(
//post 方法 /get方法
MockMvcRequestBuilders.post("/testurl")
//路径参数
.param("param1", "0")
//头参数
.header("header1", "0")
//发送请求类型
.contentType(MediaType.APPLICATION_JSON_UTF8)
//期望获取结果类型
.accept(MediaType.APPLICATION_JSON))
//body参数
.content(jsonString)
//期望获取结果
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
// 得到返回代码
int status = mvcResult.getResponse().getStatus();
// 得到返回结果
String content = mvcResult.getResponse().getContentAsString();
Assert.assertEquals(200, status);
}
}
我们想要提高单元测试覆盖率会发现还有大量的实体类没有走过,由其是当使用lomcok 插件生成的, 简直惨不忍睹所以提供实体类测试工具
主要思想是通过反射获取类/方法/参数 进行生成测试
实体类工具
public class EntityTestUtils {
//实体化数据
private static final Map<String, Object> STATIC_MAP = new HashMap<String, Object>();
//忽略的函数方法method
private static final String NO_NOTICE = "getClass,notify,notifyAll,wait,clone";
static {
STATIC_MAP.put("java.lang.Long", 1L);
STATIC_MAP.put("java.lang.String", "test");
STATIC_MAP.put("java.lang.Integer", 1);
STATIC_MAP.put("int", 1);
STATIC_MAP.put("long", 1);
STATIC_MAP.put("java.util.Date", new Date());
STATIC_MAP.put("char", '1');
STATIC_MAP.put("java.util.Map", new HashMap());
STATIC_MAP.put("boolean", true);
}
/**
* @param CLASS_LIST 类列表
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws InstantiationException
*/
public static void runTest(List<Class> CLASS_LIST)
throws IllegalAccessException, InvocationTargetException, InstantiationException, CloneNotSupportedException, NoSuchMethodException {
for (Class temp : CLASS_LIST) {
Object tempInstance = new Object();
//获取声明的内部类
List<Class> d = Lists.newArrayList(temp.getDeclaredClasses());
for (Class c :
d) {
Object innerInstance = new Object();
int i = c.getModifiers();
String s = Modifier.toString(i);
//获取默认的构造函数
Constructor con2 = c.getDeclaredConstructors()[0];
//设置可进入
con2.setAccessible(true);
innerInstance = con2.newInstance();
executeMethod(c, innerInstance);
}
//执行构造函数
Constructor[] constructors = temp.getConstructors();
for (Constructor constructor : constructors) {
final Class<?>[] parameterTypes = constructor.getParameterTypes();
if (parameterTypes.length == 0) {
tempInstance = constructor.newInstance();
} else {
Object[] objects = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
objects[i] = STATIC_MAP.get(parameterTypes[i].getName());
}
tempInstance = constructor.newInstance(objects);
}
}
executeMethod(temp, tempInstance);
}
}
/**
* 执行函数中的所有方法
*/
private static void executeMethod(Class temp, Object tempInstance)
throws IllegalAccessException, InvocationTargetException {
//执行函数方法
Method[] methods = temp.getMethods();
for (final Method method : methods) {
if (NO_NOTICE.contains(method.getName())) {
break;
}
final Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 0) {
Object[] objects = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
if ("equals".equals(method.getName()) && parameterTypes[i]
.getName() instanceof java.lang.Object
) {
objects[i] = JsonUtil.clone(tempInstance, temp);
} else {
objects[i] = STATIC_MAP.get(parameterTypes[i].getName());
}
}
method.invoke(tempInstance, objects);
} else {
method.invoke(tempInstance);
}
}
测试类
public class EntityTest extends BaseJunitTest {
@Test
public void entityTest()
throws IllegalAccessException, InstantiationException, InvocationTargetException, CloneNotSupportedException, NoSuchMethodException {
List<Class> list = new ArrayList<>();
//这里加要测试的类就行了
list.add(Resp.class);
EntityTestUtils.runTest(list);
}
}
jacoco 引入后, 会在IDEA的右侧的maven 插件列表中发现jacoco 的插件
我们只要双击install 命令或执行 mvn clean install 则会自动运行生成/target/site/jacoco 目录, 打开jacoco.html 就可以查看你的单元测试覆盖率了
ReflectionTestUtils.setField(targetObject, "merchantPartnerId", "123");
使用mockito报can not find lambda cache for this entity
解决方案
//放到 @BeforeClass 或 @BeforeAll 注解的静态方法内。
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), xxxxDO.class);
参考 github
idea 快速生成测试方法 command+N键 →Geneartor → Test → 勾选 set up /tearDown →勾选members →Ok
日志包冲突等
jacoco 还可以集成进 sonar 中, 提高代码质量