尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

PDF测试遇“no tests found”?Maven Surefire与JUnit 5排查实战

PDF测试遇“no tests found”?Maven Surefire与JUnit 5排查实战 在最近一个测试工程里我遇到了一个非常“奇怪”的现象代码明明写了Test方法项目也能正常编译可一旦执行mvn test终端却直接抛出no tests found for given includes: ...。更巧的是这个项目的src/test/resources里放了一批 PDF 文件用来做文档解析与内容提取测试。排查到最后才发现问题出在测试资源和测试类的“身份混淆”上。这篇文章就把这次 PDF 测试场景的完整踩坑过程、根因分析和自动化测试方案整理出来希望对你处理 PDF 相关测试时有所帮助。1. 问题背景一条诡异的测试报错先还原一下当时的报错场景。在项目中执行测试时Maven 显示构建成功但测试总数为 0[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) pdf-tests-demo --- [INFO] No tests to run.换一种运行方式比如在 IDEA 中右键某个测试类直接运行又会看到类似这样的报错no tests found for given includes: [com.example.demo.PdfContentTest]这行报错信息字面意思是“在给定的 includes 规则中没有找到任何测试”但它并不是说PdfContentTest这个类不存在而是说 Maven Surefire 或 JUnit Platform 在扫描类时没有把这个类识别为一个可执行的测试类。这个报错和 PDF 有什么关系主要有两种常见场景测试类命名不符合 Maven Surefire 的默认扫描规则例如类名是PdfContentCheck而不是PdfContentTest导致测试根本没有进入扫描范围。PDF 文件被错误地放进了测试源码目录如src/test/java文件格式识别异常引发一系列预料之外的资源加载或扫描问题。如果说你只是单纯写 PDF 文档解析功能可能不会碰到这个报错但一旦你把 PDF 作为测试数据、测试样本或断言对象集成到自动化测试体系中就很可能会踩到这些“测试框架本身”的坑。本文会从根因开始讲再带你搭建一套完整、可复用的 PDF 自动化测试项目。2. 环境准备与版本说明因为本文涉及具体代码先明确一下实验环境。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。工具/组件说明JDKJDK 8 及以上版本均可示例以 JDK 8 语法为主构建工具Maven 3.6测试框架JUnit 5JUnit JupiterPDF 解析库Apache PDFBox 2.xIDEIntelliJ IDEA 或 Eclipse可根据个人习惯选择如果使用 Maven 构建项目核心依赖需要保持版本匹配。尤其是 JUnit 5 和 Maven Surefire 插件之间存在版本联动很多 “No tests found” 问题就是插件版本过低导致的。这里我使用 PDFBox 2.x 版本因为 2.x 的 API 资料最多、兼容性最好。PDFBox 3.x 已经发布API 上有一些变化比如PDDocument.load(File)改为Loader.loadPDF(File)如果你的项目使用 3.x需要留意差异。后续的示例是一个标准的 Maven 工程目录结构如下pdf-tests-demo ├── pom.xml ├── src │ ├── main │ │ └── java │ │ └── com │ │ └── example │ │ └── pdf │ │ └── PdfTextUtils.java │ └── test │ ├── java │ │ └── com │ │ └── example │ │ └── pdf │ │ └── PdfContentTest.java │ └── resources │ └── sample.pdf3. “no tests found for given includes” 的根因拆解这个报错有一个很迷惑人的地方它出现在构建工具、测试框架和 IDE 三套体系交会的位置任何一层扫描失败都可能抛出同样的信息。3.1 Maven Surefire 的测试类扫描规则Maven 执行mvn test时默认使用maven-surefire-plugin扫描src/test/java下的测试类。它的默认包含规则是**/Test*.java**/*Test.java**/*Tests.java**/*TestCase.java如果测试类命名为PdfContentCheck、PdfValidator就不符合上述任意一条规则Surefire 会直接跳过。这在报错上有时表现为“找不到测试”有时表现为“Build 成功但没有任何测试被执行”。因此排查这类问题时要先确认类名是否满足默认规则。如果确实需要自定义命名必须在pom.xml中显式配置plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-surefire-plugin/artifactId version2.22.2/version configuration includes include**/*Check.java/include include**/*Tests.java/include include**/*Test.java/include /includes /configuration /plugin3.2 测试资源与源码目录混淆这是让我印象最深的一个坑。当时项目里需要把 PDF 样本作为测试资源不知道哪位同事把sample.pdf直接放到了src/test/java/com/example/pdf/目录下。从 IDEA 的 Project 视图看PDF 文件确实在那儿躺着但 Maven 编译测试源码时会把这个目录当作 Java 源码目录处理虽然 PDF 不会被编译却可能干扰 IDE 对源目录的扫描索引导致测试类的加载出现异常。同理如果测试资源放错位置运行时getResource()也可能拿不到文件。正确做法是PDF 测试样本统一放到src/test/resources/测试类统一放到src/test/java/不要将非 Java 文件放到测试源码目录中3.3 JUnit 5 与 Surefire 版本不匹配另一个高频原因是 JUnit 5 依赖没有正确传递给 Surefire。Surefire 从 2.22.0 开始才原生支持 JUnit Platform如果你还在用 2.12.4 之类的旧版本或者项目并没有显式引入junit-jupiter-engine就会出现测试类虽然存在但 JUnit 平台找不到入口的情况。下面的pom.xml依赖组合是 JUnit 5 官方推荐的基础配置dependencies dependency groupIdorg.junit.jupiter/groupId artifactIdjunit-jupiter/artifactId version5.8.2/version scopetest/scope /dependency /dependencies3.4 反模式案例PDF 文件导致的测试静默失败有一个容易忽略的细节是测试类中如果通过BeforeAll加载 PDF 资源而 PDF 路径写错或文件损坏异常可能被吞掉测试类初始化失败JUnit 认为没有任何可执行的测试方法最终也会显示no tests found。例如这样不规范的写法BeforeAll static void init() throws IOException { // 如果路径写错整个测试类初始化失败 File pdfFile new File(src/test/resources/missing.pdf); PDDocument.load(pdfFile); }一旦missing.pdf不存在PDDocument.load会抛出异常测试类初始化失败JUnit 平台把整个类标记为不可执行。因此加载测试资源时应优先使用类路径方式增加可移植性。4. PDF 自动化测试完整实战排错之后我们进入正题如何把 PDF 文件纳入自动化测试体系。下面通过一个完整的 Maven 工程演示 PDF 文本抽取、内容断言、页数校验和元数据校验的方法。4.1 添加 Maven 依赖创建项目后先在pom.xml中添加 JUnit 5 与 PDFBox 依赖dependencies dependency groupIdorg.junit.jupiter/groupId artifactIdjunit-jupiter/artifactId version5.8.2/version scopetest/scope /dependency dependency groupIdorg.apache.pdfbox/groupId artifactIdpdfbox/artifactId version2.0.27/version /dependency /dependencies build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-surefire-plugin/artifactId version2.22.2/version /plugin plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.8.1/version configuration source1.8/source target1.8/target /configuration /plugin /plugins /build4.2 准备测试用 PDF为了不依赖外部网络资源我们可以用代码生成一个最简单的 PDF 文件写入固定的测试文本。这样每次测试都能复现不会出现“外部 PDF 下载失败”的问题。生成 PDF 的代码可以单独写成一个工具方法放在src/test/java/com/example/pdf/PdfTestDataGenerator.javapackage com.example.pdf; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.font.PDType1Font; import java.io.IOException; /** * 测试数据生成器用于生成包含指定文本的 PDF 文件。 */ public class PdfTestDataGenerator { private PdfTestDataGenerator() { } public static void createSamplePdf(String outputPath, String content) throws IOException { try (PDDocument document new PDDocument()) { PDPage page new PDPage(); document.addPage(page); try (PDPageContentStream contentStream new PDPageContentStream(document, page)) { contentStream.beginText(); contentStream.setFont(PDType1Font.HELVETICA, 14); contentStream.newLineAtOffset(100, 700); contentStream.showText(content); contentStream.endText(); } document.save(outputPath); } } }这里用到了 PDFBox 的PDDocument、PDPage、PDPageContentStream三个核心类分别负责文档对象、页面对象和内容流写入。代码中的try-with-resources语法确保文档流被正确关闭避免内存和文件句柄泄漏。4.3 编写 PDF 文本抽取工具类在main代码目录下我们创建一个文本抽取工具类后续测试全部复用这个工具package com.example.pdf; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.text.PDFTextStripper; import java.io.IOException; import java.io.InputStream; /** * PDF 文本抽取工具。 */ public class PdfTextUtils { private PdfTextUtils() { } public static String extractText(InputStream inputStream) throws IOException { try (PDDocument document PDDocument.load(inputStream)) { PDFTextStripper stripper new PDFTextStripper(); return stripper.getText(document); } } public static int countPages(InputStream inputStream) throws IOException { try (PDDocument document PDDocument.load(inputStream)) { return document.getNumberOfPages(); } } }在 PDFBox 2.x 中PDDocument.load(InputStream)会读取整个 PDF 文件到内存中因此解析完以后必须关闭。PDFTextStripper是 PDFBox 提供的文本抽取器它会按页面顺序提取可见文本内容。4.4 编写 PDF 内容断言测试下面编写核心测试类注意命名必须符合 Maven Surefire 的默认规则package com.example.pdf; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.io.InputStream; import static org.junit.jupiter.api.Assertions.*; /** * PDF 内容抽取测试。 */ class PdfContentTest { private static InputStream pdfInputStream; BeforeAll static void setup() throws Exception { // 从 classpath 中加载测试资源避免文件路径在不同环境下不一致 pdfInputStream PdfContentTest.class.getResourceAsStream(/sample.pdf); assertNotNull(pdfInputStream, sample.pdf 未找到请确认文件位于 src/test/resources 目录); } Test DisplayName(PDF 应包含指定文本) void shouldContainExpectedText() throws Exception { String content PdfTextUtils.extractText(pdfInputStream); assertTrue(content.contains(Hello PDF Test), PDF 中未找到目标文本); } Test DisplayName(PDF 页数应为 1) void shouldHaveOnePage() throws Exception { int pages PdfTextUtils.countPages(pdfInputStream); assertEquals(1, pages, PDF 页数不符合预期); } Test DisplayName(PDF 不应包含非法敏感词) void shouldNotContainForbiddenText() throws Exception { String content PdfTextUtils.extractText(pdfInputStream); assertFalse(content.contains(Internal Error), PDF 中包含不应出现的文本); } }这里有一个值得注意的细节BeforeAll方法是静态的它在每个测试方法执行前只运行一次。我们把 PDF 输入流放在setup()中统一加载避免每个测试方法重复读取文件。但由于同一个InputStream在第一次解析后可能已经读完多个测试方法共用一个流时要么在每次抽取前重新打开要么在工具类内部自己加载流。上面这个例子如果直接跑第二个和第三个测试方法有可能读到空内容。更稳妥的做法是每个测试方法独立打开流Test DisplayName(PDF 应包含指定文本) void shouldContainExpectedText() throws Exception { try (InputStream is PdfContentTest.class.getResourceAsStream(/sample.pdf)) { String content PdfTextUtils.extractText(is); assertTrue(content.contains(Hello PDF Test), PDF 中未找到目标文本); } }关于这一点强烈建议理解“流是一次性资源”这个概念。很多 PDF 相关测试的“随机失败”最后都归结为流复用问题。4.5 生成测试 PDF 并运行首先用生成器创建测试文件。可以在main方法中直接调用也可以写一个独立的测试类来生成。为了不污染正式测试结果建议生成逻辑放在test代码目录并且只在需要的时候手动执行。package com.example.pdf; import org.junit.jupiter.api.Test; import java.nio.file.Paths; class PdfTestDataGeneratorTest { Test void generateSamplePdf() throws Exception { String outputPath Paths.get(src/test/resources/sample.pdf).toAbsolutePath().toString(); PdfTestDataGenerator.createSamplePdf(outputPath, Hello PDF Test); System.out.println(PDF 已生成 outputPath); } }执行该测试类后src/test/resources/sample.pdf会生成一个包含Hello PDF Test文本的单页 PDF。接下来在项目根目录运行mvn test预期输出中会看到类似下面的结果[INFO] Running com.example.pdf.PdfContentTest [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0如果你还能看到Tests run: 3说明测试被成功识别并执行了“no tests found for given includes” 的问题已经解决。4.6 扩展测试场景页数、元数据与图片PDF 测试不止文本断言。在实际项目中常常需要验证生成的报告是否包含正确的页数。PDF 的标题、作者等元数据是否正确。PDF 中是否包含指定图片。接口返回的 PDF 是否能被正常解析。我们可以继续扩展PdfTextUtils添加元数据读取方法public static String getDocumentTitle(InputStream inputStream) throws IOException { try (PDDocument document PDDocument.load(inputStream)) { return document.getDocumentInformation().getTitle(); } }对应测试Test DisplayName(PDF 标题应为空或指定值) void shouldHaveTitle() throws Exception { try (InputStream is PdfContentTest.class.getResourceAsStream(/sample.pdf)) { String title PdfTextUtils.getDocumentTitle(is); assertNull(title, 该测试 PDF 未设置标题应为 null); } }图片校验稍微复杂一些需要遍历PDResources中的 XObject。示例思路如下for (PDPage page : document.getPages()) { PDResources resources page.getResources(); for (COSName name : resources.getXObjectNames()) { PDXObject xObject resources.getXObject(name); if (xObject instanceof PDImageXObject) { // 说明页面中包含图片 } } }这段代码只是核心片段你需要放入对应的工具类中并引入org.apache.pdfbox.cos.COSName、org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject等类。5. 常见问题与排查思路下面是 PDF 测试过程中最常遇到的几个问题建议收藏备用。问题现象常见原因解决思路no tests found for given includes测试类命名不符合 Surefire 默认规则或 JUnit 依赖缺失检查类名是否以Test结尾检查junit-jupiter-engine依赖升级 Surefire 到 2.22.0测试类能编译但测试数为 0PDF 文件放到了测试源码目录IDE 索引异常将 PDF 移到src/test/resources清理 IDE 缓存后重新构建从 classpath 加载 PDF 返回 null文件不在 resources 目录或目录被排除检查文件位置确认target/test-classes下是否生成了 PDF 副本解析 PDF 时提示IOException: Error loading PDFPDF 文件损坏或不是有效 PDF用 PDF 阅读器打开确认文件可用检查文件扩展名是否被伪装抽取中文文本乱码PDF 使用了非标准编码或字体映射缺失确认 PDF 是否包含 Unicode 文本层扫描版 PDF 需要 OCR 支持多个测试方法共用 InputStream 导致数据返回空流是一次性资源读取后指针已到末尾每个测试方法独立打开流或使用TempDir复制临时文件PDFBox 3.x 中PDDocument.load(File)报错3.x 的 API 改为Loader.loadPDF根据版本调整加载方式升级项目时检索全部PDDocument.load调用其中TempDir是 JUnit 5 提供的临时目录注解特别适合需要临时生成 PDF、然后断言解析结果的场景。例如Test void generateAndParseTemporaryPdf(TempDir Path tempDir) throws Exception { Path pdfFile tempDir.resolve(temp.pdf); PdfTestDataGenerator.createSamplePdf(pdfFile.toString(), Temporary Content); try (InputStream is Files.newInputStream(pdfFile)) { String content PdfTextUtils.extractText(is); assertTrue(content.contains(Temporary Content)); } }使用临时目录可以避免测试用例之间相互污染也无需手动清理文件强烈推荐在 PDF 测试中使用。6. 最佳实践与工程建议6.1 测试资源统一管理PDF 测试样本应当放在src/test/resources下而不是散落在系统任意路径。如果你的测试需要多份 PDF建议按照业务类型划分子目录src/test/resources/pdf/ ├── 单页文本.pdf ├── 多页合同.pdf ├── 含图片报告.pdf └── 空文件.pdf加载方式优先使用 ClassLoader 的getResource(/pdf/xxx.pdf)避免使用绝对路径。这样项目换机器、换 CI 环境时测试不会因为路径问题失败。6.2 测试数据要小而可控PDF 文件如果太大会明显拖慢测试速度。通常建议单份测试 PDF 控制在几 KB 到几百 KB。只在必要的场景使用真实业务 PDF其余用代码临时生成。对 PDF 解析性能有要求的场景不要把大文件解析放在单元测试中应该使用独立的性能测试工程。6.3 不要直接修改原始 PDF 样本测试中如果需要对 PDF 增加水印、拆分页面或转换格式优先复制到临时目录再操作避免改变测试资源的原始状态。否则多次运行测试后样本被“污染”后续断言全部失败。这一点在测试 PDF 转换功能时尤其重要。例如验证“PDF 转 Word”功能若把输出文件直接覆盖到测试资源路径第二次运行就会读取到已经转换过的文件。6.4 关注安全与合规在自动化测试处理 PDF 时需要注意权限与合规问题只解析、生成、修改你有合法权利的 PDF 文档。涉及 PDF 去水印、解密等操作时必须确保自己拥有文件的使用权限或版权授权。不要在生产环境运行解析不可信来源的恶意 PDFPDFBox 在解析恶意构造的文件时也可能存在安全风险建议隔离运行或限制来源。6.5 测试命名与运行策略测试类命名是解决 “No tests found” 问题的最有效手段。建议团队统一规范单元测试统一使用XxxTest。集成测试统一使用XxxIT并配合 Failsafe 插件在独立阶段执行。临时生成数据的测试类建议加DataGenerator后缀避免被误认为业务测试。如果需要跳过某些 PDF 测试可以使用 JUnit 5 的Disabled注解并写明原因Test Disabled(该测试依赖 OCR 环境默认不执行) void ocrPdfTest() { // ... }6.6 日志与断言节奏PDF 解析结果往往不稳定尤其是碰到带大量图片的扫描版 PDF。建议断言时不要直接把完整文本打印到控制台避免日志瞬间刷屏。可以先打印关键片段再断言关键内容。其实通过断言失败信息查看输出片段也是足够的。7. 总结从一次no tests found for given includes的排查开始我重新梳理了 PDF 测试资源在 Maven 工程中的摆放规范以及 PDFBox 在自动化测试中的常用姿势。需要牢记的还是那几个点测试类命名要符合 Surefire 默认规则、PDF 文件不能混入测试源码目录、InputStream 不能跨测试方法复用。掌握这些之后再用 PDFBox 做文本抽取、页数校验、元数据校验就会顺手很多。如果你正在做文档解析类系统或者需要在 CI 里自动校验导出的 PDF 报告本文的项目结构和测试示例可以直接复用。下一步可以根据你的业务需求继续扩展多页 PDF 校验、扫描版 PDF 的 OCR 识别校验以及 PDF 转 Word、PDF 转 Excel 等转换链路的自动化断言。不要心急把基础样本和断言工具搭好后面扩展会非常快。
返回列表