2025年junit5怎么用(junit4)

junit5怎么用(junit4)p class f center p

大家好,我是讯享网,很高兴认识大家。




讯享网

 <p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0215%2F7955d1c3j00r7b5cz003gd200p000ang00p000an.jpg&thumbnail=660x&quality=80&type=jpg"/><br/></p><p id="0L6LLP8S">作者 | 有尘</p><p id="0L6LLP8U">写在前面:</p><p id="0L6LLP90">对于我们开发人员来说,单元测试一定不会陌生,但在各种原因下会被忽视,尤其是在我接触到的项目中,提测阶段发现各种各样的问题,我觉得有必要聊一下单元测试。</p><p id="0L6LLP92">为了写而写的单元测试没什么价值,但一个好的单元测试带来的收益是非常客观的。问题是怎么去写好单元测试?怎么去驱动写好单元测试?</p><p><strong>一 我们的现状</strong></p><p id="0L6LLP95">现状一:多个项目完全没有单元测试。</p><p id="0L6LLP97">现状二:开发人员没有写单元测试的习惯,或者由于赶业务记录而没有时间去写。</p><p id="0L6LLP99">现状三:单元测试写成了集成测试,比如容器、数据库,导致单元测试运行时间长,失去了意义。</p><p id="0L6LLP9B">现状四:太依赖集成测试。</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0215%2F4a8cf784j00r7b5d0001ld200m600d0g00m600d0.jpg&thumbnail=660x&quality=80&type=jpg"/><br/></p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0215%2F4f9a8fe4j00r7b5d10018d200s800hsg00s800hs.jpg&thumbnail=660x&quality=80&type=jpg"/><br/></p><p id="0L6LLP9H">以上是我在aone找的两个项目的测试情况,基本不考虑单元测试就合并发布,形同虚设。</p><p id="0L6LLP9J">站在开发的角度讲,导致以上问题的原因大概有以下几点:</p><p id="0L6LLP9L"><strong>1.开发成本</strong></p><p id="0L6LLP9N">对于系统初期,可能要花很多时间去写新业务,对于老系统又太过庞大,无法下手。</p><p id="0L6LLP9P"><strong>2.维护成本</strong></p><p id="0L6LLP9R">每修改相关的类,或者重构一次代码,我们就要去修改相应的单元测试。</p><p id="0L6LLP9T"><strong>3.ROI</strong></p><p id="0L6LLP9V">投入产出是不是正收益?可能无论是管理者还是我们开发自己都回质疑这个问题,所以有时候没有强有力的动力。</p><p><strong>二 怎么解决</strong></p><p id="0L6LLPA2">说来说去都是成本的问题,所以我们怎么去解决成本呢?</p><p id="0L6LLPA4">那么,我们一切从最开始说起:开发的成本</p><p id="0L6LLPA6">一个单元测试的传统写法,包含以下几个方面:</p><p id="0L6LLPA8">1.测试数据 (被测数据,和依赖对象)</p><p id="0L6LLPAA">2.测试方法</p><p id="0L6LLPAC">3.返回值断言</p><p id="0L6LLPAE">@Test<br/>public void testAddGroup() {<br/>// 数据<br/>BuyerGroupDTO groupDTO = new BuyerGroupDTO();<br/>groupDTO.setGmtCreate(new Date());<br/>groupDTO.setGmtModified(new Date());<br/>groupDTO.setName("中国");<br/>groupDTO.setCustomerId(customerId);<br/>// 方法<br/>Result result = customerBuyerDomainService.addBuyerGroup(groupDTO);<br/>// 返回值断言<br/>Assert.assertTrue(result.isSuccess());<br/>Assert.assertNotNull(result.getData());<br/>}<br/></p><p id="0L6LLPAG">一个简单的测试还好,但如果是一逻辑复杂,且入参数据复杂的时候,那写起来其实挺头痛的。怎么解放我们程序员的双手?</p><p id="0L6LLPAI">“工欲善其事必先利其器”</p><p id="0L6LLPAK">我们以最大的努力降低我们的开发成本,这就涉及到我们测试框架和工具的选择问题</p><p><strong>1 测试框架选择</strong></p><p id="0L6LLPAN">首先第一个问题就是junit4和junit5的选择,【从junit4到junit5】 我觉得最便利的一个好处就是可以参数化测试,并且基于参数化测试我们可以更加灵活的配置我们的参数。</p><p id="0L6LLPAP">效果如下:</p><p id="0L6LLPAR">@ParameterizedTest<br/>@ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })<br/>void palindromes(String candidate) {<br/>assertTrue(StringUtils.isPalindrome(candidate));<br/>}<br/></p><p id="0L6LLPAT">更好的是,junit5提供了扩展,比如我们常用的json格式。这里我们使用json文件作为输入:</p><p id="0L6LLPAV">@ParameterizedTest<br/>@JsonFileSource(resources = {"/com/cq/common/KMPAlgorithm/test.json"})<br/>public void test2Test(JSONObject arg) {<br/>Animal animal = JSONObject.parseObject(arg.getString("Animal"),Animal.class);<br/>List stringList = JSONObject.parseArray(arg.getString("List"),String.class);<br/>when(testService.testOther(any(Student.class))).thenReturn(stringArg);<br/>when(testService.testMuti(any(List.class),any(Integer.class))).thenReturn(stringList);<br/>when(testService.getAnimal(any(Integer.class))).thenReturn(animal);<br/>String result = kMPAlgorithm.test2();<br/>//todo verify the result<br/>}<br/></p><p><strong>2 mock框架</strong></p><p id="0L6LLPB2">然后就是其他mock类的框架了</p><p id="0L6LLPB4"><strong>Mockito:</strong>语法特别优雅,对于容器类的模拟比较合适,且对于返回值为空的函数调用也提供比较好的断言。缺点是不能模拟静态方法(3.4.x以上版本已支持)</p><p id="0L6LLPB6"><strong>EasyMock:</strong>使用方法类似,但是更严格</p><p id="0L6LLPB8"><strong>PowerMock:</strong>可以作为Mockito的一个补充,比如要测试静态方法,不过不支持junit5</p><p id="0L6LLPBA"><strong>Spock:</strong>基于Groovy语言的单元测试框架</p><p><strong>3 数据库层</strong></p><p id="0L6LLPBD">这里主要介绍一下H2数据库,其基于内存来作为对于关系型数据库的模拟,运行完成自动释放,达到隔离的目的。</p><p id="0L6LLPBF">主要配置:ddl文件路径、dml文件路径。这里不作详述。</p><p id="0L6LLPBH">但对于要不要集成数据库,很难去定义,它的作用主要是用来验证sql语法的问题,但是相对来说较重,建议可以用于轻量级的集成测试。</p><p><strong>三 Junit5 和 Mockito</strong></p><p id="0L6LLPBK">后面讲到的自动生成使用的框架和业界使用最多的都是MocKito,所以这里重点介绍一下,包括使用时遇到的问题。</p><p><strong>1 使用方法</strong></p><p id="0L6LLPBN">分别单独引入依赖,推荐引入最新版</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0215%2F4eacc81cj00r7b5d1001wd200n500khg00n500kh.jpg&thumbnail=660x&quality=80&type=jpg"/><br/></p><p id="0L6LLPBR">1.使用spring-test全家桶</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0215%2F1669b94ej00r7b5d1000od200n5006ng00n5006n.jpg&thumbnail=660x&quality=80&type=jpg"/><br/></p><p id="0L6LLPBV">junit5的使用方法这里就不多做介绍,主要说一下这个ArgumentsProvider接口,实现它就可以自定义参数化类,类似于自带的ValueSource、EnumSource等。</p><p><strong>2 Mockito 主要注解介绍</strong></p><p id="0L6LLPC2">先问为什么,为什么需要Mockito</p><p id="0L6LLPC4">因为:现在的java项目几乎离不开spring框架,而其最为著名的就是IOC,所有的bean用容器来管理,所以这给我们单元测试带来一个问题,如果要对bean做单元测试,就需要启动容器,那么带来的时间的开销将会很大。所以Mockito给我门带来了一系列的解决方法,让我们可以轻松的对bean 进行测试。</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0215%2F5abeccb3j00r7b5d2000xd200mz00ixg00mz00ix.jpg&thumbnail=660x&quality=80&type=jpg"/><br/></p><p id="0L6LLPC8">假设我们要对上面的A.func()进行单元测试。</p><p id="0L6LLPCA"><strong>@InjectMocks 注解</strong></p><p id="0L6LLPCC">表示需要注入bean的类,有两种</p><p id="0L6LLPCE">1.被测试类,这种很容易理解,我们测试这个类,当然也需要向其注入bean。比如上面的A</p><p id="0L6LLPCG">2.被测试类中的,需要执行其真实的方法,但其里面也要主要bean,也就是上面的C,我们需要测试neeExec方法,但我们不关系B的具体细节。现实中比如事物,并发锁等。这一类需要Mockito.spy(new C())的形式,不然会报错</p><p id="0L6LLPCI"><strong>@Mock</strong></p><p id="0L6LLPCK">表示要mock的数据,也就是不真实执行其方法内容,只按照我们的规则执行,或者返回,比如使用when().thenReturn()语法。</p><p id="0L6LLPCM">当然也可以,执行真实方法,则需要when().thenCallRealMethod()方式。</p><p id="0L6LLPCO"><strong>@Spy</strong></p><p id="0L6LLPCQ">表示所有方法都走真实方式,比如有些工具类,转换类,我们也写成了bean的形式(严格来说这种需要写成静态工具类)。</p><p id="0L6LLPCS">@ExtendWith(MockitoExtension.class)<br/>public class ATest {<br/>@InjectMocks<br/>private A a=new A();<br/>@Mock<br/>private B b;<br/>@Spy<br/>private D d;<br/>@InjectMocks<br/>private C c= Mockito.spy(new C());;</p><p id="0L6LLPCT">@BeforeEach<br/>public void setUp() throws Exception {<br/>MockitoAnnotations.openMocks(this);<br/>}<br/>@ParameterizedTest<br/>@ValueSource(strings = {"/com/alibaba/cq/springtest/jcode5test/needMockService/A/func.json"})<br/>public void funcTest(String str) {<br/>JSONObject arg= TestUtils.getTestArg(str);<br/>a.func();<br/>//todo verify the result<br/>}</p><p><strong>3 Mockito和junit5常见问题</strong></p><p id="0L6LLPD1"><strong>mock 静态方法</strong></p><p id="0L6LLPD3">mockito3.4以后开始支持,之前的版本可以使用PowerMock辅助使用</p><p id="0L6LLPD5"><strong>Mockito 版本和 java 版本兼容问题</strong></p><p id="0L6LLPD7">报错如下</p><p id="0L6LLPD9">Mockito cannot mock this class: xxx<br/>Mockito can only mock non-private &amp; non-final classes.</p><p id="0L6LLPDB">原因是2.17.0及之前的版本与java8是兼容的</p><p id="0L6LLPDD">但2.18之后需要使用java11,为了在java8中使用Mockito,则需要引入另一个包</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0215%2F0ej00r7b5d2000jd200n1005sg00n1005s.jpg&thumbnail=660x&quality=80&type=jpg"/><br/></p><p id="0L6LLPDH"><strong>Jupiter-api 版本兼容问题</strong></p><p id="0L6LLPDJ">Process finished with exit code 255<br/>java.lang.NoSuchMethodError: org.junit.jupiter.api.extension.ExtensionContext.getRequiredTestInstances()Lorg/junit/jupiter/api/extension/TestInstance</p><p id="0L6LLPDL">第一个问题是因为junit5中api、engine、params版本不一致导致的。</p><p id="0L6LLPDN">第二个问题是因为jupiter-api版本太低的问题,5.7.0以后的版本才支持。</p><p><strong>四 测试代码自动生成</strong></p><p id="0L6LLPDQ">选好了框架,我们还是没有解决我们的问题,“怎么节约开发成本?” ,这一节我们来谈这个问题,这也是我主要想表达的。</p><p id="0L6LLPDS">对于写单元测试,一直以来是比较头痛的事情,要组装各种各样的数据,可能还没跑成功,就被一堆“xxxx不能为null”的报错搞烦了。因此我们有理由去设想,有没有办法去解决这件事情。</p><p><strong>1 业界和集团方案调研</strong></p><p id="0L6LLPDV">在做这个事情之前,肯定是要调研有没有现成的框架。答案是有,但很遗憾,没有找到完全契合我想要的效果,我们来看一下这些插件:</p><p id="0L6LLPE1">public class BaseTest {<br/>protected TestService testService;<br/>public String baseTest() {<br/>return testService.testBase(1); // 4<br/>}<br/>}<br/>public class JCode5 extends BaseTest {<br/>public void testExtend(){<br/>String s = testService.testOther(new Student()); //1<br/>// 调用 另一个方法<br/>System.out.println(testBean());<br/>// 调用基类方法<br/>baseTest();<br/>}<br/>// 使用testService<br/>public String testBean() {<br/>testService.testMuti(new ArrayList() {{add(1);}}, 2); //2<br/>return testService.getStr(12); //3<br/>}<br/>/<br/>* 测试范型类<br/>*/<br/>public void testGeneric(Person person) {<br/>//test<br/>list.stream().forEach(a -&gt; {<br/>System.out.println(a);<br/>});<br/>for (int i = 0; i &lt; 2; i++) {<br/>Long aLong = testService.getLong("1213"<br/>, "12323");<br/>System.out.println(aLong);<br/>}<br/>System.out.println(testBean());<br/>}<br/>}<br/>public class TestService {<br/>public String testBase(Integer integer) {<br/>return "TestBase";<br/>}<br/>public List testMuti(List a, Integer c) {<br/>List res = new ArrayList&lt;&gt;();<br/>res.add(a.toString() + c + "test muti");<br/>return res;<br/>}<br/>public String getStr(Integer integer) {<br/>return "TestService" + getInt();<br/>}<br/>public String testOther(Student student) {<br/>return student.getAge() + "age";<br/>}</p><p id="0L6LLPE4">如上,testExtend一共调用了testService的4个方法,我们对比下各个插件生成的代码。</p><p id="0L6LLPE6"><strong>TestMe</strong></p><p id="0L6LLPE8">@Test<br/>void testTestExtend() {<br/>when(testService.getStr(anyInt())).thenReturn("getStrResponse");<br/>when(testService.testMuti(any(), anyInt())).thenReturn(Arrays.asList("String"));<br/>when(testService.testOther(any())).thenReturn("testOtherResponse");<br/>jCode5.testExtend(Integer.valueOf(0));<br/>}<br/>@Test<br/>void testTestGeneric() {<br/>when(testService.getStr(anyInt())).thenReturn("getStrResponse");<br/>when(testService.getLong(anyString(), anyString())).thenReturn(Long.valueOf(1));<br/>when(testService.testMuti(any(), anyInt())).thenReturn(Arrays.asList("String"));</p><p id="0L6LLPE9">jCode5.testGeneric(new Person());<br/>}<br/></p><p id="0L6LLPEB">1.生成的代码基本符合逻辑,包括需要mock的bean的逻辑都生成了。</p><p id="0L6LLPED">2.但它把最重要的一环,也就是数据省略了,只是单纯的用了构造函数的形式。这显然对于我们DDD模型不适应。</p><p id="0L6LLPEF">3.另外他没用用到junit5的一些特性,比如参数化测试。</p><p id="0L6LLPEH">4.对于testExtend的方法,它只识别了3个方法。没有识别父类的调用。</p><p id="0L6LLPEJ"><strong>JunitGenerate</strong></p><p id="0L6LLPEL">只能生成基础的框架代码,对于我想mock的逻辑、以及测试方法都没有生成,用处不大。</p><p id="0L6LLPEN">@Test<br/>public void testTestExtend() throws Exception {<br/>//TODO: Test goes here...<br/>}<br/></p><p id="0L6LLPEP"><strong>Squaretest</strong></p><p id="0L6LLPER">生成的方法非常丰富,且一个非常厉害的一点,它能生成多个分支,比如代码逻辑中有if条件,它能生成两个测试,从而走不通的分支。</p><p id="0L6LLPET">但是,最大的缺点是“收费软件,不开源”,这就决定了我们没法用它,除非是特别需要。另外测试用过程中还发现了一些其他问题,比如对于继承,重载之类的问题,它解决的也不是很好,往往识别不了需要调用的方法。</p><p id="0L6LLPEV">虽然无法使用,但还是可以借鉴。</p><p><strong>五 打造代码自动生成**方案</strong></p><p id="0L6LLPF2">既然市场上的插件都不是特别合适,就决定写一个适合自己项目的插件(暂时命名JCode5)。有兴趣的也可以自己试试。</p><p><strong>1 插件安装</strong></p><p id="0L6LLPF5">idea插件市场下载,搜索JCode5</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0215%2Fb9457cf6j00r7b5d3001rd200q100iag00q100ia.jpg&thumbnail=660x&quality=80&type=jpg"/><br/></p><p><strong>2 插件使用</strong></p><p id="0L6LLPFA">插件有三个功能</p><p id="0L6LLPFC">1.生成测试代码,也就是生成单元测试。</p><p id="0L6LLPFE">2.生成json数据,通常用来生成测试数据,比如model。用来参数化测试。</p><p id="0L6LLPFG">3.增加测试方法,随着业务开发,类可能增加一下功能方法,这个时候相应的可以增加测试方法</p><p id="0L6LLPFI">定位到需要测试的类,快捷键或菜单定位到generater,如下,选择JCode5.</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0215%2Fae38a960j00r7b5d3001td200u000dpg00u000dp.jpg&thumbnail=660x&quality=80&type=jpg"/><br/></p><p id="0L6LLPFM"><strong>生成测试类</strong></p><p id="0L6LLPFO">目前支持三个选项,后续会逐渐完善</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0215%2F63d1c879j00r7b5d4001id200n100c6g00n100c6.jpg&thumbnail=660x&quality=80&type=jpg"/><br/></p><p id="0L6LLPFS">另外两个功能类似,直接尝试使用一下就行。</p><p id="0L6LLPFU"><strong>生成的结果---类+json数据</strong></p><p id="0L6LLPG0">@ParameterizedTest<br/>@ValueSource(strings = {"/com/cq/common/JCode5/testExtend.json"})<br/>public void testExtendTest(String str) {<br/>JSONObject arg= TestUtils.getTestArg(str);<br/>Integer i = arg.getInteger("Integer");<br/>// 识别泛型活着集合类<br/>List stringList = JSONObject.parseArray(arg.getString("List"),String.class);<br/>String stringArg = arg.getString("String");<br/>String stringArg1 = arg.getString("String");<br/>String stringArg0 = arg.getString("String");<br/>// 识别四个方法,包括父类调用、其他方法调用<br/>when(testService.testBase(any(Integer.class))).thenReturn(stringArg);<br/>when(testService.testMuti(any(List.class),any(Integer.class))).thenReturn(stringList);<br/>when(testService.getStr(any(Integer.class))).thenReturn(stringArg0);<br/>when(testService.testOther(any(Student.class))).thenReturn(stringArg1);<br/>jCode5.testExtend(i);<br/>//todo verify the result<br/>}<br/></p><p id="0L6LLPG2">如上除了生成基本的代码,另外会生成测试数据,它会将该方法所需要的测试数据全都生成在一个json文件当中,完全实现</p><p id="0L6LLPG4">“数据和代码的分离”</p><p id="0L6LLPG6">如testExtend.json:</p><p id="0L6LLPG8">{<br/>"Integer":1,<br/>"String":"test",<br/>"List":[<br/>"test"<br/>]<br/>}</p><p id="0L6LLPGA"><strong>补充判定语句</strong></p><p id="0L6LLPGC">这一块前期考虑对于不同的方法有不同的校验,所以目前想的还是开发者自己去写验证代码。</p><p id="0L6LLPGE"><strong>注意事项</strong></p><p id="0L6LLPGG">在自动生成完代码之后,虽然可以运行,但如我们前面提到的,为了写单元测试而写的单元测试是没什么价值的,我们的最终目的是为了写一个好的测试。代码自动生成,但它终究能力有限,所以还是需要我们自己再去验证,比如:</p><p id="0L6LLPGI">1.该插件生成的代码需要junit5和mockito的支持,使用时需要引入相关的依赖</p><p id="0L6LLPGK">2.增加assert校验逻辑,看是不是想要的结果,目前插件不会自动生成assertEquals等断言代码。</p><p id="0L6LLPGM">3.运用参数化测试能力,复制一份生成的json文件并修改输入数据,多组测试</p><p><br/><strong>3 插件实现介绍</strong></p><p id="0L6LLPGO">主要的实现思路,参考了dubbo的SPI的源码,也就是自动实现自适应SPI那部分,简单点说就是反射获取代码逻辑,然后生成测试代码。</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0215%2F3be9111aj00r7b5d4002rd200u000jfg00u000jf.jpg&thumbnail=660x&quality=80&type=jpg"/><br/></p><p><strong>4 后期规划</strong></p><p id="0L6LLPGT">1.mock数据可定制,目前的想法是</p><p><ul><li id="0L6LLPHP"></p><p id="0L6LLPGV">固定值比如目前的String: test、Integer和boolean: 0、1</p><p></li></ul></p><p><ul><li id="0L6LLPHQ"></p><p id="0L6LLPH1">测试者使用配置模版,比如txt文件包含keyValue对</p><p></li></ul></p><p><ul><li id="0L6LLPHR"></p><p id="0L6LLPH3">使用Faker,对于name、email、phone这种特定倾向的数据进行特色自动生成</p><p></li></ul></p><p id="0L6LLPH5">2.自动分支测试,这一块的想法目前主要针对if来做,需要一定的时间。</p><p id="0L6LLPH7">3.其他</p><p><strong>六 写在最后</strong></p><p id="0L6LLPHA">对于代码自动生成,还是有很多东西可以做的,但有些问题还尚待解决,希望能尽最大努力解放我们的双手,也能提高我们单元测试的质量。</p><p id="0L6LLPHC">已在我们项目中使用此模式增加135个测试用例(除去mock的单模块达到70%):速度比集成测试(pandora、spring等)提升一个等级。代码的覆盖率相对可观。</p><p class="f_center"><img src="https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F0215%2F4bc865dfj00r7b5d5001dd200u000c4g00u000c4.jpg&thumbnail=660x&quality=80&type=jpg"/><br/></p><p id="0L6LLPHK"><strong>从现在开始 学习技术</strong><br/></p><p id="0L6LLPHN"><strong>↓ 2022年 阿里云最新、最热、最全的活动已全面开启</strong></p> 

讯享网
小讯
上一篇 2025-04-23 07:01
下一篇 2025-06-10 21:55

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/193672.html