JAVA单元测试与集成测试详解

Java

测试要求

测试要求来源于阿里嵩山版Java开发手册:

  1. 【强制】好的单元测试必须遵守 AIR 原则。
    说明:单元测试在线上运行时,感觉像空气(AIR)一样感觉不到,但在测试质量的保障上,却是非常关键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。
    ⚫ A:Automatic(自动化)
    ⚫ I:Independent(独立性)
    ⚫ R:Repeatable(可重复)
  2. 【强制】单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执 行过程必须完全自动化才有意义。输出结果需要人工检查的测试 不是一个好的单元测试。单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证。
  3. 【强制】保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。
    反例:method2 需要依赖 method1 的执行,将执行结果作为 method2 的输入。
  4. 【强制】单元测试是可以重复执行的,不能受到外界环境的影响。
    说明:单元测试通常会被放到持续集成中,每次有代码 check in 时单元测试都会被执行。如果单测对外部 环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。
    正例:为了不受外界环境影响,要求设计代码时就把 SUT 的依赖改成注入,在测试时用 spring 这样的 DI框架注入一个本地(内存)实现或者 Mock 实现。
  5. 【强制】对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级 别,一般是方法级别。
    说明:只有测试粒度小才能在出错时尽快定位到出错位置。单测不负责检查跨类或者跨系统的交互逻辑, 那是集成测试的领域。
  6. 【强制】核心业务、核心应用、核心模块的增量代码确保单元测试通过。
    说明:新增代码及时补充单元测试,如果新增代码影响了原有单元测试,请及时修正。
  7. 【强制】单元测试代码必须写在如下工程目录:src/test/java,不允许写在业务代码目录下。
    说明:源码编译时会跳过此目录,而单元测试框架默认是扫描此目录。
  8. 【推荐】单元测试的基本目标:语句覆盖率达到 70%;核心模块的语句覆盖率和分支覆盖率都 要达到 100%
    说明:在工程规约的应用分层中提到的 DAO 层,Manager 层,可重用度高的 Service,都应该进行单元测试。
  9. 【推荐】编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。
    ⚫ B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
    ⚫ C:Correct,正确的输入,并得到预期的结果。
    ⚫ D:Design,与设计文档相结合,来编写单元测试。
    ⚫ E:Error,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。
    10.【推荐】对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的,或 者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据。
    反例:删除某一行数据的单元测试,在数据库中,先直接手动增加一行作为删除目标,但是这一行新增数 据并不符合业务插入规则,导致测试结果异常。
    11.【推荐】和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者对单元测试产生的数据有明确的前后缀标识。
    正例:在阿里巴巴企业智能事业部的内部单元测试中,使用 _ENTERPRISE_INTELLIGENCE UNIT_TEST _的前缀来标识单元测试相关代码。
    12.【推荐】对于不可测的代码在适当的时机做必要的重构,使代码变得可测,避免为了达到测试 要求而书写不规范测试代码。
    13.【推荐】在设计评审阶段,开发人员需要和测试人员一起确定单元测试范围,单元测试最好覆盖所有测试用例(UC)。
    14.【推荐】单元测试作为一种质量保障手段,在项目提测前完成单元测试,不建议项目发布后补 充单元测试用例。
    15.【参考】为了更方便地进行单元测试,业务代码应避免以下情况:
    ⚫ 构造方法中做的事情过多。
    ⚫ 存在过多的全局变量和静态方法。
    ⚫ 存在过多的外部依赖。
    ⚫ 存在过多的条件语句。
    说明:多层条件语句建议使用卫语句、策略模式、状态模式等方式重构。
    16.【参考】不要对单元测试存在如下误解:
    ⚫ 那是测试同学干的事情。本文是开发手册,凡是本文内容都是与开发同学强相关的。
    ⚫ 单元测试代码是多余的。系统的整体功能与各单元部件的测试正常与否是强相关的。
    ⚫ 单元测试代码不需要维护。一年半载后,那么单元测试几乎处于废弃状态。
    ⚫ 单元测试与线上故障没有辩证关系。好的单元测试能够最大限度地规避线上故障。

