美文网首页
java单元测试

java单元测试

作者: ljt001 | 来源:发表于2023-11-24 08:29 被阅读0次

主导目标:单元测试覆盖率100%

一、主要方法

  1. 引用junit类库
  2. 引用Mockito、PowerMockito等Mock类库模拟被测类、模拟被测试类的成员、模拟方法返回
  3. 使用反射修改被测类私有字段以及静态字段
  4. 继承被测类生成Demo子类,可以用于改写protected成员方法
  5. 模拟嵌套调用

二、包依赖

本文例子基于<spring-boot.version>2.6.12</spring-boot.version>

  1. spring-boot-starter-test包依赖,其下包含了junit-jupiter、mockito等包
<!-- spring-boot-starter-test -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
  <exclusions>
    <exclusion>
      <groupId>org.junit.vintage</groupId>
      <artifactId>junit-vintage-engine</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<!-- spring-boot-starter-test中包含了junit-jupiter、mockito等包 -->
<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>5.8.2</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>4.0.0</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-junit-jupiter</artifactId>
  <version>4.0.0</version>
  <scope>compile</scope>
</dependency>
  1. powermock包依赖
<dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-module-junit4</artifactId>
  <version>2.0.9</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-api-mockito2</artifactId>
  <version>2.0.9</version>
  <scope>test</scope>
</dependency>

三、示例

Mockito使用

