
3.4 Spring的单元自动化测试
3.4.1 Java企业级应用简介
在本书中,Spring相关的示例均采用Spring Boot框架。
下面介绍Java企业级应用的分层,如图3-4所示。

图3-4
1.表示层
表示层负责和用户进行交互,在Web应用中,表示层由HTML、CSS和JavaScript等组成。现在,前端开发大多使用框架,而不是完全由纯粹的HTML、CSS和JavaScript实现,常见的前端框架有jQuery、Bootstrap、Vue.js和Angular等。
2.控制器层
控制器层用来接收用户请求,并转发给业务逻辑的处理组件,即转发给业务逻辑层。在Java企业级应用开发中,一般由Spring MVC框架充当该角色。
3.业务逻辑层
业务逻辑层是用来实现业务逻辑的,包含Service、ServiceImpl和VO(Value Object,值对象)等。一般来说,Service为接口,ServiceImpl为接口的具体实现类,VO用来在业务逻辑层进行数据传递。在Java企业级应用开发中,一般由Spring框架充当该角色。
4.数据访问层
数据访问层负责与数据库打交道,包含数据访问对象(Data Access Object,DAO)、DAOImpl和持久化对象(Persistent Object,PO)等。一般来说,数据访问对象为接口,DAOImpl为接口的具体实现类。持久化对象为对象关系映射(Object Relational Mapping,ORM),一个PO对象代表数据库的一条表记录,PO对象的属性代表数据库的表字段。数据访问层的框架较多,包括Spring Data、MyBatis和Hibernate等。
5.持久化层
持久化层的作用是将数据存储在磁盘上。一般由数据库充当该角色,比如MySQL、SQL Server或Oracle等。
在Java企业级应用的整合开发中,有两个经典的框架组合,SSH和SSM,它们分别用了不同的框架作为控制器层、业务逻辑层和数据访问层的实现,如表3-1所示。
表3-1

SSH框架组合目前已没落,SSM框架是当前主流框架组合。
说到这里又不得不介绍另一个重要概念——模板引擎(Template Engine)。模板引擎的作用是将用户界面和业务数据分离。常用的Java模板引擎有JSP、FreeMarker、Velocity和Thymeleaf等,Spring Boot默认集成Thymeleaf作为模板引擎。
3.4.2 编写待测程序
1.工程创建
创建一个新的Maven项目,关键信息填写如图3-5所示。

图3-5
创建完成后,在pom.xml文件的<name>标签后输入以下粗体部分内容:


这里添加了3个依赖,其中spring-boot-starter-web是Web开发的基础;spring-boot-starter-thymeleaf是模板引擎;spring-boot-starter-test是测试组件,可用于单元自动化测试。保存pom.xml文件,这时Maven会自动下载Spring Boot及其依赖的其他jar包。需要说明的是,Spring Boot项目的依赖较多,下载时间较长,请耐心等待。
2.编写待测程序
下面用Web应用的方式实现本章开头的像素密度计算器。出于个人习惯,笔者喜欢先开发后端,再开发前端。
(1)持久化层实现和数据访问层实现
本程序不需要将数据持久化,因此既不需要实现持久化层,也不需要实现数据访问层。
(2)业务逻辑层实现
在src/main/java中创建名为com.lujiatao.springboot.service的Package及名为CalculatorForPpiService的Class,在CalculatorForPpiService中输入以下内容:

@Service是Spring框架的业务逻辑层注解。calculate方法与之前的保持一致。
(3)控制器层实现
在src/main/java中创建名为com.lujiatao.springboot.controller的Package及名为IndexController和CalculatorForPpiController的Class,在IndexController和CalculatorForPpiController中分别输入内容。

@Controller是Spring框架的控制器层注解,另外,常用的Spring框架的控制器层注解还有@RestController。
@RequestMapping表示接收用户请求。此处代表当用户请求/index时,使用index方法处理。index方法中返回的“index-page”代表名为“index-page”的页面。
CalculatorForPpiController中的代码如下:

@Resource是一种注入注解,将一个Bean注入当前类中,即可使用该Bean。@RequestParam注解用于接收请求参数,这里使用了3次,分别接收前端传入的with、height和size参数。最后calculator()方法返回了一个JSON格式的字符串,字符串中的反斜杠“\”代表转义。
(4)表示层实现
在src/main/resources中创建名为templates的目录,在templates目录中创建index-page.html文件,文件内容如下:


<html>标签中的xmlns属性表明需要用Thymeleaf作为模板引擎。<head>标签中使用CDN方式引入jQuery。<body>中有一个表单,该表单包含3个输入框、1个按钮和1个标签。JavaScript脚本通过AJAX发送异步请求传递屏幕的宽、高和尺寸,请求地址为/calculate,请求方式为POST。后端返回结果后首先解析返回的数据并提取内容,然后将内容显示在表单的标签中,此过程是一个局部刷新页面的过程。
(5)程序入口实现
上述步骤已经实现了程序的功能,但此时程序并没有运行入口,因此无法运行。在src/main/java中创建名为com.lujiatao.springboot的Package及名为Application的Class,在Application中输入以下内容:

@SpringBootApplication注解代表将程序作为Spring Boot应用来运行。调用SpringApplication中的run方法,并传入当前Class作为参数来运行程序,该方法同时传入了main()方法中的args参数。
保存所做的修改,按快捷键F11运行工程,此时Eclipse的控制台输出如下:


可以看到,Spring Boot默认使用Tomcat作为Web服务器,并默认使用8080作为端口号。打开浏览器,访问http://localhost:8080/index,如图3-6所示。

图3-6
在输入框依次输入750、1334和4.7,单击“计算”按钮,计算结果如图3-7所示。

图3-7
至此,用Web应用的方式实现本章开头的像素密度计算器就开发完成了。
3.4.3 单元自动化测试
在此之前需要配置TestNG依赖,然后在工程(springboot)上用鼠标右击,从弹出的快捷菜单中选择“TestNG → Convert to TestNG”选项,在工程中生成testng.xml文件。
在Spring项目中,一般会对业务逻辑层(Service)和控制器层(Controller)进行单元自动化测试,对数据访问层(DAO)进行单元自动化测试的相对较少,因此本章不做介绍。
1.业务逻辑层单元自动化测试
在src/test/java中创建名为com.lujiatao.springboot.service的Package及名为CalculatorForPpiServiceTest的Class,在CalculatorForPpiServiceTest中输入以下内容:


下面对代码进行说明。
①@SpringBootTest注解为Spring Boot提供的专为测试设计的注解,classes方法的作用是传入启动类。
②AbstractTestNGSpringContextTests是一个抽象类,是Spring Boot专为TestNG打造的。
③@Autowired注解的作用类似于@Resource注解,不同的是,@Autowired注解属于Spring框架,而@Resource注解不属于Spring框架。
④测试用例的写法和之前保持一致,此处不再赘述。
在tesng.xml文件的<test>标签中输入以下粗体部分的代码:

保存所做的修改,在testng.xml上用鼠标右击,从弹出的快捷菜单中选择“Run As → TestNG Suite”选项,查看测试报告,如图3-8所示。

图3-8
可以看到,运行结果与预期相符。
2.控制器层单元自动化测试
在src/test/java中创建名为com.lujiatao.springboot.controller的Package及名为CalculatorForPpiControllerTest的Class,在CalculatorForPpiControllerTest中输入以下内容:



下面对代码进行说明。
①使用@Autowired注解将要使用的Controller注入当前类。这里使用的是Spring的Mock类MockMvc。控制器层的单元测试需要用MockMvc模拟请求的调用。
②在init()方法中使用注入的Controller创建一个MockMvc实例,这里使用的方法是standaloneSetup(),这种测试方式为独立测试方式。另外,也可以使用webAppContextSetup()方法,这种测试方式为集成Web测试方式。
③这里将7条测试用例的逻辑全部封装在了sendRequest方法中,每条测试用例只需传递参数给sendRequest方法即可。在sendRequest方法中,首先通过MockMvcRequestBuilders构建一个请求对象,然后使用MockMvc对象调用该请求,接着使用andExpect()方法添加断言。在andExpect()方法中,通过提取后端返回的JSON与预期结果进行比较。
④在请求的调用和断言过程中可能会抛出异常,因此需要对异常进行处理。这里对异常处理的方式是捕获异常。
修改tesng.xml文件,修改以下粗体部分内容:

保存所做的修改,在testng.xml上用鼠标右击,从弹出的快捷菜单中选择“Run As → TestNG Suite”选项,查看测试报告,如图3-9所示。

图3-9
测试用例全部执行通过,运行结果与预期相符。