Java单元测试技巧之 JSON序列化

本文详细介绍了如何使用JSON序列化技术来简化Java单元测试代码,包括模拟和验证数据的过程。通过JSON资源文件,可以大幅减少模拟类属性值、方法参数值和返回值的代码,以及验证方法返回值和参数值的代码。同时,文章讨论了测试用例和资源的命名规范,以及资源文件的存储和来源。此外,还分享了JSON序列化和Mockito结合的巧妙用法,如模拟方法返回多个值和对应值,以及验证多次方法调用参数。
摘要由CSDN通过智能技术生成

前言

《论语》中孔子有言:“工欲善其事,必先利其器。

今年7月,作者希望迎接更大的挑战,从高德地图数据转岗到共享出行后,接手并维护了几个Java后端项目。在熟悉业务和代码的过程中,快速地对原有项目进行单元测试用例的补充,使其单元测试覆盖率达到70%+甚至于100%。有同事问我:“你写单元测试为什么这么快?”我微微一笑:“工欲善其事,必先利其器。而我快速编写Java单元测试用例的技巧就是——JSON序列化。”

是的,做任何事情,都要讲究方式方法;只要方式方法对了,就会事半功倍。这里,作者系统性地总结了JSON序列化在编写Java单元测试用例中的使用技巧,希望能够让大家“读有所得、得有所思、思有所获”。

1. 冗长的单元测试代码

在编写单元测试用例的过程中,经常会出现以下冗长的单元测试代码。

1.1. 冗长的数据模拟代码

1.1.1. 模拟类属性值

在模拟类属性值时,会遇到以下的冗长代码:

Map<Long, String> languageMap = new HashMap<>(MapHelper.DEFAULT);
languageMap.put(1L, "Java");
languageMap.put(2L, "C++");
languageMap.put(3L, "Python");
languageMap.put(4L, "JavaScript");
... // 约几十行
Whitebox.setInternalState(developmentService, "languageMap", languageMap);

1.1.2. 模拟方法参数值

在模拟方法参数值时,会遇到以下的冗长代码:

List<UserCreateVO> userCreateList = new ArrayList<>();
UserCreateVO userCreate0 = new UserCreateVO();
userCreate0.setName("Changyi");
userCreate0.setTitle("Java Developer");
... // 约几十行
userCreateList.add(userCreate0);
UserCreateVO userCreate1 = new UserCreateVO();
userCreate1.setName("Tester");
userCreate1.setTitle("Java Tester");
... // 约几十行
userCreateList.add(userCreate1);
... // 约几十条
userService.batchCreate(userCreateList);

1.1.3. 模拟方法返回值

在模拟方法返回值时,会遇到以下的冗长代码:

Long companyId = 1L;
List<UserDO> userList = new ArrayList<>();
UserDO user0 = new UserDO();
user0.setId(1L);
user0.setName("Changyi");
user0.setTitle("Java Developer");
... // 约几十行
userList.add(user0);
UserDO user1 = new UserDO();
user1.setId(2L);
user1.setName("Tester");
user1.setTitle("Java Tester");
... // 约几十行
userList.add(user1);
... // 约几十条
Mockito.doReturn(userList).when(userDAO).queryByCompanyId(companyId);

1.2. 冗长的数据验证代码

1.2.1. 验证方法返回值

在验证方法返回值时,会遇到以下的冗长代码:

Long companyId = 1L;
List<UserVO> userList = userService.queryByCompanyId(companyId);
UserVO user0 = userList.get(0);
Assert.assertEquals("name不一致", "Changyi", user0.getName());
Assert.assertEquals("title不一致", "Java Developer", user0.getTitle());
... // 约几十行
UserVO user1 = userList.get(1);
Assert.assertEquals("name不一致", "Tester", user1.getName());
Assert.assertEquals("title不一致", "Java Tester", user1.getTitle());
... // 约几十行
... // 约几十条

1.2.2. 验证方法参数值

在验证方法参数值时,会遇到以下的冗长代码:

ArgumentCaptor<List<UserDO>> userCreateListCaptor = CastUtils.cast(ArgumentCaptor.forClass(List.class));
Mockito.verify(userDAO).batchCreate(userCreateListCaptor.capture());
List<UserDO> userCreateList = userCreateListCaptor.getValue();
UserDO userCreate0 = userCreateList.get(0);
Assert.assertEquals("name不一致", "Changyi", userCreate0.getName());
Assert.assertEquals("title不一致", "Java Developer", userCreate0.getTitle());
... // 约几十行
UserDO userCreate1 = userCreateList.get(1);
Assert.assertEquals("name不一致", "Tester", userCreate1.getName());
Assert.assertEquals("title不一致", "Java Tester", userCreate1.getTitle());
... // 约几十行
... // 约几十条

2. 采用JSON序列化简化

常言道:“眼见为实,耳听为虚。”下面,就通过JSON序列化来简化上面的单元测试用例代码,让大家先睹为快。

2.1. 简化数据模拟代码

对于数据模拟,首先需要先加载JSON资源文件为字符串,然后通过JSON反序列化字符串为数据对象,最后用于模拟类属性值、方法参数值和方法返回值。这样,就精简了原来冗长的赋值语句。

2.1.1. 模拟类属性值

利用JSON反序列化,简化模拟类属性值代码如下:

String text = ResourceHelper.getResourceAsString(getClass(), path + "languageMap.json");
Map<Long, String> languageMap = JSON.parseObject(text, new TypeReference<Map<Long, String>>() {});
Whitebox.setInternalState(mobilePhoneService, "languageMap", languageMap);

其中,JSON资源文件languageMap.json的内容如下:

{1:"Java",2:"C++",3:"Python",4:"JavaScript"...}

2.1.2. 模拟方法参数值

利用JSON反序列化,简化模拟方法参数值代码如下:

String text = ResourceHelper.getResourceAsString(getClass(), path + "userCreateList.json");
List<UserCreateVO> userCreateList = JSON.parseArray(text, UserCreateVO.class);
userService.batchCreate(userCreateList);

其中,JSON资源文件userCreateList.json的内容如下:

[{"name":"Changyi","title":"Java Developer"...},{"name":"Tester","title":"Java Tester"...},...]

2.1.3. 模拟方法返回值

利用JSON反序列化,简化模拟方法返回值代码如下:

Long companyId = 1L;
String text = ResourceHelper.getResourceAsString(getClass(), path + "userList.json");
List<UserDO> userList = JSON.parseArray(text, UserDO.class);
Mockito.doReturn(userList).when(userDAO).queryByCompanyId(companyId);

其中,JSON资源文件userList.json的内容如下:

[{"id":1,"name":"Changyi","title":"Java Developer"...},{"id":2,"name":"Tester","title":"Java Tester"...},...]

2.2. 简化数据验证代码

对于数据验证,首先需要先加载JSON资源文件为字符串,然后通过JSON序列化数据对象为字符串,最后验证两字符串是否一致。这样,就精简了原来冗长的验证语句。

2.2.1. 验证方法返回值

利用JSON序列化,简化验证方法返回值代码如下:

Long companyId = 1L;
List<UserVO> userList = userService.queryByCompanyId(companyId);
String text = ResourceHelper.getResourceAsString(getClass(), path + "userList.json");
Assert.assertEquals("用户列表不一致", text, JSON.toJSONString(userList));

其中,JSON资源文件userList.json的内容如下:

[{"name":"Changyi","title":"Java Developer"...},{"name":"Tester","title":"Java Tester"...},...]

2.2.2. 验证方法参数值

利用JSON序列化,简化验证方法参数值代码如下:

ArgumentCaptor<List<UserDO>> userCreateListCaptor = CastUtils.cast(ArgumentCaptor.forClass(List.class));
Mockito.verify(userDAO).batchCreate(userCreateListCaptor.capture());
String text = ResourceHelper.getResourceAsString(getClass(), path + "userCreateList.json");
Assert.assertEquals("用户创建列表不一致", text, JSON.toJSONString(userCreateListCaptor.getValue()));

其中,JSON资源文件userCreateList.json的内容如下:

[{"name":"Changyi","title":"Java Developer"...},{"name":"Tester","title":"Java Tester"...},...]

3. 测试用例及资源命名

俗话说:“没有规矩,不成方圆。”所以,为了更好地利用JSON序列化技巧,首先对测试用例和资源文件进行规范化命名。

3.1. 测试类命名

按照行业惯例,测试类的命名应以被测试类名开头并以Test结尾。比如:UserService(用户服务类)的测试类需要命名为UserServiceTest(用户服务测试类)。

单元测试类应该放在被测试类的同一工程的"src/test/java"目录下,并且要放在被测试类的同一包下。注意,单元测试类不允许写在业务代码目录下,否则在编译时没法过滤这些测试用例。

3.2. 测试方法命名

按照行业规范,测试方法命名应以test开头并以被测试方法结尾。比如:batchCreate(批量创建)的测试方法需要命名为testBatchCreate(测试:批量创建),queryByCompanyId(根据公司标识查询)的测试方法需要命名为testQueryByCompanyId(测试:根据公司标识查询)。

当一个方法对应多个测试用例时,就需要创建多个测试方法,原有测试方法命名已经不能满足需求了。有人建议在原有的测试方法命名的基础上,添加123等序号表示不同的用例。比如:testBatchCreate1(测试:批量创建1)、testBatchCreate2(测试:批量创建2)……但是,这种方法不能明确每个单元测试的用意。

这里,作者建议在原有的测试方法命名的基础上,添加”With+条件“来表达不同的测试用例方法。

1.按照结果命名:

    • testBatchCreateWithSuccess(测试:批量创建-成功);
    • testBatchCreateWithFailure(测试:批量创建-失败);
    • testBatchCreateWithException(测试:批量创建-异常);

2.按照参数命名:

    • testBatchCreateWithListNull(测试:批量创建-列表为NULL);
    • testBatchCreateWithListEmpty(测试:批量创建-列表为空);
    • testBatchCreateWithListNotEmpty(测试:批量创建-列表不为空);

3.按照意图命名:

    • testBatchCreateWithNormal(测试:批量创建-正常);
    • testBatchCreateWithGray(测试:批量创建-灰度);
    • testBatchCreateWithException(测试:批量创建-异常);

当然,还有形成其它的测试方法命名方式,也可以把不同的测试方法命名方式混用,只要能清楚地表达出这个测试用例的涵义即可。

3.3. 测试类资源目录命名

这里,作者建议的资源目录命名方式为——以test开头且以被测试类名结尾。比如:UserService(用户服务类)的测试资源目录可以命名为testUserService。

那么,这个资源目录应该放在哪儿了?作者提供了2个选择:

    • 0
      点赞
    • 1
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值