UT
Jacoco统计覆盖率
实际上借助了jacoco插件
jacoco的参考链接:https://blog.csdn.net/yangbo10086/article/details/89198626
https://mp.weixin.qq.com/s?__biz=MzIxNzEyMzIzOA==&mid=2652314564&idx=1&sn=a93e6154c92acaef9204b8440e66a852&scene=21
出现统计为0的情况是因为JaCoCo和PowerMock冲突了,JaCoCo会忽略注解@PrepareForTest({})里面的类,解决办法是用JaCoCo的离线模式。
目前项目pom中UT配置
两个文件路径中,about文件夹中查看具体到类的UT覆盖率
配置完成后,执行mvn clean test -Dmaven.test.skip=false,将生成的覆盖率报告文件夹中的index.html打开,即可看到覆盖率
另一种方法:IDEA中在对应模块下运行test即可查看UT
UT背景知识
Mock:针对有依赖的UT而言
new能否成功完全看包含的域当中有没有依赖
对于没有任何依赖,场景不复杂的代码,写UT是很简单的。但是作为一款产品的代码,大部分代码会依赖或者引用了平台、第三方或者服务的相关对象,因此产品中的大部分对.java文件,如果直接new一个对象,然后运行,一般会出现大堆的异常, 这是因为该对象所以依赖的外部环境不存在(这些依赖的外部对象可能是corba接口,平台提供的服务等等),如图1。
图1 非集成环境下运行异常原因示意图
从图1可以看出,直接new的对象在运行时依赖的NetWorkI/O、Lib、FileI/O和Env Info等对象方法在非集成环境下是不具备的,因此直接运行一定会抛异常。
那么如何对这部分的代码进行UT呢?必须借助与第三方的UT框架。借助于第三方的组件可以去模拟这些外部依赖。然后通过适当的抽象实现,将这个外界环境整合成一个模拟框架模型(MFM,Mock Framework Model),将此段代码跟外界环境的交互统统移交到这个框架模型中,如图2.
图2 MFM框架下的运行示意图
通过MOCK,我们的任何一个直接new的对象就可以顺利的运行下去了,然后针对某个方法就可以进行测试。
Mock框架的选型
在Java 的TDD 领域已经有很多的Mock 框架,比如EasyMock、JMock、Mockito、 PowerMock。推荐大家使用PowerMock。
PowerMock是对现有Mock框架的扩展和进一步封装,可以解决EasyMock,JMock,Mockito等不能达成的效果。目前PowerMock封装的UT框架如图3:
图3 PowerMockUT框架示意图
PowerMock 主要围绕着Junit 测试框架和TestNg 测试框架进行,其中在每个框架下面对所涉及的Mock 框架仅仅支撑EasyMock 和Mockito,因为PowerMock 对所依赖的Jar 包非常的苛刻,如果出现某个依赖包的冲突或者不一致都会出现不能使用的情况,因此请选择官网上提供的PowerMock 套件。
EasyMock的语法不大好懂,因此推荐使用选择了Junit+PowerMock+Mockito的组合的UT框架。
UT不是Mock,而Mock恰恰是为UT服务的. Mock的核心就是解依赖.(下方链接中的第七章)
什么时候需要mock?
测试我们核心功能的过程中用到了许多资源、许多方法,这个时候想要避开这些方法与资源,就用mock的方式直接模拟跳过该部分的执行,直接虚拟出我们想要的结果,让函数执行下去。
基于Groovy的Spock
需要区分单元测试与集成测试之间的区别。
比如,从某个service抛出Exception,Controller层捕捉并包装,然后到GlobalExceptionHandler中,要测这一整条链路,就是集成测试,需要借助SpringTest进行。
当前的Groovy与后面的各种Mock,都只是用于单元测试的一种技术手段,无法完成集成测试。
Spock的特点如下:
- 让我们的测试代码更规范,内置多种标签来规范单测代码的语义,从而让我们的测试代码结构清晰,更具可读性,降低后期维护难度
- 提供多种标签,比如: where、with、thrown… 帮助我们应对复杂的测试场景
- 再加上使用groovy这种动态语言来编写测试代码,可以让我们编写的测试代码更简洁,适合敏捷开发,提高编写单测代码的效率
- IDE兼容性好,自带mock功能
参考
Spock官方文档翻译版,对spock系统介绍,主要内容:
- 对文件操作的几个demo
- 基本语法:setup、when、then、expect、where、cleanup语句块
- 常用注解、常用方法
- Mock方法介绍等
官方正式版
Spock系列文章:针对异常、静态方法、条件分支、void方法等都有例子。
mock实例
// 由于mock静态变量,所以用这个runner
@RunWith(PowerMockRunner.class)
// 准备静态变量
@PrepareForTest([Static.class])
class SaaSSystemExceptionTest {
// 初始化时依赖如下@Mock的内容,需要用这种方式完成初始化
@InjectMocks
ExtendExpireTimeExecutor extendExpireTimeExecutor
@Mock
LeadRepository leadRepository
@Before
void 'setup'() {
// mock static的准备
MockitoAnnotations.initMocks(this)
PowerMockito.mockStatic(Static.class)
}
@After
void 'cleanup'() {
}
/**
* Junit的UT
*/
@Test
void wrapInfoTest1() {
MyException e = new MyException(CommonErrorCode.DEFAULT_SYSTEM_ERROR)
.pushLocalMap("operation", "test")
.wrap("method not support." + this.getClass().getSimpleName() + ".execute",
"检查LeadConvertExecutor的调用是否正确");
Assert.assertEquals("检查errorMsg", "test", e.getErrorMsg());
Assert.assertEquals("检查getCauseMsg", "method not support.SaaSSystemExceptionTest.execute",
e.getCauseMsg());
Assert.assertEquals("检查suggesion", "检查LeadConvertExecutor的调用是否正确", e.getSuggestion());
}
@Test
void 'HandlerTest'() {
given:
PowerMockito.when(Static.translate(Matchers.any(LanguageEnum.class), Matchers.any(I18nString.class)))
.thenReturn("库容校验失败")
SaaSBizException e = new MyException(AccountErrorCode.CAPACITY_CHECK_FAILED)
SaaSSystemException e2 = new MyException(CommonErrorCode.DEFAULT_BIZ_ERROR).wrap(e)
then:
e2.getCauseMsg() == "库容校验失败"
}
@Test
// mock私有方法
void "getFullInfoWithException1Test"() {
given:
def companyName = "山东遨游汽车部件有限公司"
MetisAdapter metisAdapter1 = PowerMockito.spy(new MetisAdapter())
VerifyCompanyBasicInfoResp verifyCompanyBasicInfoResp = new VerifyCompanyBasicInfoResp()
verifyCompanyBasicInfoResp.setBaseResp(new BaseResp())
// 如下方法能够真正mock成功
PowerMockito.doReturn(verifyCompanyBasicInfoResp).when(metisAdapter1, "verifyCompanyBasicInfo", companyName)
// 如下不行
// PowerMockito.when(metisAdapter1, "verifyCompanyBasicInfo", companyName).thenReturn(new VerifyCompanyBasicInfoResp())
JudgeResult metisResult = JudgeResult.builder().msg(I18nConstants.ACCOUNT_NAME_METIS_CANNOT_PASS).build()
expect:
metisAdapter1.checkMetis(companyName) == metisResult
}
// mock static:如果mock static对象时,使用到了内部的一些字段,而这些字段又依赖于初始化时的Spring注入或者其他特殊设定,那么调用该私有方法时就会出现问题。https://www.jianshu.com/p/44fcadaf041d
@PrepareForTest({UserService.class})
// test方法体中
// mock 并设定期望返回值
PowerMockito.mockStatic(Instant.class);
PowerMockito.when(Instant.now()).thenReturn(moment);
}
class ExtendExpireTimeExecutorTest extends Specification {
// 初始化时依赖如下@Mock的内容,需要用这种方式完成初始化
@InjectMocks
ExtendExpireTimeExecutor extendExpireTimeExecutor
@Mock
LeadRepository leadRepository
// 需要注入真正的对象而不是mock值
LeadDomainService leadDomainService = new LeadDomainService()
// 等价于@Before
def 'setup'() {
MockitoAnnotations.initMocks(this)
Mockito.when(leadRepository.load(Matchers.anyString(), Matchers.anyObject())).thenReturn(mockLead())
// 通过whitebox方式注入
Whitebox.setInternalState(extendExpireTimeExecutor, "leadDomainService", leadDomainService)
}
def "ExecuteWithExceptionTest"() {
when:
extendExpireTimeExecutor.execute(new ExtendExpireTimeCmd())
then:
def e = thrown(RuntimeException)
e.message != ""
}
def "ExecuteTest"() {
when:
ExtendExpireTimeCmd expireTimeCmd = new ExtendExpireTimeCmd()
expireTimeCmd.setLeadID("test")
expireTimeCmd.setAuthRequest(new AuthRequest())
expireTimeCmd.setExtendExpireTime(LocalDateTime.now())
extendExpireTimeExecutor.execute(expireTimeCmd)
then:
1 == 1
}
def 'mockLead'() {
return [
master : [
entityName : EntityConstants.LEAD_ENTITY,
sysName : SystemConstants.SYSTEM_NAME,
tenantCode : 'ToB_Bytedance_New2',
primaryKey : 6916865817787237133L,
recordType : "first_lead",
fieldValues: [
[
fieldName : "ownerId",
fieldValue: 1
] as CustomField
],
] as CustomRecord,
details: []
] as Lead
}
不能extends Specification,与junit4不兼容,导致报错:
也无法找到@Test标注的UT方法。
去除该extends即可。
PowerMock详解
PowerMock的使用详解:https://www.cnblogs.com/hunterCecil/p/5721468.html
mock一些复杂方法时
https://www.jianshu.com/p/44fcadaf041d
实例
// 对象的mock,尤其是接口对象mock:
JobExecutionContext context = PowerMockito.mock(JobExecutionContext.class);
// 静态方法:
@PrepareForTest( {FileUtil.class})
PowerMockito.mockStatic(FileUtil.class);
PowerMockito.when(FileUtil.fileUrl(Mockito.any())).thenReturn("src/test/resources/PurgeTable.yml");
对于无返回值的类,可以通过检测内部静态变量来看执行某个方法前后的变化:
Field[] fields = myObject.getClass().getDeclaredFields();
for (Field field : fields) {
if ("TARGET".equalsIgnoreCase(field.getName())) {
field.setAccessible(true);
List<PurgeTable> fieldValue = (List) field.get(purgerDataPipeline);
Assert.assertEquals(fieldValue.size(), 2);
Assert.assertTrue(fieldValue.get(0).getSourceName().equalsIgnoreCase("DB"));
}
}
Whitebox详解:注入属性等
whitebox的核心是对私有类UT的测试,可以获取私有属性值,测试私有方法等,实际上powermock也提供了对应功能的API,二者可以互相替换
String[] ssoAddr = {"/sso", "/addr"};
Whitebox.setInternalState(instance, "SSO_GUI_ADDRS", ssoAddr);
filterUtil.addBlackList(".*\\.{2,}.*$");
Whitebox.setInternalState(loginController, "whiteurl", filterUtil);
MockMVC写法参考
https://www.cnblogs.com/lyy-2016/p/6122144.html
Mockito指南
https://www.letianbiji.com/java-mockito/mockito-@injectmocks.html
powermock与mockito之间的异同:https://www.jianshu.com/p/51930cc5dcf9
尤其是Mockito.any()常用于mock方法的入参。
spy:只mock部分方法,其余方法按照真实执行
https://www.letianbiji.com/java-mockito/mockito-thencallrealmethod.html
spy 和 mock不同,不同点是:
• spy 的参数是对象实例,mock 的参数是 class。
• 被 spy 的对象,调用其方法时默认会走真实方法。mock 对象不会。
无构造器的类mock
错误的尝试:
IgniteCache<String, String> cache = spy(IgniteCache.class);
cache.put(user1, string1);
cache.put(user2, string2);
误以为cache能够像一个真实对象一样,里面填充了key、value,对应的get、containsKey方法都可以呈现对应结果。实际上,当调用如下函数就可以看到真正的cache是什么。
cache.put(user2, string1);
构造的cache只是java的一个object对象,无法做任何真实的cache能够做的操作
因此只能通过打桩的方式构造get、containKeys的返回值,即:
when(cache.get(user1)).thenReturn(loginErrorCache1);
在这里补充一个知识点是:mock与spy之间的不同,mock是纯粹打桩,不走真实的方法;spy则是借助构造出来的对象走真实的方法。
Powermockito典型的静态方法、类内对象、类内对象方法mock实例
@RunWith(PowerMockRunner.class)
@PrepareForTest({XXX.class})
@PowerMockIgnore({"javax.management.*", "javax.security.*"})
public class XXServiceTest {
private static final String PARAMETER_REQUIRED = "parameter is required";
@Autowired
private XXXRepository xxxRepository;
private XXService xxService;
@Before
public void setUp() {
Ignite ignite = PowerMockito.mock(Ignite.class);
// mock了一个模拟对象,所有方法都是打桩操作,即所有内部方法都需要mock,如果没有mock,将会报错。
// 适用于系统自动生成或者其他无法初始化一个具体对象的类
IgniteAtomicSequence NOTICE_SEQ = PowerMockito.mock(IgniteAtomicSequence.class);
// 同上
PowerMockito.mockStatic(XXX.class);
// 静态方法
try {
PowerMockito.doReturn(ignite).when(XXX.class, "getBean", "igniteInstance");
//静态方法的mock
} catch (Exception e) {
e.printStackTrace();
}
PowerMockito.when(ignite.atomicSequence("xxxSeq", 0, true)).thenReturn(NOTICE_SEQ);
// 模拟对象只能通过mock
PowerMockito.when(NOTICE_SEQ.incrementAndGet()).thenReturn(100l);
xxService = new XXService();
portalNoticeRepository = PowerMockito.mock(XXXRepository.class);
Whitebox.setInternalState(xxService, "xxxRepository", xxxRepository);
//注入模拟对象,该模拟对象起作用的方法只有mock出来的方法
Whitebox.setInternalState(xxService, "ignite", ignite);
}
@Test
public void writeNoticeTest() {
XXInfo xxInfo = new XXInfo();
xxInfo.setX1("x1");
xxInfo.setX2("x2");
xxInfo.setX3("x3");
Optional<PortalNotice> optional = Optional.empty();
PowerMockito.when(xxRepository.findById(Mockito.any())).thenReturn(optional);
PowerMockito.when(xxRepository.save(Mockito.anyMap())).thenReturn(null);
Map<Long, XXNotice> map = xxService.writeNotice("xx", xx);
}
}
UT类的注解
@RunWith(PowerMockRunner.class)
@PrepareForTest({XXX.class}) // 需要mock静态方法的类
@PowerMockIgnore({"javax.management.*", "javax.security.*"})
常用注解
• @BeforeClass : 用于方法注释,该方法在所有测试方法运行前运行,且只运行一次, 添加该注释的方法必须修饰为 public static void 且没有参数。
• @Before :用于方法注释,表示该方法在每个测试方法执行前执行一次,可用于一些初始工作。
• @Test : 方法注释,表示测试方法。
• @Ignore:方法注释,表示会被忽略的测试方法
• @After : 方法注释,被注释的方法会在每个测试方法执行完成之后执行一次,如果其它的方法抛出了异常,该方法同样会被执行。主要用于释放在@Before方法中初始化的资源。
• @AfterClass :方法注释,功能同@After,只不过是该方法释放的是@BeforeClass方法初始化的资源。且在所有的测试方法执行完成之后,只执行一次。
• 一个UT的单元测试用例执行顺序为:
@BeforeClass –> @Before –> @Test –> @After –> @AfterClass
before这些注解的执行顺序
https://blog.csdn.net/qq_42901761/article/details/93473068
junit 常用注解 + junit 断言详解
https://www.cnblogs.com/jingjiren/p/10339039.html
JUnit测试控制@Test执行顺序的三种方法
1.@FixMethodOrder(MethodSorters.JVM)
从上到下 执行@Test
2.@FixMethodOrder(MethodSorters.NAME_ASCENDING) (推荐)
按方法名字顺序执行@Test
3.@FixMethodOrder(MethodSorters.DEFAULT)
默认方法,不可预期
参考:
cnblogs.com/drizzlewithwind/p/5774581.html
JUnit测试异常
https://blog.csdn.net/Dream_Weave/article/details/83860631
测试自定义异常:
https://blog.csdn.net/spidercoco/article/details/22722699
Jmeter测试
https://blog.csdn.net/meifannao789456/article/details/82459035?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161848521116780255214097%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=161848521116780255214097&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-82459035.pc_search_result_before_js&utm_term=jmeter%E8%8F%9C%E9%B8%9F%E5%85%A5%E9%97%A8%E5%88%B0%E8%BF%9B%E9%98%B6
多线程测试资料
PowerMockito版本的多线程测试:
https://www.cnblogs.com/charlexu/archive/2013/01/11/2856643.html
Junit4版多线程测试
https://blog.csdn.net/neven7/article/details/45555687?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-1
countdownlatch多线程测试:
了解下几个关键的api,例子不看
https://blog.csdn.net/weixin_38003389/article/details/85096530
要参考的例子:
https://blog.csdn.net/jek123456/article/details/64920025
https://blog.csdn.net/u010166386/article/details/68923517
第二个列子中所有线程停止在begin.await(),当begin.countDown()之后,同时启动游戏。
比并发编程的艺术程序清单4-18的begin()要好些。
通过countdownlatch,实现UT中多线程的同时启动与全部线程结束