2026/5/18 13:28:39
网站建设
项目流程
东莞网页制作与网站设计,网站维护正常要多久,html网站标签,可以做c oj的网站前言
测试代码的写法可以归纳为三部分
第一部分#xff1a; 准备测试数据和定义mock行为
第二部分#xff1a; 调用真实的函数
第三部分#xff1a; 调用验证函数进行结果的验证
Junit4
在模块的test路径下编写测试案例。在类中使用Test注解#xff0c;就可以告诉Jun…前言测试代码的写法可以归纳为三部分第一部分 准备测试数据和定义mock行为第二部分 调用真实的函数第三部分 调用验证函数进行结果的验证Junit4在模块的test路径下编写测试案例。在类中使用Test注解就可以告诉Junit这个方法是测试方式。同时使用assert*方法可以调用Junit进行结果的验证。Testpublic void test() {assertEquals(abc, getActual());}Junit常用注解除了Test注解还有以下常见的注解可供使用注解作用备注BeforeClass会在所有的方法执行前被执行static 方法 全局只会执行一次而且是第一个运行全局的含义是在一个测试类中AfterClass会在所有的方法执行之后进行执行static 方法 全局只会执行一次而且是最后一个运行Before会在每一个测试方法被运行前执行一次 可以用来清理执行环境保证测试用例在执行前具有干净的上下文After会在每一个测试方法运行后被执行一次可选使用Junit的Rule简化代码和Junit的Before和After分别作用于每一个单元测试案例的开始和结束类似Rule注解提供了同样的能力但有一个好处就是执行前执行单元测试和执行后在同一个方法中包含在同一个上下文中这能让我们更加灵活的处理单元测试。使用起来也比较简单第一步实现TestRule接口public class MethodNameExample implements TestRule {Overridepublic Statement apply(Statement base, Description description) {//想要在测试方法运行之前做一些事情就在base.evaluate()之前做String className description.getClassName();String methodName description.getMethodName();base.evaluate(); //这其实就是运行测试方法//想要在测试方法运行之后做一些事情就在base.evaluate()之后做System.out.println(Class name: className , method name: methodName);return base;}}第二步在Test类中使用。加上Rule注解即可Rulepublic MethodNameExample methodNameExample new MethodNameExample();使用Parameterized特性减少重复测试用例(Junit5自带,Junit4需额外引入依赖)根据不同的输入待测试函数会有不同的输出结果那么我们就需要针对每一类的输入编写一个测试用例这样才能覆盖待测函数的所有逻辑分支。(写多少个测试用例能够覆盖全所有的逻辑分支可称之为待测函数的圈复杂度).使用Junit4提供的Parameterized Tests特性可以帮助我们减少用例编写的代码使测试类更清晰简单而且数据可以从CSV文件导入。以下提供一个例子(官方例子见参考资料[11])第一步引入依赖testImplementation(rootProject.ext.dependencies.jupiter)第二步测试类中添加注解RunWith(Parameterized.class)public class BioTest {}第三步可以定义实例变量明确输入和输出。比如这里我们定义了2个变量一个是预期的输出结果一个是输入的参数。private boolean expectedResult;private String inputTime;第四步定义构造函数在构造函数中对变量赋值public BioPayProviderTest2(boolean expectedResult, String time) {this.expectedResult expectedResult;this.time time;}第五步定义数据集使用注解标注返回一个数组数组代表的就是Junit4需要提供给构造函数进行实例化的数据集。Parameterized.Parameterspublic static CollectionObject[] data() {return Arrays.asList(new Object[][]{{true, null},{false, System.currentTimeMillis() :},{true, (System.currentTimeMillis() - 73 * 3600) :}});}第六步编写测试用例。对于以下的测试用例Junit4会使用第五步的数据进行填充执行3次。Testpublic void test() throws Exception {boolean ret needShowDialog(time, mockWalletId);Assert.assertEquals(expectedResult, ret);}MockitoMockito是目前使用比较广泛的Mock框架他可以根据需要mock出虚假的对象在测试环境中可以用来替换掉真实的最像达到两大目的验证这个对象的某些方法的调用情况调用了多少次参数是什么等等指定这个对象的某些方法的行为返回特定的值或者是执行特定的动作使用Mockito mock对象的某些方法的行为// 使用mock函数即可模拟一个对象这个对象的实例变量均为默认值BioGuidePresenter presenter Mockito.mock(BioGuidePresenter.class);// 设定这个mock对象被调用某一方法时应该返回的结果Mockito.when(presenter.checkIsEnrolled(1)).thenReturn(true);使用Mockito Spy对象// 使用spy函数即可模拟一个对象这个对象的实例变量均为默认值BioGuidePresenter presenter Mockito.spy(BioGuidePresenter.class);// 设定这个mock对象被调用某一方法时应该返回的结果Mockito.when(presenter.checkIsEnrolled(1)).thenReturn(true);spy()和mock()的区别在于未指定mock方法的返回值时默认返回null而为指定spy方法的返回值时默认执行目标方法的逻辑并返回对应逻辑执行的结果。另外有一个很重要的区别在于使用spy的情况下虽然提供了函数的模拟实现但Mockito框架仍然会调用真实的代码所以如果真实代码无法在单测下运行则使用spy模拟会导致测试失败。使用Mockito验证结果// 验证mock的database对象调用setUniqueId方法时的入参是否为12verify(database).setUniqueId(ArgumentMatchers.eq(12));// 验证mock的database对象的getUniqueId方法是否被调用2次verify(database, times(2)).getUniqueId();// 验证mock的database对象的getUniqueId方法是否被调用1次verify(database).getUniqueId();// 也可以使用传统的Junit判断方法判断结果是否符合预期assertEquals(foo, spy.get(0));使用Mockito-inline模拟静态方法Mockito版本升级之后支持对Static Method做Hook。前提是在build.gradle中引入Mockito-inline。org.mockito:mockito-inline:${version}以下是实例被测代码当我们在测试类中调用doSomethings()很可能无法通过调整MemoryService的返回值控制doSomthing2的入参从而覆盖更多的逻辑分支。此时我们就需要Hook DataEngine甚至是MemoryService获取我们想要的返回值。public void doSomethings() {DataEngine.getMemoryService().saveCacheObject(key, abc);...String a DataEngine.getMemoryService().getCacheObject(key);doSomething2(a);}下面给出使用mockito-inline对静态方法的处理步骤。一Hook静态方法。使用Java7的try-with-resource语法模拟触发静态方法DataEngine.getMemoryService的行为。需要注意的是mockService可以是通过Mockito mock出来的也可以是我们创建的一个真实的MemoryService子类区别在于使用Mockito mock的MemoryService我们不需要实现所有的方法只需要mock我们测试类中可能调用到的方法。MemoryStoreService mockService Mockito.mock(MemoryStoreService.class);try (MockedStaticDataEngine service Mockito.mockStatic(DataEngine.class)) {service.when(DataEngine::getMemoryService).thenReturn(mockService);}二.使用更加智能的模拟返回方法。我们使用较多的是thenReturn()方法但是在本案例的场景下我们需要功能更强大的返回方法。因为处理模拟的入参1. MemoryService::saveCacheObject返回值是Void所以无法使用thenReturn()2. 我们需要处理入参针对每一个saveCacheObject的模拟调用我们都需要真实的将其保存到Map中final MapString, Object pools new HashMap();//当触发了mockService的saveCacheObject方法就会回调answer()从而将入参的Key和Value保存到Map中Mockito.doAnswer(new Answer() {Overridepublic Object answer(InvocationOnMock invocation) throws Throwable {pools.put((String) invocation.getArgument(0), invocation.getArgument(1));return null;}}).when(mockService).saveCacheObject(Mockito.anyString(), Mockito.any());当我们使用doAnswer模拟了saveCacheObject那我们很有可能需要使用同样的策略模拟getCacheObject。就像这样Mockito.when(mockService.containsCachedObject(Mockito.anyString())).thenAnswer(invocation - pools.containsKey(invocation.getArgument(0)));使用Mockito测试异步代码段假如需要测试一段异步代码可以使用标准的异步代码测试步骤进行。举例如下public void update(ListDemo demos) {repo.refresh(demos, () - {doSomething();});}针对上述代码测试的基本思路是步骤一 模拟一个异步回调函数//1.判断需要的回调函数的类型创建ArgumentCaptorArgumentCaptorRepo.OnRefreshListener captor ArgumentCaptor.forClass(Repo.OnRefreshListener.class);//2.主动调用相应函数触发Mockito框架的执行流进行到回调函数同时将captor.capture()作为入参传入。Mockito.verify(repo).refresh(Mockito.anyList(), captor.capture());//3.通过captor.getValue()模拟异步回调函数Repo.OnRefreshListener mockListener captor.getValue();步骤二 主动调用异步回调接口从而使执行流进入回调函数中//主动调用异步函数接口使得测试执行流进入函数体mockListener.onResult();步骤三 判断是否执行了doSomething()方法或者执行结果是否符合预期的其他判断方式。使用Mockito-inline测试静态方法的异步代码段假如需对以下代码进行单元测试我们就需要用到mockito-inline.可以看到RPC请求是通过一个静态方法发出并且通过异步回调的形式返回结果。public void demo(String id) {RpcService.send(new DemoReq(id), new RpcCallbackDemoResp() {Overridepublic void onFailure(BaseReq call, String msg, String procCd, String procSts, Exception e) {if(listener ! null){listener.onFailure(msg, procCd, procSts);}}Overridepublic void onResponse(BaseReq call, WalletDetailRespMsg response) {if(listener ! null){listener.onSuccess(response);}}});}具体写法关键在定义拦截后的行为invacation保留了调用信息根据序号获取入参可以对入参进行判断之后就可以主动调用回调函数。try (MockedStaticRpcService rpcMock Mockito.mockStatic(RpcService.class)) {//告诉mockito遇到RpcService.send(参数任意参数任意)的时候拦截rpcMock.when(() - RpcManager.sendFromNative(Mockito.any(), Mockito.any())).then(invocation - {//拦截之后会进入到这里。//invocation会保留调用的信息。通过getArgument可以获取入参RpcCallback callback1 invocation.getArgument(1, RpcCallback.class);//主动调用callback可以指定回调入参callback1.onResponse(Mockito.mock(BaseReq.class),Mockito.mock(WalletDetailRespMsg.class));return null;});//主动调用被测方法presenter.refreshWalletDetail(testWalletId, callback);Mockito.verify(callback).onSuccess(Mockito.any());}使用Kotlin封装Mockito-inline单测公用方法如上我们若使用java的try-catch-resources会显得代码臃肿于是我们可以尝试Kotlin简化。 对于try-catch-resourceskotlin中的等价写法是使用use//mock了Apps.getApp()这个静态方法的返回结果。传入一个高阶函数易于进行串联调用。fun getAppMock(action: () - Any?) {Mockito.mockStatic(Apps::class.java).use { appUtilsMock -appUtilsMock.whenVoid { Apps.getApp() }.thenReturn(null)action()}}如果我们封装了大量的公用mock代码那么一段测试代码就长这样Testfun reduceWithUserRejectTest() {val change HceDefaultChange(true)getAppMock {isNfcDefaultPaymentMockStatic(true) {checkNetMockStatic(true) {val actual: PaymentPageState change.reduce(PaymentPageState())Assert.assertTrue(actual.showWaving)}}}}是不是和写Flutter或者Compose的UI页面一样啦~Roboletric如果不测试Activity页面则不建议使用Roboletric一是因为mockito已经能够完成几乎全部的工作并不需要用到Roboletric二是用Roboletric影响测试执行速度。编写可运行的Roboletric单元测试方法// 首先需要添加RobolectricTestRunner作为运行Roboletric的启动器RunWith(RobolectricTestRunner.class)// 其次需要使用Config配置本次单元测试的基础配置。// 1. 如果你的电脑上运行的JAVA版本不是11以上则需要指定sdk版本为Android 9.0以下// 2. 可以指定shadows。shadows下文会详细解析这里可配置可不配置取决于具体场景// 3. qualifiers可以配置机器的尺寸多语言环境等可配置可不配置取决于具体场景。例子中指定了中文环境Config(sdk {Build.VERSION_CODES.O_MR1},shadows {DemoShadow.class},qualifiers zh)public class DemoTest {}使用Roboletric模拟ActivityRoboletric的一大特点就是可以模拟Android的context。 我们可以再Before注解的方法中使用Roboletric创建一个ActivityBeforepublic void initActivity() {//Intent可选Intent faceIntent new Intent();faceIntent.putExtra(DEMO, uri.toString());activity Robolectric.buildActivity(VerificationBioGuideActivity.class, faceIntent).create().resume().get();}Roboletric调用buildActivity即可模拟一个Activity调用create可以触发onCreate回调调用resume可以触发onResume回调最后调动get就可以拿到这个activity对象。拿到activity的对象之后我们就可以通过activity进行一些操作了。例如获取View的控件获取字符串等。// 获取View控件TitleBar titleBar (TitleBar) activity.findViewById(R.id.title_bar);// 获取字符串activity.getString(R.string.verification_bio_pay_title_finger_success_tips)可以使用Roboletric模拟出来的activity作为context如果只需要用到applicaitonContext可以使用RuntimeEnvironment.getApplication()Roboletric的杀手锏——ShadowsRobolectric的本质是在Java运行环境下采用Shadow的方式对Android中的组件进行模拟测试从而实现Android单元测试。Shadows的作用就是使用自定义的方法和类替换原先业务的方法和类原理就是使用字节码修改技术进行动态的修改。例如业务中A.class原先要调用B.class的C()函数我们使用Shadows并定义一个函数签名一样的函数D()将其作用于C()函数上。当触发A.class的调用后程序执行流程会进入D()函数中。自定义Shadows简介第一步 使用Implements类定义需要被替换的目标类。Shadows类本身也是一个普通的Java类只不过多了Implements注解所以他也可以实现接口继承等功能。// 表明这个Shadow期望模拟ActualClass类的行为Implements(ActualClass.class)RequiresApi(api Build.VERSION_CODES.N)public class ActualClassShadow {}第二步 与目标函数使用相同的函数签名并且增加Implementation注解。可以只替换目标类中的部分函数而其他函数仍然遵循目标类的逻辑。//表明需要模拟ActualClass中的containsKey方法Implementationpublic boolean containsKey(String key) {}第三步可选__constructor__可以替换构造函数public void __constructor__(int x, int y) {realPoint.x x;realPoint.y y;}第四步可选使用RealObject可以获取目标对象RealObject private Point realPoint;第五步定义好Shadows之后我们需要再测试类的Config注解中进行注册。Config(shadows {ActualClassShadow.class})使用Chat-GPT生成单元测试案例紧跟时事我们可以使用Chat-GPT快速生成测试案例分为以下几步第一步让Chat-GPT知晓该函数的意图第二步告诉Chat-GPT单元测试的目标Optional可以指定Chat-GPT使用Junit4的一些测试特性简化单测案例序号Chat-GPTInput1java (输入待测代码)whats the authors intentions2The authors intention with this Java code is to provide a methodhexStringToByteArray that can convert a hexadecimal string to abyte array. The method takes a single argument data, which is thehexadecimal string to be converted. The code first checkswhether the input string is valid and has an even number ofcharacters, since each pair of characters in the string representsa single byte. If the input string is valid, the code creates a new byte arrayresult with a length equal to half the length of the input string(since each pair of characters represents one byte). It then loopsover the input string in pairs, converting each pair of charactersto a byte and storing it in the result array. If the input string is not valid, the code logs an error message andreturns null. Overall, the intention of this method is to provide a simple andreliable way to convert hexadecimal strings to byte arrays inJava.3A good unit test suite should aim to:- Test the functions behavior for a wide range of possible inputs- Test edge cases that the author may not have foreseen- Take advantage of the features ofParameterizedto make thetests easy to write and maintain- Be easy to read and understand, with clean code anddescriptive names- Be deterministic, so that the tests always pass or fail in the same wayJunithas many convenient features that make it easy to write and maintain unit tests. Well use them to write unit tests for the function above.For this particular function, well want our unit tests to handle the following diverse scenarios4Since the code provided is in Java, well write our unit tests inJava using the JUnit framework. Here are some example unittests we might write for the hexStringToByteArray function: RunWith(Parameterized.class)public class DesUtilsHexStringToByteArrayTest { Parameterized.Parameters public static CollectionObject[] data() { return Arrays.asList(new Object[][]{ // Test cases where input is valid {00, new byte[]{0x00}}, {7f, new byte[]{0x7f}}, {deadbeef, new byte[]{(byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef}}, {0102030405060708090a0b0c0d0e0f, new byte[]{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}}, // Test cases where input is invalid {, null}, {0, null}, {1, null}, {a, null}, {g, null}, {gg, null}, {000, null}, {001, null}, {00g, null}, {0x00, null}, {deadbeef0, null} }); } private String input; private byte[] expected; public DesUtilsHexStringToByteArrayTest(String input, byte[] expected) { this.input input; this.expected expected; } Test public void testHexStringToByteArray() { byte[] actual hexStringToByteArray(input); assertArrayEquals(expected, actual); }} In this example, were using JUnits Parameterized runner togenerate multiple test cases from a single test method. The datamethod returns a collection of test cases, each represented as anarray of input parameters and expected output. We have included test cases for a wide range of possible inputs,including valid input strings with different lengths, as well asinvalid input strings that dont have an even number ofcharacters, contain non-hexadecimal characters, or exceed themaximum length that can be converted to a byte array. Weve used descriptive names for our test class, test method,and test parameters to make the tests easy to read andunderstand. Finally, our tests are deterministic, since they will always pass or fail in the same way.感谢每一个认真阅读我文章的人礼尚往来总是要有的虽然不是什么很值钱的东西如果你用得到的话可以直接拿走这些资料对于【软件测试】的朋友来说应该是最全面最完整的备战仓库这个仓库也陪伴上万个测试工程师们走过最艰难的路程希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取