不会测试的程序员不是好程序员(一文让你掌握JMockit的使用)

2022-09-13 16:03:34 浏览数 (3)

一: 前言

  俗话说: “不会测试的开发不是好开发,不会Mock的测试不是好测试”。测试在开发中时必不可少的一环,正规的开发流程中,只有自测通过了,才会将功能提交到真正的测试人员中进行其他测试。对自己负责就是对他人负责,所以,学会测试是每个开发人员必备的一项技术。

二: 常见的测试种类

  • 1、单元测试: 对功能的最小单位(方法)进行测试(包括黑盒、白盒、灰盒测试)。
  • 2、白盒测试: 不但关注测试的输入数据和输出结果,还关注程序的执行逻辑。
  • 3、黑盒测试: 对程序的执行逻辑不关注,只关注测试的输入和输出。
  • 4、灰盒测试: 介于白盒测试与黑盒测试之间,注重程序运行的逻辑流程
  • 5、压力测试: 测试同一时间,并发情况下程序的执行情况。
  • 6、.....

三: JMockit的定义

  Mock: 英文意思有模仿、嘲笑的含义。

  JMockit: 是一种Java类/接口/对象的Mock工具,是现在JAVA程序单元测试比较常见的方式。

  Mock工具的种类: 常见的EasyMock、Mockito等。

  为什么选择JMockit: 首先、它是完全以面向对象的方式提供API,其次,它是其他Mock工具的功能的集大成者,学习知识就要学习功能性比较完成的,选择JMockit时正确的选择,具体功能对比如下:

四: JMockit常用知识讲解

  一: JMockit常用的知识点

  (一): JMockit主要是由:测试属性或测试参数,测试方法组成。其中测试方法又包括; 录制代码块,重放测试逻辑,验证代码块三部分。

  (二)测试属性:既测试类中的一个属性,它作用于测试类的所有方法,可以使用JMockit中的@Mocked, @Tested, @Injectable,@Capturing进行修饰。添加上这些注解表示这个属性它的实例化,属性赋值,方法调用的返回值全部由JMockit来接管,也就是意味着可以通过录制行为来自定义测试属性的具体实现,具体图如下:

  (三) 测试参数(其实就是方法的参数,跟测试属性的区别就是作用域不一样):

  在测试类中参数加了JMockit的注解API(@Mocked, @Tested, @Injectable,@Capturing),原本的测试方法是不可以添加参数的,但是如果参数中添加了JMockit的注解的话,就可以在测试方法中添加参数,它表示的意思是仅作用于当前的测试方法,具体图如下:

  (四): 测试方法

  由录制代码块,重放测试逻辑,验证代码块三部分组成,既Record-Replay-Verification。

  1、Record(录制): 即先录制某类/对象的某个方法调用,在当输入什么时,返回什么。

2、Replay(重放): 重放测试逻辑,实际上就是调用上面录制的方法

3、重放后的验证。比如验证某个方法有没有被调用,调用多少次。

  二: JMockit常用API注解及区别

  (一) @Mocked:修饰类/接口时,就是告诉JMockit,生成一个被修饰类/接口的Mock对象,这个对象中方法(包含静态方法)返回默认值。即如果返回类型为原始类型(short,int,float,double,long)就返回0,如果返回类型为String就返回null,如果返回类型是其它引用类型,则返回这个引用类型的Mocked对象(就类型与生成一个假对象,这个对象里面方法逻辑不存在,可以通过录制行为去控制)。

  (二) Mocked的使用场景: 测试程序依赖某个接口时,用@Mocked非常适合了。只需要@Mocked一个注解,JMockit就能帮我们生成这个接口的实例。如: 分布式系统中存在一个服务调用另一个服务的接口时,可以直接通过Mock生成实例,不需要启动微服务测试。

  (三) @Injectable注解:作用是告诉JMockit生成一个Mocked对象,但@Injectable只是针对其修饰的实例,而@Mocked是针对其修饰类的所有实例,@Injectable对类的静态方法,构造函数没有影响。因为它只影响某一个实例。

  (四) @Tested: 作用是表示被测试对象。如果该对象没有赋值,JMockit会去实例化它,若@Tested的构造函数有参数,则JMockit通过在测试属性&测试参数中查找@Injectable修饰的Mocked对象注入@Tested对象的构造函数来实例化,

  不然,则用无参构造函数来实例化。除了构造函数的注入,JMockit还会通过属性查找的方式,把@Injectable对象注入到@Tested对象中。注入的匹配规则:先类型,再名称(构造函数参数名,类的属性名)。若找到多个可以注入的@Injectable,则选择最优先定义的@Injectable对象。

  (五)@Tested & @Injectable 的使用场景:需要手工管理被测试类的依赖时,就需要用到@Tested & @Injectable。两者搭配起来用,JMockit就能帮我们轻松搞定被测试类及其依赖注入细节。

