java web测试体系

引言

-. 从测试目的来讲,分为功能测试,性能测试,安全测试,特性测试
-. 从测试V模型范围来讲,分为单元测试,集成测试,系统测试,验收测试
-. 从mvc架构来讲,测试分为dao,service,controller,view

本文旨在为程序开发人员在java web开发项目中的测试进行分析归纳

程序开发人员场景

  1. 单元测试
    单元测试更多使用mock测试,减少外部依赖影响,仅测试当前对象

  2. 切片测试
    slice是指一些在特定环境下才能执行的模块,比如MVC中的Controller、JDBC数据库访问、Redis客户端等,这些模块大多脱离特定环境后不能独立运行,需要提供测试上下文环境进行测试,一般

    1. 提供jpa上下文,对dao层进行测试
    2. 提供bean上下文,对服务层进行测试
    3. 提供bean与servlet上下文,对controller进行测试
  3. 功能测试
    使用相对完整的系统上下文进行测试,针对web服务一些特殊的期望测试,一般可以

    1. 使用restTemplate,模拟http请求进行测试
    2. 使用htmlunit,模拟ui层面的交互和验证
      3.使用mockmvc模拟httpServlet,系统测试controller,service,jpa

配合使用dbunit,flyway进行数据初始化配合测试进行验证

一. junit

推荐最佳实践

  • 测试方法上必须使用@Test进行修饰
  • 测试方法必须使用public void 进行修饰,不能带任何的参数
  • 新建一个源代码目录来存放我们的测试代码,即将测试代码和项目业务代码分开
  • 测试类所在的包名应该和被测试类所在的包名保持一致
  • 测试单元中的每个方法必须可以独立测试,测试方法间不能有任何的依赖
  • 测试类使用Test作为类名的后缀(不是必须)
  • 测试方法使用test作为方法名的前缀(不是必须)
    常用注解
*`@BeforeClass`*   在所有测试方法前执行一次,一般在其中写上整体初始化的代码 
*`@AfterClass`*   在所有测试方法后执行一次,一般在其中写上销毁和释放资源的代码 
*`@Before`*   在每个测试方法前执行,一般用来初始化方法(比如我们在测试别的方法时,类中与其他测试方法共享的值已经被改变,为了保证测试结果的有效性,我们会在@Before注解的方法中重置数据) 
*`@After`*  在每个测试方法后执行,在方法执行完成后要做的事情 
*`@Test(timeout = 1000)`* 测试方法执行超过1000毫秒后算超时,测试将失败 
*`@Test(expected = Exception.class)`* 测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败 
*`@Ignore(“not ready yet”)`*        执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类 
 *`@Test `*  编写一般测试用例
 *`@RunWith`*    在JUnit中有很多个Runner,他们负责调用你的测试代码,每一个Runner都有各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。 
如果我们只是简单的做普通Java测试,不涉及Spring Web项目,你可以省略@RunWith注解,这样系统会自动使用默认Runner来运行你的代码。

与testNg对比
a.两个框架的不同在于核心设计。JUnit 一直 是一个单元测试框架,也就是说,其构建目的是促进单个对象的测试,它确实能够极其有效地完成此类任务。而 TestNG 则是用来解决更高 级别的测试问题,
b.testNg在依赖性测试,失败和重运行,参数化测试具备一定优势,同样这些都是大规模测试准备的

二. spring test

Spring Test与JUnit等其他测试框架结合起来,提供了便捷高效的测试手段

基础

-. 依赖:org.springframework.spring-test.version
-. SpringJUnit4ClassRunner spring提供的junit测试环境
-. @ContextConfiguration加载上下文注解
1. locations属性:引入xml文件,
2. classes属性:引入bean对象

-. @TestExecutionListeners
定义了一个类级别的元数据,用于配置需要用TestContextManager进行注册的TestExecutionListener实现

-. @WebAppConfiguration
用于声明一个ApplicationContext,集成测试加载WebApplicationContext,模拟ServletContext

-. @DirtiesContext
使用@DirtiesContext,可以保证每个test class的执行上下文的独立性、隔离性,但是也会有让测试运行速度变慢的副作用。所以在使用@DirtiesContext前,弄清楚你是否真的需要使用它

-. @TestPropertySource
is a class-level annotation that configures locations of property files and inlined properties in Spring integration test.

进阶

-. TestContext
TestContext 测试框架的核心由 org.springframework.test.context 中三个类组成,分别是

  • TestContext:它封装了运行测试用例的上下文
  • TestContextManager:它是进入 Spring TestContext 框架的程序主入口,它管理着一个 TestContext 实例,并在适合的执行点上向所有注册在 TestContextManager 中的 TestExecutionListener 监听器发布事件:比如测试用例实例的准备,测试方法执行前后方法的调用等
  • TestExecutionListener:该接口负责响应 TestContextManager 发布的事件