命名规格

  1. 类命名:Test,以它要测试的类的名称开始,以 Test 结尾。例如,OrderInfoTest
  2. 方法命名:test,其中****是要测试的方法的名称。例如,testCalculateTotal()
  3. 路径:使用test作为顶级包名

单元测试

定义:是指对软件中的最小可测试单元进行检查和验证。
Java里单元指一个方法。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。

断言

断言(Assertion)是测试中常用的一种技术,用于验证测试的预期结果是否与实际结果相符。断言通常用于测试框架中,用于检查代码的输出、行为或状态是否符合预期。在Java中,常见的断言工具是JUnit框架提供的断言方法,例如assertEquals()assertTrue()、**assertFalse()**等。这些方法接受一个预期值和一个实际值,如果两者相符,则测试通过;否则,测试失败并抛出异常。

//1、assertEquals(expected, actual):验证预期值和实际值是否相等。
assertEquals(10, result); // 预期值为10,实际值为result
//2、assertTrue(condition):验证条件是否为真。
assertTrue(result > 0); // 验证result大于0
//3、assertFalse(condition):验证条件是否为假
assertFalse(result.isEmpty()); // 验证result不为空
//4、assertNull(object):验证对象是否为null
assertNull(result); // 验证result为null
//5、assertNotNull(object):验证对象是否不为null。
assertNotNull(result); // 验证result不为null

在Java中,也可以自定义断言来满足特定的测试需求。自定义断言可以根据你的应用程序逻辑进行定制化,以便更好地验证测试的预期结果。

//1、创建一个包含静态方法的类,用于执行自定义断言逻辑。
public class CustomAssertions {
    public static void assertEvenNumber(int number) {
        if (number % 2 != 0) {
            throw new AssertionError("Expected an even number, but got: " + number);
        }
    }
}
//2、在测试代码中使用自定义断言方法。
@Test
public void testCustomAssertion() {
    int result = 10;
    CustomAssertions.assertEvenNumber(result);
}
//上述代码中自定义断言方法assertEvenNumber()用于验证给定的数字是否为偶数。如果数字不是偶数,断言会抛出AssertionError异常,测试将失败

SpringBootTest框架

Spring Boot Test框架是基于JUnit的测试框架,是用于编写和执行单元测试、集成测试和端到端测试的测试框架,专门针对Spring Boot应用程序进行测试。框架提供了一系列注解、类和工具,以简化测试的编写和执行过程,并提供了与Spring应用程序的集成和自动化配置
以下是Spring Boot Test框架的一些关键特点和功能:

  1. 自动配置:Spring Boot Test框架基于Spring Boot的自动配置原理,可以自动配置测试环境,包括数据库、缓存、消息队列等。
  2. 注解驱动:Spring Boot Test框架使用注解来标记和配置测试类和方法,例如**@SpringBootTest**、@RunWith@DataJpaTest等。
  3. 依赖注入:Spring Boot Test框架支持依赖注入,可以使用**@Autowired**注解注入需要测试的对象或模拟对象。
  4. 模拟对象:Spring Boot Test框架与Mockito和EasyMock等模拟对象框架集成,方便创建和操作模拟对象。
  5. 集成测试:Spring Boot Test框架提供了对Web应用程序的集成测试支持,可以模拟HTTP请求和响应,并进行端到端的测试。
  6. 数据库测试:Spring Boot Test框架支持数据库相关测试,包括内存数据库的自动配置和管理,以及数据访问层的单元测试和集成测试。
  7. 测试工具:Spring Boot Test框架提供了一些测试工具类和辅助方法,例如TestRestTemplate用于发送HTTP请求,TestEntityManager用于操作JPA实体管理器等。
所需依赖
// 基础依赖 spring-boot-starter-test中包含了junit和mockito等依赖
// pom.xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>
// gradle
testCompile 'org.springframework.boot:spring-boot-starter-test'

