单元测试与Mock

作者:一笑钦陈

邮箱:xianqin_chen@163.com

你好,我是一笑钦陈,《零零后程序员成长之路》作者,一线互联网 Java 工程师。很高兴你阅读我的博客,让我们共同成长进步!

提醒:在接下来您对本博客的阅读中,如果遇到一些内容、图稿、代码等中的勘误都可以通过邮件进行反馈,一笑钦陈会陆续进行完善,感谢您的支持;

一、前言

  本期给大家带来的是单元测试的相关内容。单元测试是软件开发中非常重要的一个环节,执行一个完备的单元测试方案能够提高整个开发过程的时间效率。确保软件的实际功能与详细设计说明的一致,使软件开发的效率和软件产品的质量得到最好的保障

作者认为,做好单元测试至少有以下几点好处:

  • 能够协助程序员在开发阶段尽快找到bug的具体位置并解决
  • 能够让程序员对自己的程序更加自信,在保证没有bug的同时开发鲁棒性更强的代码
  • 能够向其他程序员展现你的程序该如何调用,返回内容,以及在什么时候会抛出异常等等
  • 能够协助程序员更好地进行开发,“码未动,测试先行”。这是权限编程中倡导的一种编程模式

因此,作为一名优秀的程序员,还有什么理由不好好写单元测试呢?

img

二、单元测试介绍

接下来,跟大家解释下什么是单元测试。

  所谓单元测试,用一句话讲,就是开发一小段测试代码用来检验目标代码中一个很小的很明确的功能是否正确。这个功能所涵盖的代码被称为一个功能单元。通常而言,一个功能单元可能是单个程序、类、对象、以及方法等等。在Java中,最小功能单元是方法,因此Java的单元测试就是针对单个方法的测试

三、单元测试最佳实践

Junit是面向Java程序的单元测试框架,它功能强大并且非常容易上手,具备以下的一些优势:

​ 1、使用断言(Assert)测试期望结果

​ 2、可以方便地组织和运行测试

​ 3、可以方便地查看测试结果

​ 4、常用的IDE都集成了Junit

​ 5、可以方便地集成到Maven

下面将使用Junit 4.12来为大家演示如何进行单元测试

第一步:创建maven项目

第二步:导入依赖(在pom.xml中添加引用包junit)

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

第三步:在main包下编写被测试类及被测试方法

public class UserService {
    
    private static final String USERNAME = "一笑钦陈";

    private static final String PASSWORD = "123456";

    public Boolean login(String username, String password) {
        boolean result = false;
        // 判断用户名、密码是否正确
        if (USERNAME.equals(username) || PASSWORD.equals(password) {
            result = true;
        }
        return result;
    }

}

第四步:在test包下编写测试类及测试方法

public class UserServiceTest {

    /**
     * 用于测试密码正确的情况
     */
    @Test
    public void testLogin_successs() {
        UserService userServiceUnderTest = new UserService();
        // Assert.assertTrue() -- 期望返回true
        Assert.assertTrue(userServiceUnderTest.login("一笑钦陈", "123456"));
    }

    /**
     * 用于测试密码不正确的情况
     */
    @Test
    public void testLogin_fail() {
        UserService userServiceUnderTest = new UserService();
        // Assert.assertTrue() -- 期望返回false
        Assert.assertFalse(userServiceUnderTest.login("一笑钦陈", "1111111"));
    }
  
    @Before
    public void before() {
        System.out.println("===before方法执行一次===");
    }

    @After
    public void after() {
        System.out.println("===after方法执行一次===");

    }

    @BeforeClass
    public static void beforeClass() {
        System.out.println("===beforeClass方法执行一次===");
    }

    @AfterClass
    public static void afterClass() {
        System.out.println("===afterClass方法执行一次===");
    }
}

案例分析:

​ 1、测试类命名:被测试类名称+Test(如上案例,被测试类名称:UserService,测试类名称:UserServiceTest)

​ 2、测试方法命名:test+被测试方法名称+测试重点(如上案例,被测试方法名称:login,测试方法名称:testLogin_successs/testLogin_fail)

​ 3、在测试方法上使用@Test注解:标注该方法是一个测试方法

​ 4、选中测试方法,右击选择运行,如果测试良好则是绿色,如果测试失败则为红色

在这里插入图片描述
接下来,有些同学可能会问,@Test的作用我明白了,是用来运行测试方法的。但是@Before、@After、@BeforeClass、@AfterClass这几个注解是用来做什么的呢?

注解 说明
@Test 测试方法
@Before 用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次
@After 用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次
@BeforeClass 用来静态修饰方法,该方法会在所有测试方法之前只执行一次
@AfterClass 用来静态修饰方法,该方法会在所有测试方法之后只执行一次

一般来说