// mock
FooRepo mockRepo = Mockito.mock(FooRepo.class);
// when
Mockito.when(mockRepo.getById(1)).thenReturn(entity);
// doAnswer
Mockito.doAnswer(invocationOnMock -> {
    try {
        Thread.sleep(200); // 模拟延时
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "mockAccessToken";
}).when(mockRepo).getById(1);

被测类Foo

package com.example;

import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Foo {
    /**
     * DB中AccessToken过期时间91分钟(必须大于Cache过期时间)
     */
    private static final int DB_EXPIRED_MINUTES = 91;
    @Autowired
    FooRepo repo;
    public String getName(int id, String prefix) {
        var student = repo.getById(id);
        if (student == null) {
            return null;
        }
        var name = student.getName();
        if (!isNameBeginWith(name, prefix)) {
            return null;
        }
        return name;
    }

    protected boolean isNameBeginWith(String name, String prefix) {
        return name.startsWith(prefix);
    }

    public boolean setName(int id, String name) {
        return repo.update(id, name);
    }

    @Data
    static class Student {
        int id;
        String name;

        public Student(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }

    static class FooRepo {
        public Student getById(int id) {
            return new Student(1, "Lxx");
        }

        public boolean update(int id, String name) {
            return true;
        }
    }
}

单元测试类FooTest

package com.example;

import com.exampleUnitTestUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

class FooTest {
    Foo subject;
    FooDemo demoSubject = new FooDemo();
    Foo.FooRepo mockRepo = Mockito.mock(Foo.FooRepo.class);

    @BeforeEach
    void setUp() throws NoSuchFieldException {
        subject = new Foo();
        // 使用反射修复private字段
        UnitTestUtil.setField(subject, "repo", mockRepo);
        UnitTestUtil.setField(demoSubject, Foo.class, "repo", mockRepo);
        var entity = buildEntity();
        var entity2 = buildEntity();
        entity2.setId(2);
        entity2.setName("mockName2");
        Mockito.when(mockRepo.getById(1)).thenReturn(entity);
        Mockito.when(mockRepo.getById(2)).thenReturn(entity2);
    }

    private Foo.Student buildEntity() {
        return new Foo.Student(1, "mockName");
    }

    @Test
    void getById() {
        var name = subject.getName(1, "m");
        assertEquals("mockName", name);
    }

    @Test
    void getByIdNull() {
        var name = subject.getName(2, "L");
        assertNull(name);
    }

    @Test
    void getByIdDemo() {
        var name = demoSubject.getName(1, "m");
        assertEquals("mockName", name);
    }

    /**
     * 使用Demo子类改写被测类protected成员方法
     */
    static class FooDemo extends Foo {
        @Override
        protected boolean isNameBeginWith(String name, String prefix) {
            if ("L".equals(prefix)) {
                return true;
            }
            return super.isNameBeginWith(name, prefix);
        }
    }
}

关于模拟嵌套调用,比如下面的tokenFactory.getContainer().getToken()

可以先使用反射来mock掉tokenFactory,再使用when来mock掉getContainer()的返回值

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class Foo{
    FooTokenFactory tokenFactory = SpringContextUtil.getBean(FooTokenFactory.class);
    public Token getToken() {
        var result = tokenFactory.getContainer().getToken();
        return new Token(result.getValue());
    }
}
class FooTest{
    // 第1步mock创建新factory
    FooTokenFactory tokenFactory = mock(FooTokenFactory.class);
    @BeforeEach
    void setUp() throws NoSuchFieldException {
        // 第2步mock掉被测类中的factory成员
        UnitTestUtil.setField(subject, "tokenFactory", tokenFactory);
        var containerDemo = new FooTokenFactoryDemo();
        // 第4步mock掉factory方法调用
        when(tokenFactory.getContainer()).thenReturn(containerDemo);
    }
    
    @Test
    void getToken() {
        var token = subject.getToken();
        assertEquals("mockToken", token.getValue());
    }
    
    /** 第3步子类方式改写嵌套返回 **/
    static class FooTokenFactoryDemo extends FooTokenFactory {
        @Override
        public Token getToken() {
            return new Token("mockToken");
        }
    }
}

四、其他

工具类UnitTestUtil:使用反射修改被测类的私有字段

注意要在子类对象上修改基类字段,需要将基类class对象来查找getDeclaredField

import org.springframework.util.ReflectionUtils;

public class UnitTestUtil {
    private UnitTestUtil() {
    }

    public static void setField(Object target, String fieldName, Object mockObject) throws NoSuchFieldException {
        setField(target, target.getClass(), fieldName, mockObject);
    }

    public static void setStaticField(Class<?> clazz, String fieldName, Object mockObject) throws NoSuchFieldException {
        setField(null, clazz, fieldName, mockObject);
    }

    public static void setField(Object target, Class<?> clazz, String fieldName, Object mockObject) throws NoSuchFieldException {
        var field = clazz.getDeclaredField(fieldName);
        ReflectionUtils.makeAccessible(field);
        ReflectionUtils.setField(field, target, mockObject);
    }
}

相关文章

  • 单元测试-JUnit

    java单元测试是最小的功能单元测试代码, 单元测试就是针对单个java方法的测试。相比较于main方法进行测试,...

  • 单元测试-mockito+powermock

    单元测试--Java 使用mockito+powermock进行java单元测试 实例 如下一个正常业务代码,接下...

  • Android单元测试 - 如何开始?

    回顾: 《谈谈为什么写单元测试》 基本单元测试框架 Java单元测试框架:Junit、Mockito、Powerm...

  • Android单元测试

    本文主要内容 1、单元测试介绍 2、java单元测试 3、android单元测试 4、常用方法介绍 1、单元测试介...

  • 5.自动化测试工具

    单元测试工具 JUnit:Java单元测试 NUnit CppUnit:C++单元测试 性能测试工具 LoadRu...

  • Mockito使用介绍

    Mockito是用于写Java的单元测试框架,在单元测试中使用Mockito来创建和模拟(Mock)假的Java对...

  • Instrumentation

    1、Instrumentation单元测试框架,基于Junit;Junit是java单元测试根本 2、想要进行自动...

  • Jmokit

    # Junit+Jmokit单元测试 标签(空格分隔):java单元测试 --- > 原理:mock测试就是在测试...

  • 单元测试框架之unittest简介

    一、单元测试的含义 unittest单元测试框架的设计灵感来源于Junit(Java语言的单元测试框架),它与其他...

  • nodejs mock数据 单元测试

    nodejs 单元测试 nodejs koa 框架实现Java 中mock+junit类似的单元测试。 需要用到的...

网友评论

      本文标题:java单元测试

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