//还可以根据需要添加其他测试相关的依赖项,并确保它们与Spring Boot版本兼容
常用注解

@SpringBootTest注解:该注解用于标记测试类,指示Spring Boot在测试环境中加载完整的应用程序上下文,注解用于在Spring Boot的测试环境中加载完整的应用程序上下文。它会根据配置文件和注解自动配置应用程序的各个组件,并创建一个与实际运行环境相似的测试环境。通过加载完整的应用程序上下文,可以进行更全面的集成测试,包括对依赖关系的正确性和整个应用程序的功能进行验证
@RunWith:该注解用于指定测试运行器,用于执行测试。在Spring Boot中通常使用**@RunWith(SpringRunner.class)**。

  1. **BlockJUnit4ClassRunner**:默认的运行器,用于运行基于 JUnit 4 的测试类。
  2. **PowerMockRunner**:用于支持使用 PowerMock 框架进行单元测试,可以模拟静态方法、私有方法等
  3. **AndroidJUnit4**:用于在 Android 环境中运行测试,支持 Android 特定的功能和断言
  4. **SpringRunner** 是 JUnit 4 的一个运行器(Runner),用于在 Spring 环境中运行测试,继承自 JUnit 的 **BlockJUnit4ClassRunner**,它提供了与 Spring 框架集成的功能。当使用 **SpringRunner** 运行测试类时,会自动创建和管理 Spring 容器,并将依赖注入到测试类中,使得可以在测试中使用 Spring 的功能,如依赖注入、事务管理、AOP 等

@MockBean: 该注解用于创建一个模拟对象(Mock)并将其注入到应用程序上下文中。它通常用于模拟依赖的外部组件或服务
@Autowired: 该注解用于自动注入依赖。它可以用于将被测试对象或其他组件注入到测试类中。
@Test:该注解用于标记测试方法。测试方法应该使用该注解进行注释,以便在执行测试时被识别。@Test(timeout = 1000) 测试方法执行超过1000毫秒后算超时,测试将失败。@Test(expected = Exception.class) 测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败。
@Before: 该注解用于标记在每个测试方法之前执行的方法。它通常用于准备测试环境,例如初始化对象或设置测试数据
@After: 该注解用于标记在每个测试方法之后执行的方法。它通常用于清理测试环境,例如释放资源或重置状态

测试案例
@RunWith(SpringRunner.class) // 使用Spring环境测试
@SpringBootTest //指示Spring Boot在测试环境中加载完整的应用程序上下文(整个应用程序的配置和运行环境)
public class Test {
	@Autowired
    private MyService myService;

    @Test
    public void testAddNumbers() {
        int result = myService.addNumbers(2, 3);
        assertEquals(5, result);
    }
}
import net.remote.response.AccountApiResponse;
import net.service.UserService;
import net.site.SiteInfoService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
@EnableAutoConfiguration
public class SpBootMokDemo{

    @Autowired
    private SiteInfoService siteInfoService;

    @MockBean
    private UserService userService;

    @Before
    public void setup() throws Exception {
        // 模拟方法调用的返回值
        AccountApiResponse.AccountDetailReturn accountDetailReturn = new AccountApiResponse.AccountDetailReturn();
        accountDetailReturn.setStatus("okk");
        Mockito.when(userService.getUserInfoById("aaa")).thenReturn(accountDetailReturn);  //模拟方法 参照PowerMock测试框架
    }

    @Test
    public void testCase()throws Exception {
        System.out.println(userService.getUserInfoById("aaa"));
    }
}

Mocking