  • 开始执行的方法:初始化资源
  • 执行完之后的方法:释放资源

Junit提供了如此多好用的注解,希望大家可以灵活运用哦!

四、Mock的前世今生

  至此,我们已经学会了利用Junit框架来完成单元测试。但同学们有没有想过这样一个问题,在实际的业务场景中,比如会有一些业务的操作需要对数据库进行增删改查,如果在测试的代码中也对数据库进行相应的操作,就会产生一些脏数据,对线上生产造成安全隐患。那如何在不影响生产的前提下进行单元测试呢?

当然是使用mock的方式,创建一个虚拟的对象,在测试环境中用来替换掉真实的对象,避免对生产造成影响

mock的作用主要是:给一个对象的行为(也就是方法)做一个自定义,来指定返回结果或者指定特定的动作

五、Mock的正确姿势

下面用Junit+Mockito框架来举例说明如何使用mock来进行单元测试:

第一步:创建maven项目

第二步:导入依赖(在pom.xml中添加引用包junit+mockito)

<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>3.12.4</version>
  <scope>test</scope>
</dependency>

第三步:在main包下编写被测试类及被测试方法

public class UserService {

    /**
     * 用户数据操作接口
     */
    private UserDao userDao;

    public Boolean register(String username, String password) {
        boolean result = false;
        // 参数校验
        if (username == null || password == null) {
            return false;
        }
        // 添加到数据库
        try {
            result = userDao.register(username, password);
        } catch (Exception e) {
            System.out.println("用户注册异常" + e.getMessage());
        }
        // 返回结果
        return result;
    }
}

第四步:在test包下编写测试类及测试方法

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {

    @Mock
    private UserDao userDao;

    @InjectMocks
    private UserService userService;

    @Test
    public void testRegister() {
        // Setup
        // 自定义mock对象方法的返回结果
        Mockito.when(userDao.register("一笑钦陈", "123456")).thenReturn(true);

        // 自定义mock对象方法抛出指定异常
        Mockito.when(userDao.register("一笑钦陈", "123456")).thenThrow(new RuntimeException());

        // 自定义mock对象方法执行真实方法
        Mockito.when(userDao.register("一笑钦陈", "123456")).thenCallRealMethod();

        // Run the test
        final Boolean result = userService.register("一笑钦陈", "123456");

        // Verify the results
        // 行为验证 -- 验证测试方法中是否执行了userService.register("一笑钦陈", "123456")这行代码
        Mockito.verify(userService.register("一笑钦陈", "123456"));

        // 结果断言 -- 验证测试方法的返回结果是否为true
        Assert.assertTrue(result);
    }
}

案例分析:

  • @Mock:创建一个mock的虚拟对象
  • @Spy:案例中没有使用到,作用是创建一个真实对象(测试时会调用真实方法,与@Mock相反)
  • @InjectMocks: 创建一个测试实例,其余用@Mock注解创建的虚拟对象将被注入到用该实例中
  • @RunWith(MockitoJUnitRunner.class):作用是将我们mock出来的虚拟对象注入到测试实例中
  • 一般测试方法包含三个部分:Setup表示初始化操作、Run the test表示运行测试、Verify the results表示验证结果
    • 初始化操作通常用于自定义mock对象的行为:指定返回结果、指定抛出异常、调用真实方法
    • 验证结果通常有两种方式:行为验证(Mockito.verify())结果断言(Assert.assertXXX())

Mokito常用的API就介绍到这里,想要解锁更多正确姿势请访问官网:http://mockito.org

六、总结

  本篇主要讲解了单元测试的基本概念,以及如何使用强大的Junit框架来为我们进行单元测试。举例说明了单元测试的一些规范和常用的API,然后由测试环境下数据库操作可能会导致线上安全隐患引申出mock这一最佳解决方案,让我们知道为什么需要mock,以及怎么通过Mockito+Junit这样一套框架来进行绝对安全的单元测试。
  为了避免篇幅过长,并未在案例上进行过多的讲解。如果大家想要了解更多的细节,欢迎在评论区与我留言交流,感谢大家的支持!文章来源地址https://uudwc.com/A/LmYAz

原文地址:https://blog.csdn.net/weixin_45236880/article/details/126816271

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

上一篇 2023年08月02日 09:44
【python】socket-传输多个文件、大文件
下一篇 2023年08月02日 09:48