主题
测试体系
仓库有 27 个
src/test测试类和 1 个独立的破解回归工程,但没有testng.xml、没有 surefire 定制、CI 也不跑测试——测试能力存在,测试流程不存在。
1. 测试资产全景
| 位置 | 类型 | 数量 | 是否随 mvn test 执行 |
|---|---|---|---|
captcha-test/ | 独立 Selenium 破解回归工程(源码在 src/main) | 8 个类 | 不执行,需 java -jar 指定参数 |
captcha-base/src/test | JUnit 单测 + 手工实验类 | 8 个 | 会尝试执行 |
captcha-server/src/test | Spring MockMvc 接口测试 + 本地环境测试 | 16 个 | 会尝试执行(依赖真实中间件) |
captcha-admin/src/test | 工具类单测 + 一次性生成器 | 2 个 | 会尝试执行 |
captcha-image/src/test | 上传控制器测试 | 1 个 | 会尝试执行 |
captcha-demo/src/main/webapp/v2.x/ | 联调页面(非测试代码) | 30+ HTML/JS | 不适用 |
全仓 没有 testng.xml、没有 JUnit Platform、没有 surefire/failsafe 插件配置(pom.xml 里只在 dependencyManagement 声明了 testng.version=7.5.1,:84、:868-870)。这就是所有构建命令都带 -DskipTests 的根本原因。
2. captcha-test:Selenium 破解回归
2.1 工程定位
它不是“验证正确性”的测试,而是反向压力测试:用自动化浏览器模拟攻击者解题,验证验证码的防破解能力与风控拦截是否生效。产物是可执行的 shaded jar(captcha-test/pom.xml:214-251,mainClass=auto.Main)。
2.2 代码组织
| 包 | 类 | 职责 |
|---|---|---|
auto | Main | 唯一入口,按 args[0] 分派 drag / smart(Main.java:22-31) |
auto.base | BaseDriverWrapper | 驱动初始化:按 OS 设 webdriver.chrome.driver(硬编码 C:\webdriver/,BaseDriverWrapper.java:33-38),读 config.properties(:42),可选起 BrowserMob Proxy 抓包 |
auto.base | ActionBot / ActionWebBot | 页面操作封装(等待、点击、拖拽、截图、Alert),近似 Page Object 的底层能力层 |
auto.cases | BaseYidunCrack | 图像算法基类:下载背景图/缺口图到 c://webdriver/bg.jpg、fg.png(BaseYidunCrack.java:44-48),OpenCV 灰度化 + 模板匹配求偏移(solve,:53-60) |
auto.cases | YidunCrack | 通用流程:prepare() 打开页面 → extendPrepare() 子类扩展 → 循环 autoDrag(count) 累计成功次数(YidunCrack.java:31-50) |
auto.cases | YidunDragCrack | 滑块场景:操作演示页控制面板,勾 jigsaw/embed 并确认(YidunDragCrack.java:26-50) |
auto.cases | YidunSmartCrack | 智能无感知场景:失败后弹窗降级解题 |
设计上是三层:驱动层(BaseDriverWrapper)→ 操作层(ActionBot/ActionWebBot)→ 用例层(YidunCrack 及子类)。没有用 Selenium 官方 PageFactory,也没有 .page 类,所谓“Page Object”只是 CSS 选择器内联在用例里(如 .control-panel__header、.icon-angle--top),页面改版就得改代码。
2.3 运行方式
bash
# 1) 环境:C 盘根目录建 webdriver 文件夹,放入 chromedriver;安装同版本 Chrome
# 2) 打包(shade 插件已配好 mainClass)
mvn clean package -pl captcha-test -am -DskipTests
# 3) 运行:第一个参数 drag|smart,第二个参数是 js demo 页地址,第三个是重复次数
java -jar captcha-test/target/captcha-custom-test-1.0-SNAPSHOT-shaded.jar \
smart http://10.130.64.231:8653/v2.x/test/ 10参数与注意事项来自 captcha-test/doc/usage.txt(全 8 行):smart/drag 二选一、第二个参数必须是可访问的 demo 页、第三个是“成功次数”而非“尝试次数”(YidunCrack.java:47 的循环条件是 successCount < count,失败会无限重试——没有最大重试上限,这是已知缺陷)。
captcha-test/src/main/resources/config.properties 控制驱动行为:
| key | 默认 | 说明 |
|---|---|---|
type | 3 | 1 android / 2 ios / 3 web,当前固定 web |
hubUrl | http://localhost:4444/wd/hub | 远程 Grid 地址,本地驱动时不用 |
browserName | chrome | 仅 chrome 路径被验证过 |
waitTime / retrycount | 10 / 1 | 显式等待秒数、重试 |
appPath/platformVersion/platformName/deviceName/appPackage/appActivity | Android 默认值 | 移动端预留,未实际使用 |
captcha-test/doc/ 里同时提供 chromedriver(Linux)、chromedriver.exe(Win)、chromedriver_mac 三个二进制,但代码只从 C:\webdriver\ 取,Linux/mac 上跑必须改 BaseDriverWrapper.java:34-38 的硬编码路径。
3. captcha-demo:联调与人工回归
captcha-demo 既是演示站点也是事实上的人工回归环境,它的页面就是 captcha-test 的靶场:
| 路径 | 内容 |
|---|---|
webapp/v2.x/test/index.html | 主要联调页,含控制面板(jigsaw/embed 开关、多版本 SDK 切换) |
webapp/v2.x/test/simple-demo.html | 最小接入示例 |
webapp/v2.x/test/test-captcha.v2.*.js | 多版本 SDK(2.11.1 至 2.30.0),用于版本兼容回归 |
webapp/v2.x/test/mobile.v2.*.html | 移动端/小程序形态页面 |
webapp/v2.x/online/ | 生产同源版本页面对照 |
webapp/LoginServlet*.java、SendSmsServlet.java | 服务端二次校验示例,演示 NECaptchaVerifier 用法(captcha-demo/src/main/java/.../sdk/java/NECaptchaVerifier.java) |
webapp/api/v2/mobile.html | 移动端桥接页 |
部署路径不是 ROOT,而是固定 /mgt/decision-captcha/v2/demo(captcha-demo/Dockerfile:31-33 写 Tomcat context 描述符)。联调时 URL 形如 http://<demo-host>/mgt/decision-captcha/v2/demo/v2.x/test/。
服务地址通过环境变量注入:CAPTCHA_STATIC_SERVER、CAPTCHA_API_SERVER(captcha-demo/Dockerfile:19-20),页面里的 _config 从这里取值——demo 页能打开但题面加载不出来,90% 是这两个变量为空。
4. 各模块单测真实现状
4.1 captcha-base(8 个,有真实价值)
| 测试类 | 类型 | 说明 |
|---|---|---|
frequency/assembler/StrategyDetailAssemblerTest | JUnit 4,纯逻辑 | 策略执行详情组装,覆盖“命中/未命中/去重计数未命中”等分支(:219 起) |
imagemeta/click/create/ClickCaptchaCreateV2Test | JUnit 4 | 点选题面生成 |
imagemeta/click/create/ValidateToTokenUtilsTest | JUnit 4 | validate→token 转换 |
filter/PropertiesEncryptUtilTest | JUnit 4 | Enc(...) SM2 加解密往返 |
ElasticsearchConfigTest | 集成 | 需真实 ES |
FontTest / FontDetecter / OjdbcTest | 手工实验 | main 式脚手架,不是断言测试 |
4.2 captcha-server(16 个,MockMvc 为主)
MockSetupConf 是公共基类,MockApiV2GetControllerTest、MockApiV2CheckControllerTest、MockApiV2CollectControllerTest、MockApiGetConfControllerTest、MockUplinkSMSControllerTest、VerifyControllerV2Test 继承它跑 mockMvc.perform(...)(MockApiV2GetControllerTest.java:25-28),并直接断言响应里的 token、type(:55-59)。
问题在于:这些测试依赖真实 Redis、MySQL、ES 与已导入的 captchaId(用例里写死了 83aeb187a3f3464d8928e91e6f205674 之类的 id),在干净环境里必然失败。另外 local/AbstractUnitTest、LocalCaptchaTest、LicenceHelperTest、mapper/MapperTest 都要求本地起全套中间件;DragCaptchaTest/DragCaptchaFallbackTest、ZhuoWangIp*Test 分别验证滑块降级与卓望 IP 字段绑定。
4.3 captcha-admin(2 个)/ captcha-image(1 个)
PasswordUtilsTest:JUnit 3 风格(extends TestCase+junit.framework,PasswordUtilsTest.java:3,11),只System.out.println不打断言——是示例,不是测试。DragCaptchaGenerator:一次性素材生成器,放在src/test借 classpath。UploadControllerTest:图片上传接口,需真实存储。
4.4 结论
| 判定 | 说明 |
|---|---|
| 有单测的模块 | base / server / admin / image 四个 |
| 无任何测试的模块 | captcha-common(常量与枚举,风险低)、captcha-schedule(定时任务零测试,风险最高) |
| 可在无中间件环境跑通的 | 仅 StrategyDetailAssemblerTest、PropertiesEncryptUtilTest、ClickCaptchaCreateV2Test、ValidateToTokenUtilsTest、FontTest 少数几个 |
| 是否进 CI | 否。仓库无 CI 配置文件,构建脚本一律 -DskipTests |
5. 测试环境依赖与数据准备
| 依赖 | 要求 | 准备方式 |
|---|---|---|
| MySQL | 45 张表 + 初始数据 | build/db_init/mysql/mysql.sql + init-data.sql |
| Redis | 两套(业务 + 限频) | base.properties:2-9、frequency.properties:3-13 |
| Elasticsearch | 7.17,captcha_info_* 模板 | build/es/elasticsearch/captcha_info_template + build/es/docker-entrypoint.sh |
| S3 / 对象存储 | bucket 可写 | 或改用 captcha-image 本地目录 /java-web-dir/custom-image/ |
| License | nc_licence + nc_license 各一行 | init-data.sql:64-69 自带示例行(有效期至 2027 年附近,测试够用) |
| Chrome + chromedriver | 版本必须匹配 | 放 C:\webdriver\,或改 BaseDriverWrapper 路径 |
| 预生成数据 | nc_image_output 有 status=1 行 | 启动 captcha-schedule 跑一轮(fixedDelay=60000),或手工插行 |
最小可跑通“取图-校验-二次校验”的数据准备顺序:建库 → 导 init-data.sql → 启 schedule 预热 → 记下 nc_app 中某个 captcha_id(如 ef0d0f556d384d9185a74cd80b34b45f 点选)→ 用它调 /api/v3/get。
6. 覆盖缺口与建议补充清单
| 优先级 | 缺口 | 建议 |
|---|---|---|
| P0 | captcha-schedule 零测试,而它承担预生成、清理、统计、子版本切换全部不可逆操作 | 对 ImageCleanUpWorker、SubVersionSwitchJob 做纯逻辑单测(注入 mock Mapper),覆盖“空集合/超期/状态异常”边界 |
| P0 | 无 CI 流水线,测试从不执行 | 加 GitHub/GitLab CI:mvn -pl captcha-base test 先跑可脱库用例,其余标 @Ignore |
| P1 | MockMvc 用例硬编码 captchaId,无法在新环境复现 | 改为 @Before 从库里按 product_id 查一条 online_status=1 的 app,或用 Testcontainers 造数 |
| P1 | 无接口契约测试(get/check/verify 响应结构) | 用 JSON Schema 断言响应骨架,防止平行类 *ControllerV2 演进时字段漂移 |
| P1 | YidunCrack 失败无限重试(YidunCrack.java:47) | 加最大尝试次数与超时,避免 CI 挂死 |
| P2 | PasswordUtilsTest 无断言 | 补 assertEquals(PasswordUtils.encode("admin"), 期望值) |
| P2 | 无并发/限流测试 | 对 /api/v3/check 做 N 并发压测,验证 RedisRateLimiter 阈值 |
| P2 | 无 License 过期/类型不符的负向用例 | LicenceCheckInterceptor 与 LicenseService 各补 3 条负向用例 |
| P3 | 前端多版本 SDK 兼容靠人工点 v2.x/test 各页面 | 用 Playwright 参数化跑 2.11.1→2.30.0 全部版本页 |
7. 一句话总结
本项目测试重心在逆向对抗(captcha-test 破解工具 + captcha-demo 靶场)而非正向断言;src/test 里的 27 个类绝大多数是开发期脚手架或依赖真实中间件的集成脚本,加上构建全程 -DskipTests,实际回归手段仍是人工点 demo 页 + 线上灰度观察。补齐 captcha-schedule 单测并接入 CI 是收益最高的一步。