一: 前言
俗话说: “不会测试的开发不是好开发,不会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());
}
}
(四) 执行结果:
写在最后
很多时候,找到符合自己的工具比盲目地开始更加重要,开源是现在也是未来的主流,希望能够有更多有志之士加入开源,建设更好的开源生态。