Mocking是软件开发中的一种技术,用于模拟或替代系统中的依赖项,以便进行单元测试。通过模拟依赖项的行为,我们可以控制测试环境并隔离被测代码的影响。在进行单元测试时,我们可以使用mocking框架创建和配置模拟对象,以替代实际的依赖项。通过定义模拟对象的行为和返回值,我们可以模拟依赖项的响应,以便在测试中验证被测代码的行为。
Mocking通常用于以下情况:

  1. 当某个依赖项不容易创建或访问,例如数据库连接、网络请求或外部服务。
  2. 当某个依赖项的行为不稳定或不可靠,例如随机生成的数据或时间相关的操作。
  3. 当某个依赖项的使用会导致副作用,例如修改数据库或发送电子邮件。

常见的mocking框架包括:

  • Mockito:用于Java的常见mocking框架,易于使用且功能强大。
  • PowerMock:在Mockito基础上扩展,支持更多的mocking场景,如静态方法、私有方法和构造函数等。
  • EasyMock:另一个流行的Java mocking框架,提供了简单的API和易于理解的语法。
  • Jest:用于JavaScript的mocking框架,适用于前端开发和Node.js环境。
  • Sinon.js:JavaScript的另一个流行mocking框架,可以与Jest等测试框架集成使用。

使用这些mocking框架,可以轻松地创建模拟对象,并使用断言和验证来验证被测代码与依赖项之间的交互。这样就可以更好地控制测试环境,减少外部因素对测试结果的影响,并提高测试的可靠性和可重复性

Mockito框架

Mockito是一个用于Java的流行的mocking框架,用于进行单元测试和行为驱动开发(BDD)。它提供了简洁而强大的API,使开发人员可以轻松地创建和配置模拟对象,并验证被测代码与依赖项之间的交互。

所需依赖
// 1、添加Spring Boot Starter Test依赖,含了JUnit和Mockito的依赖会自动引入JUnit和Mockito无需单独添加它们的依赖
//在测试类中使用@RunWith(SpringRunner.class)注解,可与Spring Boot集成测试一起使用Mockito。可以使用Mockito来模拟Bean与注入依赖
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

//2、引入Mockito的核心库
//这只是Mockito的核心依赖,如果需要使用Mockito的其他功能或扩展,可能需要添加其他相关依赖
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>3.11.2</version>
    <scope>test</scope>
</dependency>
//除了Mockito本身的依赖,还需要确保项目中包含JUnit或其他适当的测试框架的依赖,因为Mockito通常与测试框架一起使用
//添加JUnit的依赖 , 引入整个 JUnit Jupiter 模块
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.8.1</version>
    <scope>test</scope>
</dependency>
常用方法
Mockito.mock(classToMock)	//模拟对象  可以使用@Mock注解代替
Mockito.verify(mock)	//验证行为是否发生
Mockito.when(methodCall).thenReturn(value1).thenReturn(value2)	//触发时第一次返回value1,第n次都返回value2
Mockito.doThrow(toBeThrown).when(mock).[method]	//模拟抛出异常。
Mockito.mock(classToMock,defaultAnswer)	//使用默认Answer模拟对象
Mockito.when(methodCall).thenReturn(value)	//参数匹配
Mockito.doReturn(toBeReturned).when(mock).[method]	//参数匹配(直接执行不判断)
Mockito.when(methodCall).thenAnswer(answer))	//预期回调接口生成期望值
Mockito.doAnswer(answer).when(methodCall).[method]	//预期回调接口生成期望值(直接执行不判断)
Mockito.spy(Object)	//用spy监控真实对象,设置真实对象行为
Mockito.doNothing().when(mock).[method]	//不做任何返回
Mockito.doCallRealMethod().when(mock).[method] //等价于Mockito.when(mock.[method]).thenCallRealMethod();	调用真实的方法
reset(mock)	//重置mock

//Mockito继承Matchers,anyInt()等均为Matchers方法,使用了参数匹配,那么所有的参数都必须通过matchers来匹配,当传入两个参数
Mockito.anyInt() //任何 int 值 ;
Mockito.anyLong() //任何 long 值 ;
Mockito.anyString() //任何 String 值 ;
Mockito.anyBoolean()  //任何boolean 值
Mockito.any(XXX.class) //任何 XXX 类型的值 等等
Mockito.any() //任何参数

