当前位置: 首页 > news >正文

【JUnit实战3_16】第九章:容器内测试(上)

JUnit in Action, Third Edition

《JUnit in Action》全新第3版封面截图

写在前面
这一章给我的感觉是对前面两种模拟技术的延伸,并且更贴近真实的工作场景,毕竟企业级 Java 应用几乎天天都在和各种形式的容器打交道。做惯简单的 play-test 编程人员,一开始见识这种正规军的打法难免会有点不适应,甚至总有点吐槽的冲动——搞那么复杂,还不如手动测试来得过瘾……吐槽归吐槽,该纠偏还得纠偏,不然学到这里打退堂鼓就真的亏大了。

文章目录

  • 第九章 容器内测试(上)
    • 9.1 容器内测试的应用场景
    • 9.2 容器内测试的基本流程
    • 9.3 Stub 模拟、mock 对象模拟、容器内测试横向对比
      • 9.3.1 Stub 桩代码模拟的优缺点
      • 9.3.2 mock 对象模拟的优缺点
      • 9.3.3 容器内测试的优缺点

第九章 容器内测试(上)

本章概要

  • mock 对象模拟的局限性分析
  • 容器内测试的用法
  • Stub 模拟、mock 对象模拟及容器内测试的横向评估对比
  • Arquillian 框架用法简介

The secret of success is sincerity. Once you can fake that you’ve got it made.
成功的秘诀在于真诚。一旦能伪装出真诚,离成功也就不远了。

—— Jean Giraudoux

本章探讨了一种在应用容器内对组件进行单元测试的方法:容器内单元测试或集成测试。这里的容器不是像 Docker 那样的容器,而是像 JettyTomcat 这样的 servlet 容器,或者像 JBoss(已更名为 WildFly)那样的企业级 Java Bean(即 EJB)容器。本章还会对比容器内测试和 Stub 桩模拟、mock 对象模拟的优缺点,并介绍 Arquillian 框架 —— 一种与具体容器无关的、专门用于集成测试的 JavaEE 框架的用法。

9.1 容器内测试的应用场景

对容器内测试的迫切需求,源于常规单元测试的局限性。例如,在 servlet 语境下,如果一个类继承了抽象类 javax.servlet.http.HttpServlet,并重写了 isAuthenticated(HttpServletRequest request) 方法:

public class SampleServlet extends HttpServlet {private static final long serialVersionUID = 1L;public boolean isAuthenticated(HttpServletRequest request) {HttpSession session = request.getSession(false);if (session == null) {return false;}String authenticationAttribute = String.valueOf(session.getAttribute("authenticated"));return Boolean.parseBoolean(authenticationAttribute);}
}

按照此前 mock 对象模拟的思路,借助 Mockito 框架写出的测试用例大致如下:

@ExtendWith(MockitoExtension.class)
public class TestSampleServletWithMockito {@Mockprivate HttpServletRequest request;@Mockprivate HttpSession session;private SampleServlet servlet;@BeforeEachpublic void setUp() {servlet = new SampleServlet();}@Testpublic void testIsAuthenticatedAuthenticated() {when(request.getSession(false)).thenReturn(session);when(session.getAttribute("authenticated")).thenReturn("true");assertTrue(servlet.isAuthenticated(request));}@Testpublic void testIsAuthenticatedNotAuthenticated() {when(request.getSession(false)).thenReturn(session);when(session.getAttribute("authenticated")).thenReturn("false");assertFalse(servlet.isAuthenticated(request));}@Testpublic void testIsAuthenticatedNoSession() {when(request.getSession(false)).thenReturn(null);assertFalse(servlet.isAuthenticated(request));}
}

上述代码通过在每个测试用例中分别初始化 mock 对象、设置期望值、最后执行断言,isAuthenticated() 方法的核心逻辑全部得以验证通过,看起来没什么问题。

但是问题就出在对这些对象的模拟上:因为被测系统是在 servlet 语境下运行的,要测试 isAuthenticated() 需要一个有效的 HttpServletRequest 对象及 HttpSession 对象;而 HttpServletRequest 只是一个接口,其生命周期和具体实现是由所在的 servlet 容器提供的。只要这些对象是在容器运行时创建并管理的,测试人员就应该模拟出一个与真实场景差不多的容器环境,最好还能追踪这些对象的状态,让测试用例在一个相对真实的环境下运行,而不是脱离容器、仅凭常规的 JUnit 技术(即 JUnit 5 的功能特性、Stub 桩和 mock 对象模拟等)来测试它们。

上述 mock 对象的测试方法虽然也没问题,但遇到真实的复杂业务逻辑,运行测试前很可能需要设置大量的期望值来还原当时的容器环境,导致编写大量的辅助代码,淹没真正的测试逻辑。虽然人们总希望对容器行为的模拟尽可能精简,但往往事与愿违。一旦 servlet 环境出现变动,这些预设的期望值也必须同步更新,无形中加大了测试编码的编写难度和后期的运维成本。

总之,当代码与容器存在交互,且测试无法创建有效的容器对象(如 HttpServletRequest)时,应该首选 容器内测试(in-container testing)

9.2 容器内测试的基本流程

