UT:Jacoco统计插件、Groovy(Spock)、PowerMock、Whitebox、MockMVC、Mockito、PowerMockito、Junit介绍与使用

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的特点如下:

  1. 让我们的测试代码更规范,内置多种标签来规范单测代码的语义,从而让我们的测试代码结构清晰,更具可读性,降低后期维护难度
  2. 提供多种标签,比如: where、with、thrown… 帮助我们应对复杂的测试场景
  3. 再加上使用groovy这种动态语言来编写测试代码,可以让我们编写的测试代码更简洁,适合敏捷开发,提高编写单测代码的效率
  4. IDE兼容性好,自带mock功能

参考

Spock官方文档翻译版,对spock系统介绍,主要内容:

  1. 对文件操作的几个demo
  2. 基本语法:setup、when、then、expect、where、cleanup语句块
  3. 常用注解、常用方法
  4. 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指南

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中多线程的同时启动与全部线程结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值