使用案例
//验证行为是否发生
List mock =  Mockito.mock(List.class);//模拟创建一个List对象
mock.add(1);//调用mock对象的方法
mock.clear();
Mockito.verify(mock).add(1);//验证方法是否执行

//多次触发返回不同值
Iterator iterator = mock(Iterator.class);//mock一个Iterator类
Mockito.when(iterator.next()).thenReturn("hello").thenReturn("world");//预设当iterator调用next()时第一次返回hello,第n次都返回world
String result = iterator.next() + " " + iterator.next() + " " + iterator.next();//使用mock的对象
Assert.assertEquals("hello world world",result);//验证结果

//模拟抛出异常
@Test(expected = IOException.class)//期望报IO异常
public void when_thenThrow() throws IOException{
      OutputStream mock = Mockito.mock(OutputStream.class);
      Mockito.doThrow(new IOException()).when(mock).close();//预设当流关闭时抛出异常
      mock.close();
}

//参数匹配
@Test
public void with_arguments(){
    TestB b = Mockito.mock(TestB.class);
    Mockito.when(b.getSex(1)).thenReturn("男");//预设根据不同的参数返回不同的结果
    Mockito.when(b.getSex(2)).thenReturn("女");
    Assert.assertEquals("男", b.getSex(1));
    Assert.assertEquals("女", b.getSex(2));
    Assert.assertEquals(null, b.getSex(0));//对于没有预设的情况会返回默认值
}
@Data
class TestB{
    private String name;
    public String getSex(Integer sex){
        if(sex==1){
            return "man";
        }else{
            return "woman";
        }
    }
}

// 匹配参数
@Before
public void setup() throws Exception{
    // 模拟鉴权方法调用的返回值 : 输入任意参数 返回 true
    Mockito.when(accountService.isMain(Mockito.any())).thenReturn(true);
    // 模拟云盘剩余额度方法请求的返回值 : 输入任意参数 返回 10086
    Mockito.when(rulesService.getAllowSSDVolumeSize(Mockito.any(),Mockito.any())).thenReturn(10086);
    // 模拟实例剩余额度方法请求的返回值 : 输入任意参数 返回 10086
    Mockito.when(rulesService.getAllowCreateVmNum(Mockito.anyBoolean(),Mockito.any(),Mockito.any())).thenReturn(10086);
}

//重置 mock
@Test
public void reset_mock(){
    List list = mock(List.class);
    Mockito. when(list.size()).thenReturn(10);
    list.add(1);
    Assert.assertEquals(10,list.size());
    //重置mock,清除所有的互动和预设
    Mockito.reset(list);
    Assert.assertEquals(0,list.size());
}
@Mock注解

@Mock注解是Mockito框架提供的一个注解,用于创建一个模拟对象(Mock Object)。可以理解为对 mock 方法的一个替代。使用该注解时,要使用MockitoAnnotations.initMocks 方法,让注解生效。旧版的是initMocks,新版的是openMocks(从Mockito版本3.4.0开始引入的新方法)。也可以用MockitoJUnitRunner来代替MockitoAnnotations.initMocks

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Random;
import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Mock
    private Random random;

    @Before
    public void before() {
        // 让注解生效
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void test() {
        when(random.nextInt()).thenReturn(100);
        Assert.assertEquals(100, random.nextInt());
    }

}


// 也可以用MockitoJUnitRunner来代替MockitoAnnotations.initMocks
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Random;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class MockitoDemo {

    @Mock
    private Random random;

    @Test
    public void test() {
        when(random.nextInt()).thenReturn(100);
        Assert.assertEquals(100, random.nextInt());
    }

}