-. TestExecutionListener
+. DependencyInjectionTestExecutionListener:该监听器提供了自动注入的功能,它负责解析测试用例中 @Autowried 注解并完成自动注入

  • DirtiesContextTestExecutionListener:一般情况下测试方法并不会对 Spring 容器上下文造成破坏(改变 Bean 的配置信息等),如果某个测试方法确实会破坏 Spring 容器上下文,你可以显式地为该测试方法添加 @DirtiesContext 注解,以便 Spring TestContext 在测试该方法后刷新 Spring 容器的上下文,而 DirtiesContextTestExecutionListener 监听器的工作就是解析 @DirtiesContext 注解
    +. TransactionalTestExecutionListener:它负责解析 @Transaction、@NotTransactional 以及 @Rollback 等事务注解的注解
    +. MockitoTestExecutionListener 解析:@Mock:模拟bean,@InjectMocks:注入模拟bean
    +. DbUnitTestExecutionListener 解析:@DatabaseSetup, @DatabaseTearDown 和 @ExpectedDatabase

三. springboot test

Spring Boot Test 是在Spring Test之上的再次封装,增加了切片测试,增强了mock能力。

  • 基于junit测试框架,延续spring test,使用一些断言库,mock库完成测试工作
  • 提供两个核心模块:1.核心测试支持,2.spring-boot-test-autoconfig
  • 其依赖有junit.spring test,assertj,hamcrest,mockito,jsonpath,jsonAssert

1. 基础

  1. 依赖

    org.springframework.boot
    spring-boot-starter-test
    test

+. @SpringRunner
继承SpringJUnit4ClassRunner,可能是为了缩减命名长度吧

+. @SpringBootTest
- 此注解替代了spring-test中的@ContextConfiguration注解,目的是加载ApplicationContext,启动spring容器,
- @SpringBootTest注解会自动检索程序的配置文件,检索顺序是从当前包开始,逐级向上查找被@SpringBootApplication或@SpringBootConfiguration注解的类
+. @AutoConfigureWebMvc
- 启用与Web层相关的所有自动配置,并且仅启用Web层.这是整体自动配置的子集

+. @AutoConfigureMockMvc
- 启用与MockMvc和ONLY MockMvc相关的所有自动配置.同样,这是整体自动配置的子集.

+. @WebMvcTest
- 包括@AutoConfigureWebMvc和@AutoConfigureMockMvc以及其他功能.
-
+. @MockBean
- 提供mock支持,在运行时会扫描到你注释的 MockBean ,并自动装配到被测试的 controller 里面。这也是和 @Mock 注释不同的地方,后者只能生成一个 Mock 类,但是并不能自动装配到其它类里面。
- @SpyBean 与 @Spy 的关系类似于 @MockBean 与 @Mock 的关系。和 @MockBean 不同的是,它不会生成一个 Bean 的替代品装配到类中,而是会监听一个真正的 Bean 中某些特定的方法,并在调用这些方法时给出指定的反馈。
- @Mock: 创建一个Mock.
- @InjectMocks: 创建一个实例,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中

+. @TestConfiguration
- 提供一些测试相关的配置入口

四. 实践

测试过程中的几个关键要素
项目结构:maven提供依赖与结构管理
测试框架:junit
容器管理:springboot
mock能力:Mockito提供了强大mock功能。
断言能力:AssertJ、Hamcrest、JsonPath提供了强大的断言能力

单元测试

使用junit+mock进行测试

ps:单元测试仅仅测试当前单元类,其依赖均使用mock模拟实现;集成测试时也会用mock模拟一些边界来配合测试,如HttpRequest等,但是会测试到相关依赖类的实现是否正确

集成测试

支撑方式:通过@RunWith 和 @SpringBootTest启动spring容器

切片测试:一般面向难于测试的边界功能,介于单元测试和功能测试之间。涉及到的注解 @WebMvcTest,@DataJpaTest等。

