单元测试 vs 手工测试:以水印功能为例

一、一句话区分

单元测试 手工测试
谁来跑 机器(JUnit/Mockito)
测什么 代码逻辑对不对 用户体验对不对
多快 秒级 分钟~小时级
能否进 CI ✅ 每次提交自动跑 ❌ 需要人手操作

二、水印功能的测试全景

我们的水印模块涉及三个层面:路径解析 → 图片绘制 → 权限分流。项目里为每个层面写了对应的单元测试,共 3 个测试类、12 个用例,全部是纯内存测试,不连 MySQL、Redis、S3。

复制代码
项目结构
src/test/java/.../upload/watermark/
├── S3KeyUtilsTest.java                  # S3 路径解析(6 个用例)
├── ImageWatermarkServiceTest.java       # 水印绘制(2 个用例)
└── ListingPhotoAccessServiceImplTest.java # 权限分流(4 个用例)

三、单元测试长什么样

3.1 S3KeyUtilsTest --- 路径映射

测什么: URL ↔ S3 key 之间的转换规则,纯工具类,无任何 Mock。

java 复制代码
// 示例:验证新上传路径的水印 key 映射
@Test
void resolveWatermarkedKey_forNewUploadLayout() {
    // listings/original/abc.jpg → listings/watermarked/abc.jpg
    String result = S3KeyUtils.resolveWatermarkedKey("listings/original/abc.jpg");
    assertEquals("listings/watermarked/abc.jpg", result);
}

特点:

  • 输入明确,输出可预测
  • 不依赖外部系统
  • 跑完 6 个用例不到 1 秒

3.2 ImageWatermarkServiceTest --- 水印绘制

测什么: Java2D 在内存中画水印,不访问 S3、不访问网络。

java 复制代码
@Test
void applyWatermark_keepsImageDimensions() {
    BufferedImage source = new BufferedImage(800, 600, TYPE_INT_RGB);
    BufferedImage result = service.applyWatermark(source, context);

    assertEquals(800, result.getWidth());   // 尺寸不变
    assertEquals(600, result.getHeight());
}

@Test
void applyWatermark_outputsJpegBytes() {
    byte[] pngBytes = encodePng(source);
    byte[] watermarked = service.applyWatermark(pngBytes, context);

    assertNotNull(watermarked);
    assertTrue(watermarked.length > 0);
    // 验证输出是合法 JPEG
    BufferedImage decoded = ImageIO.read(new ByteArrayInputStream(watermarked));
    assertNotNull(decoded);
}

特点:

  • 手动 new ImageWatermarkService(...),不走 Spring 容器
  • 用空白 BufferedImage 当输入,classpath 下的 PNG 当 Logo
  • 只验证"能生成合法 JPEG",不验证 Logo 位置、文字内容------那些需要人眼确认

3.3 ListingPhotoAccessServiceImplTest --- 权限分流

测什么: 根据用户身份决定返回原图还是水印 URL。

java 复制代码
@Test
void resolvePhotoUrls_ownerGetsOriginal() {
    // 用户 100 看自己的房源 → 返回原图 URL
    List<String> urls = service.resolvePhotoUrls(listingPhotos, 100L);
    assertTrue(urls.get(0).contains("original"));
}

@Test
void resolvePhotoUrls_otherUserGetsWatermarked() {
    // 用户 200 看别人的房源 → 返回水印 URL
    List<String> urls = service.resolvePhotoUrls(listingPhotos, 200L);
    verify(watermarkService).ensureWatermarked(any());
}

特点:

  • 用 Mockito @Mock 模拟依赖,不启动 Spring Boot
  • 覆盖四种场景:业主、他人、助手、分享链接
  • 验证的是业务规则,不关心水印本身长什么样

四、手工测试测什么

单元测试覆盖不到的东西,就需要人来做:

测试项 为什么单元测试做不了
S3 真实上传/下载 依赖 AWS 网络,Mock 无法替代真实网络延迟和权限
Controller HTTP 接口 需要完整请求链路:Filter → Interceptor → Controller → Service → S3
水印视觉效果 Logo 位置对不对?平铺文字是否清晰?"Listed by" 条颜色对不对?这些只能靠人眼

