单元测试实践从小白到高手|实战技巧与避坑指南

单元测试实践从小白到高手|实战技巧与避坑指南 一

文章目录CloseOpen

从小白到高手的实战技巧

刚接触单元测试时,你可能觉得“不就是调个JUnit跑方法吗?”但真正能落地的测试,得像搭积木一样——既稳当又灵活。我带新人时,都会让他们先搞懂三个核心步骤,亲测按这个路径走,团队新人最快2周就能写出“抗造”的测试用例。

第一步:测试用例设计,别只测“正常情况”

你是不是也只爱测“输入正确参数,返回正确结果”这种“阳光普照”的场景?我之前带团队做代码review,发现80%的新人测试用例都这个毛病。结果上线后,用户输个负数、传个空字符串,系统直接崩了——这就是没做好“边界值测试”和“等价类划分”。

教你个简单方法:拿到一个方法,先画个“输入框”,把参数可能的取值范围拆成三类:正常值(大部分情况)、边界值(比如int的最大值、数组的第一个/最后一个元素)、异常值(null、空集合、不合法格式)。举个例子,测试一个“计算订单金额”的方法(入参是商品列表和优惠券),我会让团队这么设计用例:

  • 正常值:3件商品+满100减20优惠券(覆盖大部分用户场景)
  • 边界值:1件商品(最小数量)、10件商品(系统限制的最大数量)、优惠券刚好抵扣完金额(比如订单100元用100元券)
  • 异常值:空商品列表(用户没选商品就提交)、过期优惠券、优惠券金额大于订单金额
  • 这么设计下来,测试用例覆盖的“真实场景”能提升60%以上。之前我们团队用这个方法后,边界值引发的线上bug直接降了一半。

    第二步:Mock框架,让测试“不依赖别人”

    你写测试时是不是总被“外部依赖”卡住?比如测试一个调用数据库的方法,得先搭个测试库;测试调用第三方接口,还得等对方服务开测试环境?我之前带项目时,就因为测试依赖外部服务,每次跑测试都跟“开盲盒”似的——今天能跑明天不能跑。后来全团队改用Mock框架,测试效率直接翻了倍。

    现在主流的Mock框架就俩:Mockito(Java)和Spock(Groovy/Java)。给你做个对比,方便你选:

    框架 优势 劣势 适合场景
    Mockito Java原生、学习成本低、文档丰富 复杂逻辑测试代码较长 Java项目、简单依赖模拟
    Spock 语法简洁、支持数据驱动测试、内置断言 需要学Groovy基础 复杂业务逻辑、多场景测试

    小技巧

    :用Mock时别“过度模拟”。我见过有人把Service层的依赖全Mock了,结果测试跑通了,实际调用时因为参数校验没测到照样出问题。记住:只Mock“外部不可控依赖”(数据库、第三方接口),自己写的工具类、DTO转换逻辑,尽量别Mock,直接测真实代码。 第三步:跟CI/CD集成,让测试“自动干活”

    你是不是写完测试就扔那儿了?等上线前才发现“上周改的代码把测试全搞挂了”?我之前带团队时,就因为测试没集成到流程里,导致一个小改动引发测试用例批量失败,排查了一整天。后来我们把单元测试加到Jenkins流水线里,每次提交代码自动跑测试,谁改挂了测试谁负责fix,效率一下子提上来了。

    具体怎么做?很简单:在项目的pom.xml(Maven)或build.gradle(Gradle)里配置测试命令,然后在CI工具(Jenkins/GitHub Actions)里设置“提交代码后自动执行测试”。这里有个小细节:别等所有测试跑完再反馈,用“快速失败”模式——第一个测试失败就终止,节省时间。我团队用这个方法后,平均每次提交的测试反馈时间从5分钟降到了1分半。

    高手都绕开的3个坑,你别踩

    学会了技巧,还得知道哪些“坑”不能踩。我带过50+开发者做单元测试,发现大家最爱掉这三个坑里,今天我把解决方案直接给你。

    坑1:盲目追求“覆盖率100%”

    你是不是觉得“覆盖率越高,代码越安全”?之前有个新人跟我说“我把覆盖率干到100%了,领导肯定表扬我”,结果代码上线后还是出了bug。后来一看他的测试:全是“为了覆盖而覆盖”,比如getter/setter都写了测试,核心业务逻辑却只测了表面。

    Martin Fowler在博客里说过:“测试覆盖率是个有用的指标,但不是目标”(原文链接:https://martinfowler.com/bliki/TestCoverage.htmlnofollow)。真正有用的是“有价值的覆盖率”——核心业务逻辑(比如订单计算、权限校验)覆盖率要尽量高( 90%+),而工具类、简单DTO的getter/setter,没必要硬凑覆盖率。我一般让团队这么做:用JaCoCo生成覆盖率报告,重点看“分支覆盖率”(每个if/else是否都测到了),而不只是“行覆盖率”。

    坑2:测试依赖“外部环境”

    你写的测试是不是必须连数据库、开Redis才能跑?我之前接手一个项目,跑单元测试得先启动3个服务,配5个环境变量,简直是“测试地狱”。后来重构时,我们把所有外部依赖都用Mock替换,现在随便一台电脑clone代码就能跑测试,新人上手速度快了一倍。

    解决办法很简单:用内存数据库(H2)替代真实数据库,用WireMock模拟第三方接口,再配合Mock框架隔离依赖。比如测试一个查询数据库的方法,用H2内存库预先插入测试数据,测试完自动清空,既快又安全。

    坑3:异步代码测试“测不准”

    你写异步测试(比如多线程、定时任务)时,是不是总遇到“测试时而通过时而失败”?我之前测一个异步发送消息的方法,用Thread.sleep(1000)等结果,结果偶尔网络慢一点就超时失败。后来学了“异步测试专用工具”,问题一下解决了。

    给你两个方案:

  • 用CountDownLatch:在测试代码里加个计数器,异步任务执行完调用countDown(),测试主线程调用await()等待,避免“猜睡眠时长”。
  • 用专用框架:JUnit 5支持@TestFactory写动态测试,配合CompletableFuture的thenRun()方法,能优雅地测异步逻辑。我团队用这个方法后,异步测试的成功率从70%提到了100%。
  • 最后说句掏心窝的话:单元测试不是“额外工作”,而是帮你少加班的“保险”。我带的团队里,坚持写好单元测试的开发者,平均每周排查bug的时间比别人少5小时。你按今天说的方法试试,先从核心业务逻辑开始写测试,两周后回来告诉我,你的代码质量有没有提升?


    你是不是也遇到过这种情况?写了个异步方法,测试的时候明明逻辑对,就是偶尔失败,一看日志,测试跑完了异步任务还没执行完——这就是线程不同步的锅。之前我带团队测一个“异步发送短信”的接口,一开始用Thread.sleep(1000)等结果,结果服务器负载高的时候,1秒根本不够,测试就失败;负载低的时候又白白等1秒,特别浪费时间。后来改用CountDownLatch,这问题一下就解决了。具体怎么做呢?很简单,你先初始化一个CountDownLatch(1),在异步任务的回调里调用countDown(),然后测试主线程调用await()等着——这样任务没执行完,测试就会一直等,执行完了立刻继续,既不会超时也不会白等。我们团队用这个方法后,异步测试的成功率从70%直接提到了100%,再也不用天天重启测试了。

    如果觉得CountDownLatch还是有点麻烦,你可以试试更优雅的工具。比如用JUnit 5的@TestFactory配合CompletableFuture,写异步测试就像写同步代码一样顺。举个例子,你要测一个返回CompletableFuture的异步方法,直接在测试里调用thenRun(),把验证逻辑放进去,JUnit会自动等异步结果出来再判断。要是你用的是响应式框架(比如Spring WebFlux),那Reactor Test的StepVerifier简直是神器,它能帮你一步步验证异步流的每一步结果,连“先返回部分数据,再返回完整数据”这种复杂场景都能测。我之前测一个响应式接口,用StepVerifier写测试,不仅代码简洁,还能直观看到数据流的每一步,比自己写线程控制舒服多了。总之记住,异步测试别硬扛,用好工具能省不少事。


    单元测试覆盖率是不是越高越好?

    不是。覆盖率是衡量测试完整性的指标,但不是目标。核心业务逻辑(如订单计算、权限校验) 覆盖率90%+,需重点关注分支覆盖(每个if/else是否都测试);而简单的getter/setter、工具类等非核心代码,不必强求100%覆盖率。Martin Fowler曾指出,过度追求覆盖率可能导致“为覆盖而覆盖”,反而忽视测试质量。

    Mock框架该选Mockito还是Spock?

    根据项目场景选择:Mockito是Java原生框架,学习成本低,适合简单依赖模拟(如数据库、第三方接口),文档丰富,新手友好;Spock基于Groovy,语法简洁,支持数据驱动测试和内置断言,适合复杂业务逻辑或多场景测试,但需要掌握基础Groovy语法。中小项目推荐先用Mockito上手,复杂场景可尝试Spock。

    异步代码测试总失败,有什么解决办法?

    可通过两种方式解决:一是用CountDownLatch控制线程等待,在异步任务执行完毕后调用countDown(),测试主线程调用await()等待结果,避免依赖Thread.sleep()猜时长;二是使用JUnit 5的@TestFactory配合CompletableFuture的thenRun()方法,或专用异步测试框架(如Reactor Test),实现优雅的异步逻辑验证。

    单元测试应该在写业务代码前还是后?

    推荐“测试先行”(TDD)或“边写边测”。TDD模式(先写测试用例,再写业务代码)能提前明确接口设计和边界条件,适合复杂逻辑;若不习惯TDD,至少在写完核心业务逻辑后立即写测试,避免代码迭代后遗忘逻辑细节。实践中,“边写业务代码边补测试”比“全部写完再补”效率更高,测试质量也更有保障。

    测试用例依赖外部环境(如数据库),怎么处理?

    需隔离外部依赖:用内存数据库(如H2)替代真实数据库,测试前插入临时数据,测试后自动清空;用WireMock模拟第三方接口,预设返回结果;对Redis等中间件,可使用TestContainers启动轻量级容器,或直接用Mock框架模拟客户端(如MockJedis)。核心原则是:只测试当前代码逻辑,不依赖外部环境的稳定性。

    0
    显示验证码
    没有账号?注册  忘记密码?