测试 SampleServlet 最理想的方案是在一个 servlet 容器中运行测试用例,此时无需模拟 HttpServletRequestHttpSession,而是可以直接在真实容器中访问所需的对象和方法。这就要寻求某种机制,让测试用例可以部署到容器中执行。

容器内测试大致有两种架构驱动模式:

  • 服务端驱动
  • 客户端驱动

以下为客户端驱动的示意图:

Fig9.1

基于客户端驱动的容器内测试的生命周期(基本流程)大致如下:

  1. 执行客户端测试类;
  2. 调用服务器端相同的测试用例(通过 HTTP(S) 等协议);
  3. 测试领域对象;
  4. 将结果响应给客户端。

补充:服务端驱动的基本流程

根据 DeepSeek 提供的思路,服务端驱动的大致流程如下(待验证):

Fig9.4

其基本流程概括如下:

  1. 打包并部署:将包含测试代码的应用部署到服务器。
  2. 从外部触发:通过测试客户端(通常为一个脚本或一个简单的 Java 程序)触发容器内的测试协调器。
  3. 在容器内部:由协调器利用 JUnit 执行测试类,并通过测试类调用真实的业务组件实施测试。
  4. 收集结果:最终生成测试报告,供外部客户端消费。

9.3 Stub 模拟、mock 对象模拟、容器内测试横向对比

9.3.1 Stub 桩代码模拟的优缺点

优点缺点
快速且轻量需要专门的方法来验证状态
易于编写和理解无法测试模拟对象(faked objects)的行为
功能强大在复杂交互场景下耗时较长
适合更粗粒度的测试代码变更时需要更多维护

9.3.2 mock 对象模拟的优缺点

优点缺点
测试执行无需依赖容器的运行无法测试与容器的交互,以及组件间的交互
配置和运行速度快不测试组件的部署
支持更细粒度的单元测试需充分了解待模拟的 API(尤其对外部库而言可能很困难)
无法保证代码能在目标容器中运行
更细粒度的测试也意味着测试代码很可能被接口淹没
代码变更时需要投入更多维护(与 Stub 模拟类似)

9.3.3 容器内测试的优缺点

主要优势:提供容器环境方便测试。

劣势:

  • 需要特定的工具支持:尽管概念本身具备通用性,但具体的实现工具则因容器 API 而不同:基于 servlet 容器需用 JettyTomcat;基于 EJB 容器需用 WildFly 等等;
  • IDE 的集成欠佳:常用 Maven / Gradle 在嵌入式容器中执行测试,或在持续集成服务器中运行 build 构建流程。业内普遍缺乏良好的 IDE 支持;
  • 执行时间较长:由于测试在容器中运行,需要启动和管理容器,这可能会很耗时;
  • 配置复杂:这也是容器内测试的最大缺点。为了让应用及其测试在容器中运行,项目必须打包(如 warear 文件)并部署到容器中;然后必须启动容器、运行测试。最佳实践是将该测试流程自动化并纳入整个构建体系。

(未完待续,下篇重点梳理 Arquillian 框架的基本用法,敬请关注)

http://www.dtcms.com/a/536310.html

相关文章:

  • 面向对象编程练习题
  • 【FPGA】时序逻辑计数器设计级验证
  • 大连网站建设哪里好网络营销推广方法和手段有哪些
  • __工艺数据管理的范式转变:金仓数据库替代MongoDB实操实践__
  • RV1126 NO.35:OPENCV重点结构体Mat的讲解
  • 【靶场】linux提权
  • 网站优化总结报告技术类网站模板
  • 衡水网站开发报价怎么利用自媒体做网站优化
  • 深析Springboot之自动配置
  • 基于大数据的游戏数据可视化分析与推荐系统 Steam游戏 电子游戏 娱乐数据 Flask框架 selenium爬虫 协同过滤推荐算法 python✅
  • 网站建设对公司来说重要吗wordpress页脚插件
  • TDengine 数据函数 SIN 用户手册
  • 无人机多光谱在高山松地上生物量估测的研究应用进展
  • Spring Boot3零基础教程,Kafka 小结,笔记79
  • LiveQing视频推流点播流媒体功能-支持RTSP推流支持rtmp推流无人机rtmp推流无人机rtsp推流
  • 做化妆品代理在那些网站比较多江苏公司响应式网站建设报价
  • RAG文档切片新方案HiChunk:LLM重构RAG文档分块
  • mybatis的sql中,如果一个条件column=#{column},column的值为null时,会不会匹配出column is null 的记录
  • 购物商城系统搭建实战指南
  • 怎么做网站挣钱做一下网站收购废钢
  • 专业移动微网站建设玖玖建筑网
  • 无锡建行网站网站加速cdn
  • kafka使用-admin创建
  • 渗透测试中常见的网络端口
  • 超市进销存系统管理源码 超市管理系统源码
  • Python 文件常数引用
  • 钦州电商网站建设南京网络建站公司
  • TCP数据中转服务器/广播服务器(高并发TCP服务广播)winform.netcore(net8)
  • 实验室温湿度高精度采集与监控中心集中管理实施细则
  • set和map的封装(C++)