Mockito实战

为什么要有个Mockito,本身Junit就可以做单元测试,为什么还要加一个Mcokito作为单元测试框架。

实际上两者不矛盾,甚至两者是搭配着使用的,确切的说Mockito是一个单元测试模拟框架,来帮助发现隐藏bug,提高代码质量。为什么会有这样的作用呢,具体一点的实践场景都有哪些?

首先Mockito注重的是一个模拟,本身mock本意就有模仿假装的意思在里面,假设我们的代码需要跟第三方联调,我们需要人家给我们数据,设置前置条件。实际工作中就知道,工作本身可能不太难,但是和人交流协同有时候更消耗时间。那我们可不可以模拟“别人”,给我们提供我们设想的数据,这样我们就不用看别人“眼色”。

还有一种就是我们需要考虑各种测试场景,这里避免不了各种脏数据产生,导致测试环境什么样的数据都有,这些脏数据可能会影响后面的测试,我们可以模拟各种场景,不产生脏数据。

TDD测试驱动开发

首先我们要理解一个概念先,首先并不是什么代码都可以很方便的单元测试。这也是为什么我们不太愿意写单元测试的原因之一,麻烦觉得没意义。

但是我们如果要理解一个概念TDD测试驱动开发,去理解写代码,能提高我们对需求的理解和提高代码的可维护性。以下是TDD的官方表述:

TDD(Test-Driven Development,测试驱动开发)是一种软件开发方法,它强调在编写代码之前先编写测试用例,然后只编写足够的代码使测试用例通过。TDD的基本思想是:先写测试,再写代码,然后重构代码,以确保代码的正确性和可维护性。

TDD的流程通常被称为红-绿-重构(Red-Green-Refactor):

红:编写测试用例,运行测试用例,测试用例失败(红色)。

绿:编写足够的代码使测试用例通过,运行测试用例,测试用例通过(绿色)。

重构:重构代码,以提高代码的质量和可维护性,然后重新运行测试用例,确保测试用例仍然通过。

TDD的目标是通过测试来驱动开发,以确保代码的正确性和可维护性,同时提高开发效率和项目质量。TDD还可以帮助开发人员更好地理解需求,促进团队协作,提高系统稳定性。

试想一下,我们重构代码,对应的测试案例在maven构建时自动处理(前提是比不要把测试的给屏蔽了),能不能通过,构建的时候就走了一遍。

同样在编写的时候,我们考虑到测试,做到将需求明确和逻辑梳理,把各个场景考虑清楚,避免过度测试,因为有时候很多地方测试测的时候,会出现很多重复测试某个场景。

Mockito基础

在理解TDD的概念,我们可以借助Mockito来帮助我们完善单元测试,可以帮助哦我们去模拟各种场景,各种参数。

在工程中使用 Mockito,需要按照以下步骤:

添加 Mockito 依赖项:在项目的构建文件中添加 Mockito 的依赖项,例如使用 Maven 的项目,在 pom.xml 文件中添加以下依赖项:


    org.mockito
    mockito-core
    3.12.4
    test

创建测试用例:创建一个测试类,并在类中添加测试方法。使用 @Test 注解标记测试方法。

创建模拟对象:在测试方法中,使用 Mockito.mock() 方法创建一个模拟对象。例如:

// 创建一个模拟对象
List mockedList = Mockito.mock(List.class);
设置模拟对象的行为:使用 Mockito.when() 方法设置模拟对象的行为。例如:

// 设置模拟对象的行为
Mockito.when(mockedList.get(0)).thenReturn("first");
运行测试:使用 JUnit 运行测试用例,例如使用 @RunWith 注解和 @Test 注解。例如:

// 运行测试用例
@RunWith(MockitoJUnitRunner.class)
public class MyTest {
    @Test
    public void testMethod() {
        // 测试方法中使用模拟对象
        List mockedList = Mockito.mock(List.class);
        Mockito.when(mockedList.get(0)).thenReturn("first");
        assertEquals("first", mockedList.get(0));
    }
}
以上是使用 Mockito 的基本步骤,可以根据具体的测试场景和要求,使用 Mockito 提供的其他 API 和功能,例如验证模拟对象的方法调用次数、设置模拟对象的默认行为等。

以上只是一个大致的步骤,实际上我们使用的时候,肯定要比这复杂得多,举个例子,我们的一个service类,里面有很多依赖需要注入的bean,有的是数据库DAO层,有的是别的component。这时候我们该怎么做,怎么改来实现我们的预想目标。

Mockito实战

接上面,首先我们写的一些业务代码,由于设计不好,写的像一条“布”,一样一步一步去写,由于业务的不断修改,就变得又臭又长的“布”。首先我们需要把代码写好,做好设计,如果你对一个很复杂的做接口测试,你会发现涉及到的东西太多,Mock了一大堆,场景复杂自己都不知道该如何是好。

(1)编写代码

第一步就是编写好代码,除了新写,也包括改造重构

对于TDD代码的要求

TDD(测试驱动开发)对代码设计的要求主要包括以下几个方面:

可测试性:代码必须易于测试,即能够轻松地编写测试用例并进行测试。为了实现可测试性,代码应该遵循良好的编码规范和设计原则,例如单一职责原则、开闭原则、依赖倒置原则等。

可扩展性:代码必须易于扩展,即能够轻松地添加新的功能或修改现有的功能,而不会影响现有的功能或导致代码重构。为了实现可扩展性,代码应该遵循良好的设计模式和架构模式,例如工厂模式、策略模式、MVC模式等。

可维护性:代码必须易于维护,即能够轻松地修改和更新代码,而不会影响现有的功能或导致代码重构。为了实现可维护性,代码应该遵循良好的编码规范和设计原则,例如代码重构、注释、命名规范等。

可重用性:代码必须易于重用,即能够轻松地将代码移植到其他项目或模块中,从而提高代码的复用性和可维护性。为了实现可重用性,代码应该遵循良好的设计模式和架构模式,例如模板方法模式、适配器模式、代理模式等。

总之,TDD要求代码具有良好的可测试性、可扩展性、可维护性和可重用性,以确保代码的正确性和可维护性,同时提高开发效率和项目质量。
(2)Mock注解的使用

在代码写好后,我们要去测试,我们要利用Mockito来模拟对应的对象。

创建 Mock 对象的语法为 mock(class or interface)。

mock(Class classToMock);
 mock(Class classToMock, String name)
 mock(Class classToMock, Answer defaultAnswer)
 mock(Class classToMock, MockSettings mockSettings)
 mock(Class classToMock, ReturnValues returnValues)

或者

 @Mock
    private MaterialAllocateOrderMapper materialAllocateOrderMapper;

    @Mock
    private MaterialAllocateOrderDetailMapper materialAllocateOrderDetailMapper;

我们实际工作中会出现AService依赖BService,这种情况怎么处理呢?

实际上我们这里要模拟的是BService,需要自动注入模拟对象,这里我们就用到@InjectMocks注解。

    @Mock
    private MaterialAllocateOrderMapper materialAllocateOrderMapper;

    @Mock
    private MaterialAllocateOrderDetailMapper materialAllocateOrderDetailMapper;

    @InjectMocks
    private MaterialAllocateOrderSupport orderStatusBuildRequest;

设置对象调用的预期返回值

原则上我们mock的对象,没有设置默认或者于其返回值,调用里面的方法,什么忙都不会做,但实际上我们需要他们“返回”一些我们需要的东西,这就是要设置预期返回值。

通过 when(mock.someMethod()).thenReturn(value) 来设定 Mock 对象某个方法调用时的返回值。

    @Test
    public void testBuildOrderStatusRequest() {
       handleTestBuildOrderStatusRequest();
    }


    private OrderStatusBuildRequest handleTestBuildOrderStatusRequest() {

        // 设置模拟的方法调用返回值
        when(materialAllocateOrderMapper.findById(anyLong())).thenReturn(allocateOrderDO);
        when(materialAllocateOrderDetailMapper.findByAllocateOrderNo(anyString(), anyLong())).thenReturn(detailDOList);

        // 调用被测试的方法
//        OrderStatusBuildRequest result = orderStatusBuildRequest.buildOrderStatusRequest(1L, 1);
        OrderStatusBuildRequest result = orderStatusBuildRequest.buildOrderStatusRequest(1L, 0);

        // 断言返回值是否符合预期
        Integer status = result.getStatus();
        Assert.assertTrue(result!=null);

        return result;
    }

但是有时候我们需要给定一些值,作为条件,比如我们模拟用户,首先要把用户信息先给进去,这里我们直接用@Before注解就可以

@Before 是 JUnit 框架提供的一个注解,用于标记在每个测试方法执行之前需要执行的方法。通常,在测试方法执行之前需要进行一些初始化操作,例如创建对象、初始化变量等。使用 @Before 注解可以将这些初始化操作封装到一个方法中,并在每个测试方法执行之前自动执行。

使用 @Before 注解时,需要满足以下条件:

  1. 被测试类中需要有一个方法,它使用 @Before 注解进行标记。

  2. @Before 注解标记的方法必须是公共方法(public),没有参数,并且没有返回值。

  3. @Before 注解标记的方法会在每个测试方法执行之前自动执行。

例如:

public class MyTest {
    private MyObject myObject;

    @Before
    public void setUp() {
        myObject = new MyObject();
    }

    @Test
    public void testMethod1() {
        // 测试方法1
    }

    @Test
    public void testMethod2() {
        // 测试方法2
    }
}

在上面的例子中,MyTest 类中有一个 setUp() 方法,它使用 @Before 注解进行标记。在 setUp() 方法中,创建了一个 MyObject 对象,并将其赋值给 myObject 属性。在测试方法中,可以使用 myObject 属性进行测试。

使用 @Before 注解可以将测试方法中的重复代码提取到一个公共的方法中,避免代码重复,提高测试代码的可读性和可维护性。

你可能感兴趣的:(junit,单元测试)