美文网首页
微服务架构下单元测试落地实践(下)

微服务架构下单元测试落地实践(下)

作者: gigglesoso | 来源:发表于2020-09-28 20:33 被阅读0次

    相关阅读:
    微服务架构下单元测试落地实践(上)

      上篇介绍了单元测试的相关概念以及微服务架构下单元测试的mock框架的选型,该篇主要针对项目中涉及到的单元测试的场景进行代码演示,首先对使用的PowerMock框架进行简单的介绍。

    一、PowerMock简介

    1.概念

      Mockito是java流行的一种mock框架,mock技术的使用可以让我们单元测试隔离外部依赖,免除了复杂外部依赖的构建工作,使得单元测试更加容易进行。其工作原理是创建依赖对象的一个Proxy对象,调用时,对依赖对象的调用都是经过Proxy对象,Proxy对象拦截了所有的请求,只是按照预设值返回。但是Mockito在单元测试时,有一些场景无法解决,它不提供对静态方法、构造方法、私有方法以及 Final 方法的模拟支持,因此这里就需要PowerMock了,可以把PowerMock理解为Mockito的增强。

    2.核心API和注解

    A a = PowerMockito.mock(A.Class)    
    PowerMockito.when(a.method()).thenReturn(obj)
    

      上面两行代码包含了两层含义,第一行代码表示创建了A的一个虚拟对象a;第二行指定了这个虚拟对象的一个行为,在调用a的method()方法时,返回了一个obj对象。这两行代码其实已经完成了外部依赖的模拟过程。

    @Mock:创建一个虚拟对象
    @InjectMocks:创建一个实例,其余用@Mock(或@Spy)注解创建的对象将被注入到用该实例中
    

      这两个核心注解用来在单元测试类中构建类的依赖关系,不需要依赖spring环境,使得单元测试更轻量级。

    3. 使用

    使用很简单,直接在pom中引入相应的依赖即可,需要注意的一点,由于PowerMock依赖Mockito,因此两者依赖需要同时引入,注意两者版本有严格的对应关系,应当按照官方指定的版本依赖关系才能保证正常使用。具体版本可以参考官方文档:https://github.com/powermock/powermock/wiki/Mockito

    <properties>
            <mockito.version>2.8.9</mockito.version>
            <powermock.version>1.7.4</powermock.version>
    <properties/>
    
    <dependencies>
            <dependency>
                <groupId>org.powermock</groupId>
                <artifactId>powermock-module-junit4</artifactId>
                <version>${powermock.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.powermock</groupId>
                <artifactId>powermock-api-mockito2</artifactId>
                <version>${powermock.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.mockito</groupId>
                <artifactId>mockito-core</artifactId>
                <version>${mockito.version}</version>
                <scope>test</scope>
            </dependency>
    <dependencies/>
    

    二、第三方依赖和复杂对象构建

    1. Dao接口模拟

    @Service
    public class ConsumerService {
    
        @Autowired
        private ConsumerMapper consumerMapper;
    
        public String helloDB(String name){
            System.out.println("调用数据库前的业务逻辑……");
            String result = consumerMapper.callDB(name);
            System.out.println("调用数据库后的业务逻辑……");
            return result;
        }
    }
    
    @Mapper
    public interface ConsumerMapper {
        String callDB(String name);
    }
    

    单元测试类如下,该测试类及后续的示例中只展示对应情形下所需要注入的类和依赖:

    // 表明当前单元测试类使用PowerMock框架
    @RunWith(PowerMockRunner.class)
    public class PowerMockDaoTest {
    
        // 创建一个consumerService
        @InjectMocks
        ConsumerService consumerService;
    
        // 创建一个虚拟的consumerMapper
        @Mock
        ConsumerMapper consumerMapper;
    
        @Before
        public void setup(){
            // 根据类的不同注解,创建不同的mock对象,同时完成装配工作,即整个单元测试的初始化
            MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void test() {
            String name = "hulu";
            // 指定虚拟的的consumerMapper的行为,调用callDB("hulu")时,返回hello hulu, this is DB
            PowerMockito.when(consumerMapper.callDB(name)).thenReturn("hello hulu, this is DB");
            // 调用service方法
            String result = consumerService.helloDB(name);
            //最后这里加上断言,进行判断执行结果是否达到我们的预期
            Assert.assertEquals("hello hulu, this is DB",result);
        }
    
    }
    

    以上是经常需要mock的一种情况,service中调用了dao接口,使用mock后,可以在调用dao接口方法时,让该方法按照我们指定的入参返回想要的返回值,这个过程是不需要真实的拥有数据库环境,实现屏蔽数据库的第三方依赖的情况。

    下图展示了真实调用过程中执行到callDB("hulu")时,consumerMapper的真实状态


    可以看出,这时的consumerMapper其实是一个MockitoMock生成的代理对象,并不是真实的连接数据库的mapper,这就是mock框架能屏蔽第三方依赖的原理。

    2. feign远程调用

    下面是微服务中,使用feign调用远程服务的示例代码:

    // service方法中通过helloRemote调用了一个远程服务
    @Service
    public class ConsumerService {
    
        @Autowired
        HelloRemote helloRemote;
      
        public String helloRemote(@PathVariable("name") String name) {
            System.out.println("其他业务逻辑");
            String hello = helloRemote.hello(name);
            System.out.println("其他业务逻辑");
            return hello;
        }
    }
    
    // feign接口
    @FeignClient(name="service-producer",fallback=HelloRemoteFallback.class)   
    public interface HelloRemote {
        @RequestMapping(value="/hello")
        String hello(@RequestParam(value = "name") String name);
    }
    

    单元测试示例如下,和上述的屏蔽数据库依赖是很类似的:

    @RunWith(PowerMockRunner.class)
    public class PowerMockFeignTest {
    
        @InjectMocks
        ConsumerService consumerService;
    
        @Mock
        HelloRemote helloRemote;
    
        @Before
        public void setup(){
            MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void test() {
            String name = "hulu";
            String mockResult = "hello hulu, this is fallback";
            PowerMockito.when(helloRemote.hello(name)).thenReturn(mockResult);
            // 真实调用service方法
            String result = consumerService.helloRemote(name);
            //最后这里加上断言,进行判断执行结果是否达到我们的预期
            Assert.assertEquals(mockResult,result);
        }
    
    }
    

    3. redis访问

    分布式系统中redis是最常用的中央缓存,以下是对redis的mock过程。
    示例代码:

    @Service
    public class ConsumerService {
        // 这里直接使用redisTemplate操作redis,实际项目中可能会再封装一层
        @Autowired
        private RedisTemplate<String, String> redisTemplate;
      
        public String helloRedis(String key){
            System.out.println("调用redis前的业务逻辑……");
            String result = redisTemplate.opsForValue().get(key);
            System.out.println("调用redis后的业务逻辑……");
            return result;
        }
    }
    
    @RunWith(PowerMockRunner.class)
    public class RedisTest {
    
        @InjectMocks
        ConsumerService consumerService;
    
        @Mock
        private RedisTemplate<String, String> redisTemplate;
    
        @Before
        public void setup(){
            // 这句话执行以后,redisTemplate自动注入到consumerService中
            MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void test() {
            String key = "test";
            // 对redisTemplate.opsForValue().get("key")进行mock   这也是一个级联方法的mock
            ValueOperations valueOperations = PowerMockito.mock(ValueOperations.class);
            PowerMockito.when(redisTemplate.opsForValue()).thenReturn(valueOperations);
            PowerMockito.when(valueOperations.get("test")).thenReturn("this is mock redis");
            // 真实调用service方法
            String result = consumerService.helloRedis(key);
            //最后这里加上断言,进行判断执行结果是否达到我们的预期
            Assert.assertEquals("this is mock redis",result);
        }
    }
    

    上述示例的特殊之处在于调用的方法是一个级联方法redisTemplate.opsForValue().get(key);
    对这类调用的mock需要分步进行,即先指定redisTemplate.opsForValue()的返回值,然后再指定其返回值get(key)的返回值,完成一个级联方法的模拟。

    4. kafka调用

    项目中使用了kafka,实现了对kafka生产者和消费者的模拟。示例代码如下:

    @Service
    public class ConsumerService {
        
        @Autowired
        private KafkaTemplate kafkaTemplate;
      
        /**
         * 生产者
         */
        public String sendMessage(String topic,String value){
            System.out.println("向kafka发送消息前执行的逻辑");
            ListenableFuture send = kafkaTemplate.send(topic, value);
            System.out.println("向kafka发送消息后执行的逻辑");
            return "success";
        }
    
        /**
         * 消费者
         */
        @KafkaListener(topics = {"${kafka.consumer.topic}"})
        public void consumeMessage(ConsumerRecord<?, ?> record) throws IOException {
            String value = (String) record.value();
            System.out.println("当前获取的消息内容为:"+value);
            System.out.println("使用消息执行了一些逻辑");
        }
    }
    
    @RunWith(PowerMockRunner.class)
    @PrepareForTest({ConsumerRecord.class})
    public class KafkaTest {
    
        @InjectMocks
        ConsumerService consumerService;
    
        @Mock
        private KafkaTemplate kafkaTemplate;
    
        @Before
        public void setup(){
            MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testSend() {
            String topic = "result";
            String value = "这是一条测试的消息";
            // 首先指定mock send方法的返回值
            ListenableFuture mockFuture = PowerMockito.mock(ListenableFuture.class);
            // 指定send方法调用时候的动作
            PowerMockito.when(kafkaTemplate.send(topic,value)).thenReturn(mockFuture);
            String result = consumerService.sendMessage(topic,value);
            Assert.assertEquals("success",result);
        }
    
        @Test
        public void testConsume() throws IOException {
            ConsumerRecord mockRecord = PowerMockito.mock(ConsumerRecord.class);
            PowerMockito.when((String)mockRecord.value()).thenReturn("value");
            consumerService.consumeMessage(mockRecord);
        }
    }
    

    以上demo中相比之前多了一个@PrepareForTest({ConsumerRecord.class}),这里简要说明一下@PrepareForTest的作用:
    当mock一些特殊方法时,如static,final,private,系统类static方法时,这些方法所在的类需要在@PrepareForTest里指明,否则会报以下异常:

    org.mockito.exceptions.base.MockitoException: 
    Mockito cannot mock this class: class org.apache.kafka.clients.consumer.ConsumerRecord.
    Mockito can only mock non-private & non-final classes.
    If you're not sure why you're getting this error, please report to the mailing list.
    

    Mockito无法模拟一个ConsumerRecord,因为ConsumerRecord是一个final类,使用了注解后,就可以实现。这也是PowerMock相对于Mockito的过人之处,解决了Mockito无法mock特殊方法的痛点;另外当使用PowerMockito.whenNew方法时,必须加注解@PrepareForTest,注解@PrepareForTest里写的类是需要mock的new对象代码所在的类。

    6. session的mock

    项目使用了SpringSession+Redis的分布式session解决方案,因此经常需要从session中获取当前用户id,由于request和session对象的复杂性,在这类情形下编写单元测试非常困难,因为无法手动创建一个符合预期的request或者session对象,使用mock框架可以轻松解决。

    @Service
    public class ConsumerService {
        
        public String getUserName(HttpServletRequest request){
            String userId = (String)request.getSession().getAttribute("userId");
            System.out.println("这里还执行了一些其他逻辑");
            // 假设根据用户id查询了数据库
            String userName = consumerMapper.callDB(userId);
            // 返回用户姓名
            return userName;
        }
    }
    

    上述情景为是一个从session中获取用户id,并根据用户id查询数据库获取用户信息的示例。单元测试代码如下:

    @RunWith(PowerMockRunner.class)
    public class SessionTest {
    
        @InjectMocks
        ConsumerService consumerService;
    
        @Mock
        ConsumerMapper consumerMapper;
    
        @Before
        public void setup() {
            MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void test() {
            // mock HttpServletRequest
            HttpServletRequest mockRequest = PowerMockito.mock(HttpServletRequest.class);
            // mock HttpSession
            HttpSession mockSession = PowerMockito.mock(HttpSession.class);
            // 指定了request调用getSession时返回mockSession
            PowerMockito.when(mockRequest.getSession()).thenReturn(mockSession);
            // 指定session获取userId时返回123
            PowerMockito.when(mockSession.getAttribute("userId")).thenReturn("123");
            // 指定了用123去查询数据库返回的用户zhangsan
            PowerMockito.when(consumerMapper.callDB("123")).thenReturn("zhangsan");
            // 真实调用service方法
            String result = consumerService.getUserName(mockRequest);
            //最后这里加上断言,进行判断执行结果是否达到我们的预期
            Assert.assertEquals("zhangsan", result);
        }
    }
    

    7. controller单元测试

    通常按照代码规范,controller层通常是不允许有业务逻辑的存在,因此也谈不上需要进行单元测试了。但是若需要进行单元测试,使用框架也是可以实现的。待测试代码如下:

    @RestController
    public class ConsumerController{
    
        @Autowired
        private ConsumerService consumerService;
      
        @RequestMapping(value="/helloLocal/{name}")
        public String helloLocal(@PathVariable("name") String name){
            return consumerService.helloLocal(name);
        }
    }
    

    单元测试示例如下:

    @RunWith(PowerMockRunner.class)
    public class ControllerTest {
    
        @Mock
        private ConsumerService service;
    
        @InjectMocks
        private ConsumerController consumerController;
    
        @Test
        public void testHelloLocal(){
            String name = "hulu";
            // 指定mockservice方法的行为
            PowerMockito.when(service.helloLocal(name)).thenReturn("hello hulu, this is mock local");
            String result = consumerController.helloLocal(name);
            Assert.assertEquals("hello hulu, this is mock local",result);
        }
    }
    
    

    观察以上代码发现方法和前面的测试实现思路一样,只是原来待注入的类是service,现在变成了controller,模拟service,指定service的行为,完成controller层的单元测试。

    8. 构造方法

    构造方法的mock主要是针对代码中使用对象的构造方法创建对象。下面是我们自定义的一个实体,包含了无参构造和有参构造。

    public class NewClass {
    
        private String message;
    
        public NewClass(){}
    
        public NewClass(String message) {
            this.message = message;
        }
    
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    }
    
    public class TargetClass {
        
        public String getMessage(){
            // 使用无参构造创建了一个对象
            NewClass object= new NewClass();
            return object.getMessage();
        }
      
        public String getInsertMessage(){
            // 使用有参构造构建了一个对象
            NewClass object= new NewClass("this is another message");
            return object.getMessage();
        }
    }
    

    假如单元测试中需要对new一个对象这行代码进行模拟,测试代码示例如下:

    @RunWith(PowerMockRunner.class)
    // 需要在@PrepareForTest中指明这两者
    @PrepareForTest({NewClass.class,TargetClass.class})
    public class NewObjectTest {
    
        /**
         * 这种new对象的mock也可以理解成是mock无参构造的方法
         */
        @Test
        public void testConstructionMethodWithNoArg() throws Exception {
            // 要mock的new的类
            NewClass newClass = PowerMockito.mock(NewClass.class);
            // 这里是当使用无参构造时候返回我们mock的实体
            PowerMockito.whenNew(NewClass.class).withNoArguments().thenReturn(newClass);
            // 指定当调用mock的这个实体的方法时候返回指定的值
            String mockResult = "this is mock message";
            PowerMockito.when(newClass.getMessage()).thenReturn(mockResult);
            // 创建我们要测试的目标类,并执行我们的方法
            TargetClass targetClass = new TargetClass();
            String message = targetClass.getMessage();
            // 校验返回值
            Assert.assertEquals(mockResult,message);
        }
    
        /**
         * mock 有参构造
         */
        @Test
        public void testConstructionMethodWithArg() throws Exception {
            // 先mock一个这个类的对象
            NewClass newClass = PowerMockito.mock(NewClass.class);
            // 这里是当使用有参构造时候返回我们指定的mock对象
            String message = "this is another message";
            PowerMockito.whenNew(NewClass.class).withArguments(message).thenReturn(newClass);
            // 指定当调用mock对象的方法时候的返回值
            String mockMessage = "this is another mock message";
            PowerMockito.when(newClass.getMessage()).thenReturn(mockMessage);
            // 这里创建包含mock对象的类,这里进行方法测试
            TargetClass targetClass = new TargetClass();
            String returnMessage = targetClass.getInsertMessage();
            // 校验返回值
            Assert.assertEquals(mockMessage,returnMessage);
        }
    }
    

    9. final方法

    首先创建一个持有final方法的类:

    public class FinalMethodClass {
    
        public final String finalMethod(){
            return "this is finalMethod";
        }
    }
    

    创建一个调用这个final方法的类:

    public class FinalMethodCallerClass {
        // 持有一个包含FinalMethod的对象
        private FinalMethodClass finalMethodClass;
    
        public FinalMethodCallerClass(FinalMethodClass finalMethodClass) {
            this.finalMethodClass = finalMethodClass;
        }
    
        public String callFinalMethod(){
            return finalMethodClass.finalMethod();
        }
    }
    

    测试代码如下:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({FinalMethodClass.class}) // 指明持有final方法的类
    public class FinalMethodTest {
    
        // 使用注解构建了mock对象,和直接使用PowerMockito.mock效果一样
        @Mock
        private FinalMethodClass finalMethodClass;
    
        @Test
        public void testCreateUser(){
            String result = "this is mock finalMethod";
            // 指定这个mock对象调用某个方法时的行为
            PowerMockito.when(finalMethodClass.finalMethod()).thenReturn(result);
            // 将mock对象传递给调用方,这里通过构造方法
            FinalMethodCallerClass callerClass = new FinalMethodCallerClass(finalMethodClass);
            // 调用这个调用方的调用方法
            String mockResult = callerClass.callFinalMethod();
            Assert.assertEquals(result,mockResult);
        }
    }
    

    上述过程同样需要在@PrepareForTest中指明持有final方法的类。

    10. static方法

    首先创建一个持有static方法的类:

    public class StaticMethodClass {
    
        public static String sayHello(String name){
            return "hello " + name + ", this is static method";
        }
    }
    

    测试类如下:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(StaticMethodClass.class) // 指明持有static方法的类
    public class StaticMethodTest {
    
        @Test
        public void test(){
            // 和前面的区别,使用mockStatic方法
            PowerMockito.mockStatic(StaticMethodClass.class);
            // 指定static方法返回我们预期值
            PowerMockito.when(StaticMethodClass.sayHello("hulu")).thenReturn("hello hulu, this is mock static method");
            String result = StaticMethodClass.sayHello("hulu");
            Assert.assertEquals("hello hulu, this is mock static method",result);
        }
    }
    

    上述特殊之处在于仍然需要在@PrepareForTest中指明持有static方法的类,同时在创建该类的mock对象时使用的是mockStatic方法。

    11. private方法

    创建一个持有private方法的类:

    public class PrivateMethodClass {
    
        public String callPrivate() {
            System.out.println("these is other method");
            return  privateMehod();
        }
    
        private String privateMehod() {
            return "this is private mthod";
        }
        
    }
    

    测试代码如下:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({PrivateMethodClass.class})
    public class PrivateMethodTest {
    
        @Test
        public void testMockPrivateFunc() throws Exception {
            // spy一个PrivateMethodClass  spy出来的对象可以认为是个真的对象
            PrivateMethodClass privateMethodClass = PowerMockito.spy(new PrivateMethodClass());
            // 指定这个对象中的private方法的返回值
            String result = "this is mock private method";
            PowerMockito.when(privateMethodClass, "privateMehod").thenReturn(result);
            // 调用这个这个类中的调用private方法的返回值
            String realResult = privateMethodClass.callPrivate();
            Assert.assertEquals(realResult, result);
        }
    }
    

    同样需要在@PrepareForTest指定持有private方法的类。

    12. 级联方法的模拟

    级联方法指的是形如a.method1().method2()的方法,关于这类方法的mock,思路就是一层一层进行模拟,具体例子可以参考redis部分和session部分,参考redisTemplate.opsForValue().get(key)和request.getSession().getAttribute("userId")的模拟。

    13. 抛异常的模拟

    以下例子为模拟调用某方法时,模拟方法返回异常,可以用来测试代码中异常处理机制是否满足需求。

    public class ExceptionClass {
    
        public void throwException(String name){
            System.out.println("抛出异常");
        }
    }
    

    测试代码如下:

        @Test(expected = RuntimeException.class)
        public void testException(){
            // mock这个类
            ExceptionClass mock = PowerMockito.mock(ExceptionClass.class);
            // mock某个方法在指定参数下抛出异常
            Exception e = new RuntimeException("test");
            PowerMockito.doThrow(e).when(mock).throwException("mock");
            // 执行当前行后就会抛出异常
            String result = mock.throwException("mock");
            System.out.println("执行不到当前行代码");
        }
    

    上述描述mock异常的示例代码,可以根据实际需求进行异常的模拟,检测代码中异常处理机制。

    14. 内部类

    首先定义一个类Outer,持有两种内部类,分别是非静态和静态内部类。

    public class Outer {
    
        public class Inner2{
            String s = "hello hulu, this is inner method2.";
    
            public String innerMethod(){
                System.out.println("inner2");
                return s;
            }
        }
    
        public static class Inner3{
            static String  s = "hello hulu, this is inner method3.";
    
            public static String innerMethod(){
                System.out.println("inner3");
                return s;
            }
        }
    }
    

    测试代码如下:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest({Outer.Inner3.class}) // 静态内部类需要在这里指明
    public class PowerMockInnerClassTest2 {
        /**
         * mock public修饰的非静态内部类
         */
        @Test
        public void InnerClassTest() {
            // mock 普通成员变量内部类
            Outer.Inner2 mockInner2 = PowerMockito.mock(Outer.Inner2.class);
            PowerMockito.when(mockInner2.innerMethod()).thenReturn("this is mock inner method2.");
            String result = mockInner2.innerMethod();
            Assert.assertEquals("this is mock inner method2.",result);
        }
        /**
         * mock public修饰的静态内部类
         */
        @Test
        public void InnerClassTest2() {
            // mock static 的内部类
            PowerMockito.mockStatic(Outer.Inner3.class);
            PowerMockito.when(Outer.Inner3.innerMethod()).thenReturn("this is mock inner method3.");
            String result = Outer.Inner3.innerMethod();
            Assert.assertEquals("this is mock inner method3.",result);
        }
    }
    

    15. 单例

    这里使用单例模式的最好实践,静态内部类的单例,懒加载,并且单例用类加载机制保证,代码如下:

    public class Singleton {
        
        private Singleton(){};
      
        public String printHelloWorld( String value ) {
            StringBuilder stringBuilder = new StringBuilder( "name is: " );
            return stringBuilder.append( value ).toString();
        }
    
        /**
         * 内部类持有外部类的一个对象,类加载时候不会初始化,只有真正被调用时候才会被初始化
         */
        private static class SingletonInstance {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        public static Singleton getInstance() {
            return SingletonInstance.INSTANCE;
        }
    }
    

    创建一个单例类的使用者,调用了单例的方法。

    public class SingletonUser {
    
        public String useSingletonMethod(){
            String result = Singleton.getInstance().printHelloWorld("hulu");
            return result;
        }
    }
    

    单例的单元测试代码如下:

    @RunWith(PowerMockRunner.class)
    @PrepareForTest(Singleton.class) // 特殊类 需要使用这个注解进行准备
    public class SingletonTest {
    
        /**
         * mock一个单例,并且模拟单例的方法返回值
         * @throws ClassNotFoundException
         */
        @Test
        public void test() throws ClassNotFoundException {
            // mock一个单例类
            Singleton mockSingleton = PowerMockito.mock(Singleton.class);
            // 通过反射获取这个单例类内部的内部类的class对象
            Class clazz = Whitebox.getInnerClassType(Singleton.class, "SingletonInstance");
            // 通过反射给内部类持有的外部类的引用赋值为mock出的单例对象
            Whitebox.setInternalState(clazz, "INSTANCE", mockSingleton);
            // 当调用单例类的方法时候  指定其返回值
            PowerMockito.when( mockSingleton.printHelloWorld(Mockito.anyString())).thenReturn( "this is mock singleton method result" );
            // 创建单例类调用者
            SingletonUser singletonUser = new SingletonUser();
            // 单例类方法调用者调用方法
            String result = singletonUser.useSingletonMethod();
            Assert.assertEquals("this is mock singleton method record",singletonUser.useSingletonMethod());
        }
    }
    

    上述代码中特殊之处在于,由于是private的内部类,因此在获取内部类的class对象时,需要使用PowerMock提供的反射工具类,通过反射的方式获取其class对象,然后通过反射给内部类的属性指定我们mock出的单例,并且指定单例在执行本身printHelloWorld()方法时返回我们预期的值,进而完成单例模式的模拟。

    上述介绍的场景其实有很多在实际单元测试过程中是很少遇到的,通常mock框架使用最多的场景是前面介绍的第三方依赖及复杂对象的场景。

    三、总结

    以上展示单元测试如何解决项目中一些强的第三方依赖、复杂对象及特殊方法特殊场景的落地,使用mock框架创建虚拟的对象,指定相应对象方法调用时的返回值,从而达到调用对应代码时,能够按照预期返回值,进而保证单元测试方法中其他业务逻辑的正常执行。

    相关文章

      网友评论

          本文标题:微服务架构下单元测试落地实践(下)

          本文链接:https://www.haomeiwen.com/subject/glxsyktx.html