手工测试典型流程:

  1. 启动应用,打开 Swagger UI 或 Postman
  2. 调上传接口传一张真实房源图片
  3. 调下载接口,分别用业主身份和访客身份
  4. 打开下载的 jpg,肉眼确认:
    • ✅ Logo 在右下角
    • ✅ 半透明平铺文字覆盖全图
    • ✅ "Listed by xxx" 底部条清晰可读
    • ✅ 水印图尺寸和原图一致

五、核心区别对照表

维度 单元测试 手工测试
执行速度 秒级(12 个用例 < 3s) 分钟~小时级
可重复性 完全相同输入 → 完全相同结果 人可能漏看、误判
依赖 不连 MySQL/Redis/S3 需要完整运行环境
CI 集成 ✅ 每次 push 自动跑 ❌ 无法自动化
反馈时机 写代码时立刻知道对不对 部署后才能验证
测什么 逻辑正确性 视觉体验、真实链路
维护成本 代码即文档,改逻辑时同步改 需要写测试用例文档
发现问题类型 边界条件、回归 bug 视觉瑕疵、交互问题

六、一个形象的类比

复制代码
单元测试 = 工厂质检机器
手工测试 = 质检员肉眼验收

机器能测:
  ✅ 螺丝扭矩是否达标
  ✅ 零件尺寸是否在公差范围内
  ✅ 电路是否导通

质检员看:
  👀 漆面有没有划痕
  👀 屏幕颜色是否偏色
  👀 整体手感是否舒适

两种方式互补,缺一不可。

七、项目当前测试分层总结

复制代码
┌─────────────────────────────────────────┐
│           手工测试 / 视觉验收             │  ← 人眼:Logo位置、文字、S3真实链路
├─────────────────────────────────────────┤
│        集成测试(未实现)                  │  ← @SpringBootTest + 真实 S3/DB
├─────────────────────────────────────────┤
│   Controller 层测试(未实现)             │  ← @WebMvcTest + MockMvc
├─────────────────────────────────────────┤
│   Service 层单元测试 ✅ 已实现(3类12例)  │  ← Mockito,纯内存
├─────────────────────────────────────────┤
│   Util 工具类测试 ✅ 已实现               │  ← 纯 JUnit,无依赖
└─────────────────────────────────────────┘

八、总结

  1. 单元测试验证代码逻辑------快、可重复、能进 CI,适合覆盖边界条件和回归场景。
  2. 手工测试验证用户体验------能看到真实效果,发现代码无法自证的视觉问题。
  3. 二者不是替代关系,而是互补关系。 单元测试保证"没写错",手工测试保证"没做错"。
  4. 当前项目的水印单元测试覆盖了 路径解析、绘制基础、权限分流 三个核心逻辑层。S3 读写、HTTP 接口、视觉效果仍需手工或后续集成测试覆盖。
相关推荐
2601_9622998810 小时前
python脚本如何单元测试
python·单元测试·pytest·unittest·mock对象
牛油果子哥q1 天前
C++大型项目工程精讲:CMake完整实战、静态库&动态库、模块化拆分、单元测试、gdb调试、性能工具、工程踩坑全解
开发语言·c++·单元测试
leoZ2312 天前
AI+前端提效-09 AI赋能前端测试:单元测试、E2E测试自动生成,提升覆盖率
前端·人工智能·opencv·目标检测·数据挖掘·单元测试·语音识别
边吃番茄边敲代码6 天前
从本地工具到 MCP:给 Agent 接入独立工具服务,并补齐自动化测试
人工智能·python·功能测试·ai·单元测试·pytest
qq_452396236 天前
第十三篇:《测试策略:单元测试、组件测试、E2E 测试的落地实践》
单元测试
ly76897 天前
前端测试完整指南:从单元测试、组件测试到端到端测试与 CI/CD
前端·ci/cd·单元测试
咖啡星人k8 天前
2026 AI 自动化测试:让 AI 帮你写用例、跑测试、修 Bug(MonkeyCode 云端实战)
人工智能·功能测试·单元测试·测试用例·bug·集成测试
花椒技术8 天前
不是跑通一次 Demo:花椒 QA 怎样把真机 UI 自动化做成回归流程
ios·单元测试·测试
边吃番茄边敲代码8 天前
企业智能助手Agent 安全测试,包含Prompt Injection 和越权工具调用
人工智能·python·功能测试·ai·单元测试·prompt·模块测试