功能测试:一般面向某个完整的业务功能,同时也可以使用切面测试中的mock能力,使用Mockmvc进行测试,推荐使用。涉及到的注解有@RunWith @SpringBootTest等。

  1. 关于web
    在spring没有为此提供测试支持,开发者只能启动完整服务对这些模块进行测试,这在一些复杂的系统中非常不方便,所以springboot为这些模块提供了测试支持,使开发者有能力单独对这些模块进行测试。
  • @SpringBootTest注解中,给出了webEnvironment参数指定了web的environment

    1. MOCK:此值为默认值,该类型提供一个mock环境,可以和@AutoConfigureMockMvc或@AutoConfigureWebTestClient搭配使用,开启Mock相关的功能。注意此时内嵌的服务(servlet容器)并没有真正启动,也不会监听web服务端口。
    2. RANDOM_PORT:启动一个真实的web服务,监听一个随机端口。
    3. DEFINED_PORT:启动一个真实的web服务,监听一个定义好的端口(从application.properties读取)。
    4. NONE:启动一个非web的ApplicationContext,既不提供mock环境,也不提供真实的web服务
  • @WebMvcTest(IndexController.class)
    使用@WebMvcTest和MockMvc搭配使用,可以在不启动web容器的情况下,对Controller进行测试

仅仅只是对controller进行简单的测试,如果Controller中依赖用@Autowired注入的service、dao等则不能这样测试,如果要测试,则必须mock相应的service,dao来进行测试

五. 其它

  1. mock测试
    打桩(Stub)或则模拟,当你调用一个不好在测试中创建的对象时,Mock框架为你模拟一个和真实对象类似的替身来完成相应的行为
    在进行单元测试时,经常遇到被测方法依赖外部对象和环境,如需要数据库连接,网络通信依赖等,需要进行大量的初始化工作,这时可以采用powermock+mockito对被测对象进行模拟,通过录放的形式解决此类问题。

  2. Mockito
    它与 EasyMock 和 jMock 很相似,都是为了简化单元测试过程中测试上下文 ( 或者称之为测试驱动函数以及桩函数 ) 的搭建而开发的工具。
    方法名 描述
    Mockito.mock(classToMock) 模拟对象
    Mockito.verify(mock) 验证行为是否发生
    Mockito.when(methodCall).thenReturn(value1).thenReturn(value2) 触发时第一次返回value1,第n次都返回value2
    Mockito.doThrow(toBeThrown).when(mock).[method] 模拟抛出异常。
    Mockito.mock(classToMock,defaultAnswer) 使用默认Answer模拟对象
    Mockito.when(methodCall).thenReturn(value) 参数匹配
    Mockito.doReturn(toBeReturned).when(mock).[method] 参数匹配(直接执行不判断)
    Mockito.when(methodCall).thenAnswer(answer)) 预期回调接口生成期望值
    Mockito.doAnswer(answer).when(methodCall).[method] 预期回调接口生成期望值(直接执行不判断)
    Mockito.spy(Object) 用spy监控真实对象,设置真实对象行为
    Mockito.doNothing().when(mock).[method] 不做任何返回
    Mockito.doCallRealMethod().when(mock).[method] //等价于Mockito.when(mock.[method]).thenCallRealMethod(); 调用真实的方法
    reset(mock) 重置mock

  3. PowerMock
    在其它单元测试模拟框架的基础上做出的扩展。通过提供定制的类加载器以及一些字节码篡改技巧的应用,对静态方法、构造方法、私有方法以及 Final 方法的模拟支持,对静态初始化过程的移除等强大的功能。
    现如今比较流行的Mock工具如jMock 、EasyMock 、Mockito等都有一个共同的缺点:不能mock静态、final、私有方法等。而PowerMock能够完美的弥补以上三个Mock工具的不足。
    它有两个重要的依赖:javassist和objenesis。
    javassist是一个修改java字节码的工具包,
    objenesis是一个绕过构造方法来实例化一个对象的工具包
    由此看来,PowerMock的本质是通过修改字节码来实现对静态和final等方法的mock的。当某个测试方法被注解@PrepareFordytest标注以后,在运行测试用例时,会创建一个新的org.powermock.core.classloader.MockClassLoader实例,
    然后加载该测试用例使用到的类(系统类除外)。
    • PowerMock会根据你的mock要求,去修改写在注解@PrepareFordytest里的class文件(当前测试类会自动加入注解中),以满足特殊的mock需求。例如:去除final方法的final标识,在静态方法的最前面加入自己的虚拟实现等。
    • 如果需要mock的是系统类的final方法和静态方法,PowerMock不会直接修改系统类的class文件,而是修改调用系统类的class文件,以满足mock需求。

  4. EasyMock
    提供了根据指定接口动态构建 Mock 对象的方法,避免了手工编写 Mock 对象

  5. Hamcrest
    是一个书写匹配器对象时允许直接定义匹配规则的框架.有大量的匹配器是侵入式的

  6. AseertJ:
    JAVA 流式断言器,支持一条断言语句对实际值同时断言多个校验点