Java用正确的姿势写单元测试以及mock

1. 前言

对于一些简单的功能或业务,我们也许可以通过前端调试、postman等接口工具、main函数调用进行测试。但这每次改动代码都要人力测试,耗费大量的人力资源且不高效,真正的项目中单元测试是必不可少的。

2. 要点

单元测试的三步走:

1、组装方法入参

2、执行方法

3、对方法的执行结果进行断言(Assert)比对

建议把所有实际操作数据的测试方法上面加上事务注解。

对于会抛异常的情况,需要用try包住,在catch中断言异常文本,以及在try下面加上Assert.fail(),由于catch捕获的是Exception,而Assert.fail()抛出的是Error,因此如果代码没有抛异常并走到了Assert.fail(),单测会报错,即和预期不同。

3. 入门级知识点

3.1 常用注解

@SpringBootTest:把当前类标记为测试类并交给spring管理,通常和@RunWith(SpringRunner.class)一起出现。另如果需要初始化环境变量,建议先定义一个父测试类,加上上面两个注解,写一个静态内部类System.setProperty("key","value")。其他测试类继承即可。

@Test:用在方法上,标记为测试方法。注意必须是public。

@Before:所有单元测试前都会执行一遍。注意:启动单元测试有两种方式,启动方法和启动类,启动类会自动执行测试类下的所有测试方法

@After:所有单元测试后都会执行一遍。

测试方法的执行顺序是:构造方法->@Before->@Test->@After

上面两个方法在Junit5种被换为@BeforeEach和@AfterEach。

另外Junit5还新加了几个注解,比如:@DisplayName("对测试方法重命名");@BeforeAll、@AfterAll。@BeforeAll会放在构造方法前,@AfterAll会放在@After后;且两个都是静态方法,整个测试类只会执行一此。

3.2 测试私有方法

ReflectionTestUtils.invokeMethod(类对象,"私有方法名",.....私有方法入参);

另外如果此私有方法里面有spring的bean比如类对象,需要在调用invokeMethod之前注入类对象:

ReflectionTestUtils.setField(类对象,"类对象内需要注入的类名",需要注入的类);

4. mock

对于一些外部接口,可能无法控制本地环境能收到结果,或者干脆就没必要真实去调用,就可以采用mock的方法,模拟出接口返回值。

本文主要用org.mockito包进行实例说明。

4.1 基本写法:

4.1.1 mock常见的写法有两种:

Mockito.doReturn(1)when(demoService).demoMethod("a");

when(demoService.demoMethod("a")).thenReturn(1);

表示同一个意思:当执行demoService类的demoMethod方法,且入参是a时,将会返回1。生效时间是这行代码到本测试方法结束。

4.1.2 任意入参

可以mock任意入参时都会返回设定的返回值。

any():任意字符串

anyLong():任意Long类型

更多略

4.1.3 mock返回值

除了thenReturn,还有thenThrow,另外.thenCallRealMethod代表返回真实结果。

4.2 mock注入

如果要对一个类使用mock,需要先改变类的注入方式,常见的注解有:

@Mock:对函数的调用均使用mock,不会调用真实方法

@Spy:对函数的调用是真实调用,即代码会真实走到方法内部。

@InjectMocks:可以调用真实代码的方法,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。

@MockBean:spring封装的@mock,会加入spring容器

@SpyBean:spring封装的@Spy,会加入spring容器

4.3 Mock高级用法

4.3.1 捕获入参

定义:

private ArgumentCaptor<DTO> ar = ArgumentCaptor.forClass(DTO.class);

使用:

Mockito.doReturn(true).when(demoClient).demoMethod(ar.capture());

校验:

List<DTO> dtoList = ar.getAllValues();  // 这里的值是方法入参

由此可以得到mock时的入参,一般用于定时任务时校验深层方法的入参是否正确。

4.3.2 mock静态方法

MockedStatic<Util> theMock = Mockito.mockStatic(Util.class);

theMock.when(()->Util.getTime(any())).thenReturn(new Date());

另外因为是mock静态方法,如果一个测试类中静态方法被多个方法mock,会抛异常,解决办法有两种:

1、手动调用close()。

2、把第一行包在try with resources块的小括号里面,把第二行写在try(){}的大括号里面,代表只有块里面的需要mock。

4.3.3 mock私有方法

需要引入PowerMockito包,具体使用可另行百度。

4.3.4 mock Http请求

代码中一些http调用,用上述的方法无法覆盖行和伪造返回值,因此需要一个专门mock Http请求的方法。

代码示例:

    @Test
    public void test() throws IOException, InterruptedException {
        // 创建一个MockWebServer
        try(MockWebServer server = new MockWebServer()) {
            // 设置一个MockResponse
            String body = "hello, world!";
            server.enqueue(new MockResponse().setResponseCode(200)
                    .addHeader("Content-Type","application/json").setBody(body));

            // 启动MockWebServer
            server.start();

//            Map<String, String> headerMap = new HashMap<>();
//            headerMap.put("Content-Type","application/json");
//            String httpResult = httpService.sendGet("/api/test/hello", headerMap);


            // 创建一个OkHttpClient
            OkHttpClient client = new OkHttpClient();

            // 创建一个Request
            Request request = new Request.Builder()
                    .url(server.url("/api/test/hello").toString())  // 使用MockWebServer的URL
                    .build();

            // 发送请求并获取Response
            Response response = client.newCall(request).execute();

            // 验证Response
            System.out.println(response.body().string());  // 输出:hello, world!

            // 获取并验证RecordedRequest
            RecordedRequest recordedRequest = server.takeRequest();
            System.out.println(recordedRequest.getMethod());  // 输出:GET
            System.out.println(recordedRequest.getPath());    // 输出:/api/test/hello
        }
    }

上述代码的

创建一个OkHttpClient、创建一个Request、发送请求并获取Response

部分是为了正常运行,实际应该换成注释的代码部分,调用业务接口,由业务接口内部发送http请求。

原理是MockWebServer是一个模拟服务器,在上述try中的放入enqueue的请求会发送到模拟虚拟器中,得到mock的返回值。

  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值