五: JMockit实战

  (一) 引入依赖坐标(JUnit4.x及以下用户特别注意事项,如果你是通过mvn test来运行你的测试程序 , 请确保JMockit的依赖定义出现在JUnit的依赖之前,否则会报错):

代码语言:javascript复制
 <!-- https://mvnrepository.com/artifact/org.jmockit/jmockit -->
        <dependency>
            <groupId>org.jmockit</groupId>
            <artifactId>jmockit</artifactId>
            <version>${jmockit.version}</version>
        </dependency>

  (二) 构建需要测试的测试类

代码语言:javascript复制
// 测试数据类,常规来说我们如果想调用这些方法则需要通过Mybatis或者其数据层操作组件才能够调用
// 但测试通过JMockit可以Mock一个Dao对象,来完成我们的需求
public interface UserDao {

    Integer deleteUser(Integer id);

    User getUserById(Integer id);

    List<User> getUserList();

    Integer insertUser(User user);

    Integer updateUser(User user);

    Integer test(Integer test);
}

  (三) 书写测试类:

代码语言:javascript复制
public class JmockitDemo {
    // 这是一个测试属性,使用Mock标识,表示UserDao的它的实例化,属性赋值,
    // 方法调用的返回值全部由JMockit来接管,可以通过录制行为来控制属性中的行为
    @Mocked
    UserDao userDao;

    @Test
    public void testSeletById(){
        User users = new User(19);
        /**
         * 步骤:
         *  1、录制行为-Record,设置预期值
         *  2、重放行为-replay,查看预期值
         *  3、校验行为-verification,用于查看程序执行是否跟自己设想一直
         *  注意事项:
         *  你录制多少个行为,就需要在下面播放多少个行为,否则会报错: Missing 1 invocation to(缺少预期行为的调用)
         */
        // 可以录制多个行为
        new Expectations(){
            {
                // 加上删除用户时传入12,我预期返回的结果是result后的值
                userDao.deleteUser(12);
                // result表示执行上面的行为后,预期返回的结果
                result = 123;
                userDao.getUserById(13);
                // result表示执行上面的行为后,预期返回的结果
                result = new User(12);
                userDao.getUserList();
                List<User> a= new ArrayList<>();
                User user = new User(123);
                User user1 = new User(124);
                a.add(user);
                a.add(user1);
                // result表示执行上面的行为后,预期返回的结果
                result = a;
            }
        };
        //..Assert.assertTrue(123 == userDao.deleteUser(12));
        // 重放
        Integer integer = userDao.deleteUser(12);
        System.out.println(integer);
        User userById = userDao.getUserById(13);
        System.out.println(userById.getId());
        //Assert.assertTrue(123 == userDao.getUserById(13).getId());
    }
}

  (四) 执行结果:

写在最后

很多时候,找到符合自己的工具比盲目地开始更加重要,开源是现在也是未来的主流,希望能够有更多有志之士加入开源,建设更好的开源生态。

0 人点赞