使用**@Mock注解和Mockito.mock(classToMock)方法都可以创建模拟对象,它们的功能是相同的。它们都可以用于模拟依赖对象,以便进行单元测试时进行行为验证和结果验证。@MockBean注解是Spring Boot框架提供的注解,它是基于Mockito的@Mock注解的增强版。@MockBean注解除了创建模拟对象外,还会将模拟对象注入到Spring应用程序上下文中,替换实际的bean。这样,在进行集成测试时,可以模拟和控制应用程序中的依赖对象。
@Mock注解和
Mockito.mock(classToMock)**之间存在的差异:

  1. 语法:@Mock注解是一种基于注解的方式,可以直接在测试类中使用。而**Mockito.mock(classToMock)**是一种方法调用,需要在测试方法中显式地调用。
  2. 初始化:使用**@Mock注解时,通常需要配合MockitoAnnotations.initMocks(this)MockitoJUnitRunner来初始化模拟对象。而使用Mockito.mock(classToMock)**方法时,模拟对象会立即被创建。
  3. 可读性:@Mock注解的使用可以更清晰地表达测试类中哪些对象是模拟对象,使代码更易读。而使用**Mockito.mock(classToMock)**方法则需要显式地指定模拟对象的创建。

@Mock注解和Mockito.mock(classToMock)与@MockBean之间存在的差异:

  1. 使用范围:@Mock注解与Mockito.mock(classToMock)主要用于单元测试中,而@MockBean注解主要用于集成测试中。
  2. Spring上下文:@MockBean注解会将模拟对象注入到Spring应用程序上下文中,以便在整个应用程序中使用。而**@Mock注解与Mockito.mock(classToMock)**不会自动将模拟对象注入到Spring上下文中。
  3. 自动配置:@MockBean注解还可以与**@SpringBootTest**或其他Spring Boot测试注解一起使用,以便自动配置模拟对象。它会自动创建模拟对象,并将其注册到应用程序上下文中,以供其他组件使用。

总体而言,无论是使用**@Mock注解还是Mockito.mock(classToMock)方法,它们都是用于创建模拟对象的工具。选择使用哪种方式取决于个人喜好和项目的约定。@Mock注解与Mockito.mock(classToMock)用于基本的单元测试中创建模拟对象,而@MockBean**注解是Spring Boot提供的用于集成测试的注解,除了创建模拟对象外,还会自动将其注入到Spring上下文中。选择使用哪个注解取决于测试场景和需求。

PowerMock测试框架

PowerMock 是一个 Java 测试框架,它扩展了 Mockito 和 EasyMock,提供了更强大的能力,可以模拟和测试一些传统的难以测试的场景,如静态方法、私有方法、构造函数等。使用 PowerMock 可以帮助开发人员解决一些传统的难以测试的问题,提高代码的测试覆盖率和质量。在使用 PowerMock 进行测试时,通常需要结合 JUnit、Mockito 或 EasyMock 等测试框架来完成测试代码的编写。
PowerMock 的主要特点和功能包括:

  1. 模拟静态方法:PowerMock 可以模拟静态方法的行为,使得测试静态方法的代码变得简单和可控。
  2. 模拟私有方法:PowerMock 允许模拟私有方法,以便进行私有方法的单元测试。
  3. 模拟构造函数:PowerMock 可以模拟对象的构造函数,包括私有构造函数和带参数的构造函数。
  4. 模拟 final 类和方法:PowerMock 可以模拟 final 类和方法,以便进行对它们的测试。
  5. 模拟静态初始化块:PowerMock 允许模拟静态初始化块的行为。
所需依赖
// 引入依赖
testImplementation 'org.powermock:powermock-api-mockito2:2.0.9'
testImplementation 'org.powermock:powermock-module-junit4:2.0.9'
模拟静态方法
// 假设有一个包含静态方法的类
public class MyUtils {
    public static String generateUniqueId() {
        // 生成唯一标识符的逻辑
        return UUID.randomUUID().toString();
    }
}

// 使用 PowerMock 模拟静态方法
@RunWith(PowerMockRunner.class)
@PrepareForTest(MyUtils.class)
public class MyTestClass {
    @Test
    public void testGenerateUniqueId() {
        PowerMockito.mockStatic(MyUtils.class);
        Mockito.when(MyUtils.generateUniqueId()).thenReturn("mockedId");
        String result = MyUtils.generateUniqueId();
        assertEquals("mockedId", result);
    }
}
模拟私有方法
// 假设有一个包含私有方法的类
public class MyClass {
    private String getPrivateData() {
        // 私有方法的逻辑
        return "private data";
    }
    public String processData() {
        String privateData = getPrivateData();
        // 处理私有数据的逻辑
        return "processed data: " + privateData;
    }
}

// 使用 PowerMock 模拟私有方法
@RunWith(PowerMockRunner.class)
@PrepareForTest(MyClass.class)
public class MyTestClass {
    @Test
    public void testProcessData() throws Exception {
        MyClass mockedInstance = PowerMockito.spy(new MyClass());
        PowerMockito.when(mockedInstance, "getPrivateData").thenReturn("mocked private data");
        String result = mockedInstance.processData();
        assertEquals("processed data: mocked private data", result);
    }
}

模拟构造方法
// 假设有一个需要模拟构造函数的类
public class MyObject {
    public MyObject() {
        // 构造函数的逻辑
    }

    public String processData() {
        // 处理数据的逻辑
        return "processed data";
    }
}

// 使用 PowerMock 模拟构造函数
@RunWith(PowerMockRunner.class)
@PrepareForTest(MyObject.class)
public class MyTestClass {
    @Test
    public void testProcessData() throws Exception {
        MyObject mockedInstance = PowerMockito.mock(MyObject.class);
        PowerMockito.whenNew(MyObject.class).withNoArguments().thenReturn(mockedInstance);
        String result = mockedInstance.processData();
        assertEquals("processed data", result);
    }
}

模拟 final 类和方法
//###############  模拟 final 类
// 假设有一个 final 类
public final class FinalClass {
    public String getData() {
        return "data";
    }
}

// 使用 PowerMock 模拟 final 类
@RunWith(PowerMockRunner.class)
@PrepareForTest(FinalClass.class) //使用 @PrepareForTest 注解来指定要模拟的类
public class MyTestClass {
    @Test
    public void testFinalClass() {
        FinalClass mockedInstance = PowerMockito.mock(FinalClass.class);
        Mockito.when(mockedInstance.getData()).thenReturn("mocked data");
        String result = mockedInstance.getData();
        assertEquals("mocked data", result);
    }
}


//###############  模拟 final 方法
// 假设有一个包含 final 方法的类
public class MyClass {
    public final String getData() {
        return "data";
    }
}

// 使用 PowerMock 模拟 final 方法
@RunWith(PowerMockRunner.class)
@PrepareForTest(FinalClass.class) //使用 @PrepareForTest 注解来指定要模拟的类
public class MyTestClass {
    @Test
    public void testFinalMethod() throws Exception {
        MyClass mockedInstance = PowerMockito.mock(MyClass.class);
        Mockito.when(mockedInstance.getData()).thenReturn("mocked data");
        String result = mockedInstance.getData();
        assertEquals("mocked data", result);
    }
}

模拟静态初始化块
// 假设有一个包含静态初始化块的类
public class MyClass {
    static {
        // 静态初始化块的逻辑
    }

    public String getData() {
        return "data";
    }
}

// 使用 PowerMock 模拟静态初始化块  调用静态块时不做操作
@RunWith(PowerMockRunner.class)
@PrepareForTest(MyClass.class)//使用 @PrepareForTest 注解来指定要模拟的类
public class MyTestClass {
    @Test
    public void testStaticInitializer() throws Exception {
        PowerMockito.mockStatic(MyClass.class); // PowerMock 提供的静态方法,用于模拟静态方法和静态初始化块
        PowerMockito.doNothing().when(MyClass.class, "clinit");// PowerMock 的方法,用于指定当调用指定类的静态初始化块时执行的操作,doNothing() 方法表示在调用静态初始化块时不执行任何操作
        MyClass mockedInstance = new MyClass();
        String result = mockedInstance.getData();
        assertEquals("data", result);
    }
}
// 使用 PowerMock 模拟静态初始化块  调用静态块时做操作
@RunWith(PowerMockRunner.class)
@PrepareForTest(MyClass.class)
public class MyTestClass {
    @Test
    public void testStaticInitializer() throws Exception {
        PowerMockito.mockStatic(MyClass.class);
        PowerMockito.doAnswer(invocation -> {  //使用 PowerMock 的 doAnswer() 方法结合 Mockito 的 Answer 接口来模拟静态块操作
            // 在静态初始化块调用时执行的操作
            System.out.println("Performing custom operation during static initialization");
            // 返回自定义的结果
            return null;
        }).when(MyClass.class, "clinit");
        MyClass mockedInstance = new MyClass();
        String result = mockedInstance.getData();
        assertEquals("data", result);
    }
}

集成测试

集成测试是它是一种测试方法,用于验证多个组件、模块或系统之间的协同工作。它主要关注不同部分之间的交互和集成,以确保整个系统按预期工作。
集成测试的目的是验证不同组件之间的接口、数据传递和协作是否正常。它可以帮助发现系统集成方面的问题,确保系统的整体功能和性能达到预期。集成测试通常在单元测试之后进行,以确保单元测试通过后的组件在集成时能够正常工作。
一般的集成测试流程:

  1. 确定测试范围:确定要进行集成测试的组件、模块或系统。
  2. 准备测试环境:设置测试环境,包括所需的硬件、软件、数据库、网络等。
  3. 定义测试用例:根据需求和设计规格,编写测试用例,覆盖不同的集成场景和交互情况。
  4. 准备测试数据:根据测试用例,准备必要的测试数据,以模拟实际环境中的数据交换和处理。
  5. 执行测试:执行集成测试,按照定义的测试用例对不同组件之间的交互进行测试。
  6. 检查和记录结果:验证每个测试用例的执行结果,记录通过和失败的测试用例,并检查是否符合预期结果。
  7. 分析和修复问题:如果测试中发现问题或错误,分析其原因,并修复或调整相应的组件或系统。
  8. 进行回归测试:在修复问题后,进行回归测试以确保修复不会引入新的问题。
  9. 清理和整理:在集成测试完成后,清理测试环境和数据,并进行必要的文档整理和总结。
import net.remote.response.AccountApiResponse;
import net.service.UserService;
import net.site.SiteInfoService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
@EnableAutoConfiguration
public class SpBootMokDemo{

    @Autowired
    private SiteInfoService siteInfoService;

    @MockBean
    private UserService userService;

    @Before
    public void setup() throws Exception {
        // 模拟方法调用的返回值
        AccountApiResponse.AccountDetailReturn accountDetailReturn = new AccountApiResponse.AccountDetailReturn();
        accountDetailReturn.setStatus("okk");
        Mockito.when(userService.getUserInfoById("aaa")).thenReturn(accountDetailReturn);
    }

    @Test
    public void testCase()throws Exception {
        System.out.println(userService.getUserInfoById("aaa"));
    }
}

在springboot集成测试中需要使用@RunWith(SpringRunner.class)创建和使用spring容器,加载应用上下文。所以在测试时如果有静态方法的外部依赖就无法使用PowerMock做到有效模拟(由于PowerMock需要使用到@RunWith(PowerMockRunner.class)用于支持使用 PowerMock 框架进行单元测试,可以模拟静态方法、私有方法等)。(经过调研暂未找到可以同时使用SpringBootTest框架进行针对springboot应用程序进行测试的同时模拟静态方法依赖的技术手段,需后续调研)
推荐在开发业务功能时,避免使用静态方法调用外部环境。可以代替为普通public方法,并将该类交由spring容器管理。
交由spring容器管理的类都可以使用@MockBean:(该注解用于创建一个模拟对象(Mock)并将其注入到应用程序上下文中。它通常用于模拟依赖的外部组件或服务)进行模拟

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值