什么是Mock?怎么做单测的Mock?

典型回答 Mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。 不仅仅是软件开发领域的单元测试,其实在很多工业测试场景中,也会使用一些Mock技术,比如常见的汽车碰撞测试。 汽车在上市之前都需要经过碰撞测试,并且公布测试结果。碰撞测试过程中需要通过真实撞击来评定汽车的耐撞能力以及对内部驾驶人员的保护能力。 但是,为了保证验证的准确性,有为了保证测试人员的安全,一般会采用假人来进行测试。这些假人都是经过特制的,他们的生物学性能一般和真人是一样的,比如体重、关节能力、组织强度等。 而且有时候,为了保证测试的全面性,还会采用各种各样的假人,如成年人、老人、小孩子、男性、女性等都需要充分测试到。 所以,这个假人其实就是一个Mock对象。在软件单元测试中,我们也需要用到这些测试对象。目的也类似,就是为了保证测试的全面性及准确性。 之所以要在测试中使用Mock对象,其实有很多原因,其中最重要的原因就是真实对象的构造成本太高。这时候一般就会采用mock对象。 而市面上也有很多工具可以方便的帮助我们进行单元测试的mock,如Easymock、jMock、Mockito、Unitils Mock、PowerMock、JMockit等。 其中比较常用的就是Easymock(https://easymock.org/ )、JMockit(https://jmockit.github.io/index.html )和Mockito(https://site.mockito.org/ )这三种,用起来都比较简单。 关于这些工具的对比,在JMockit官网中有一张图还是挺明显的,如下: 扩展知识 接口mock 如我们的要测试的一个方法,其中依赖了一个RPC远程服务,因为远程服务的返回值可能是各种各样的,我们为了测试我们的接口的鲁棒性,就会针对各种边界情况进行充分测试。 如果把外部接口mock掉,也就是把外部接口的返回值当做一个mock对象,那么我们就可以很方便的模拟各种情况。如外部接口正常返回、异常返回、请求超时等等,都可以很方便的被测试。 其实,随着软件开发这么多年的发展,mock技术已经不仅仅局限于单元测试阶段了,尤其是随着微服务的兴起,应用拆分的越来越细,应用见依赖也越来越多。 这时候Mock技术就显得尤为重要了。 很多时候,一个项目中,大家可能是同时开发的,而我们的很多下游依赖可能还没开完完,或者有些特殊case没办法构造,那这时候就可以利用mock技术来mock掉下游接口。 我们日常开发中用到的时候很多,比如日常开发环境调用支付宝,我们需要经常构造诸如协议过期、余额不足、请求超时、账户不存在等case的时候,就可以想办法将他们的接口mock掉。 市面上现在也有很多接口mock工具可以使用,如RAP、Yapi、Moco和DOClever等。 RAP RAP是阿里团队出的一款接口管理工具,能给你提供方便的接口文档管理、Mock、导出等功能。他可以通过分析接口结构,动态生成模拟数据,校验真实接口正确性,围绕接口定义,通过一系列自动化工具提升我们的协作效率。 现在该项目已暂停维护,但是官方团队推出了RAP2,RAP2 是在 RAP1 基础上重做的新项目,项目地址:https://github.com/thx/rap2-delos YApi YApi是去哪儿网移动架构组开发的一个开源项目,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API。官网地址:https://hellosean1025.github.io/yapi/ Moco Moco 是一个搭建模拟服务器的工具,其支持 API 和独立运行两种方式,前者通常是在 junit 等测试框架中使用,后者则是通过运行一个 jar 包开启服务。项目地址:https://github.com/dreamhead/moco DOClever DOClever是一个可视化免费开源的接口管理工具 ,可以分析接口结构,校验接口正确性, 围绕接口定义文档,通过一系列自动化工具提升我们的协作效率。主要提供接口信息管理、接口调试运行、接口Mock、自动化测试、团队协作等功能。官网地址:http://doclever.cn/controller/index/index.html

March 22, 2026 · 1 min · santu

什么是单元测试,和集成测试有什么区别?

典型回答 单元测试(Unit Testing)是对软件系统中最小的可测试单元进行测试的过程。单元测试通常是由开发人员编写的,旨在验证代码的正确性和可靠性。单元测试的目标是尽可能地覆盖代码中的每个功能单元,例如函数、方法、类等,并通过测试框架和断言来检测这些功能单元的正确性。单元测试通常是自动化的,并且可以快速运行。 **集成测试(Integration Testing)是对整个系统或系统的某个模块进行测试的过程。**集成测试的目标是验证系统中不同模块之间的交互和协调是否正确。集成测试通常是由测试人员编写的,旨在验证整个系统是否符合需求和规范。集成测试通常需要将不同的模块集成在一起,并使用手动或自动的测试工具来执行测试。 二者主要有以下区别: 范围不同:单元测试是针对代码的最小单元,例如函数、方法、类等进行测试,而集成测试是针对整个系统或某个模块进行测试。 编写者不同:单元测试通常由开发人员编写,而集成测试通常由测试人员编写。 自动化程度不同:单元测试通常是自动化的,并且可以快速运行,而集成测试通常需要手动或自动地执行。 目的不同:单元测试的目标是验证代码的正确性和可靠性,而集成测试的目标是验证整个系统或系统的某个模块是否符合需求和规范。 扩展知识 写单测有哪些好处? 写单元测试可以带来以下好处: 更早发现问题:单元测试可以在代码编写期间就发现问题,这样问题可以及时修复,而不是在后期开发或者生产中出现更严重的问题。理论上一个问题发现的越早,解决起来的成本就越低,所以写单元测试可以节省时间和资源,并提高开发效率。 确保代码质量:单元测试可以确保代码的质量,最重要的是,我们在修改代码以后,可以通过运行单元测试验证代码是否按照预期工作的。这可以避免回归问题,并提高代码的可维护性。使得开发人员敢改代码。 加速开发过程:单元测试可以快速地运行,提高开发速度。它可以帮助开发人员快速定位代码问题并解决问题,从而加快软件开发过程。 降低风险:单元测试可以降低代码出现错误的风险。通过测试代码,可以确保代码的正确性,并避免在生产环境中出现不必要的错误和故障。

March 22, 2026 · 1 min · santu

你平时是怎么做单元测试的?

典型回答 ✅什么是单元测试,和集成测试有什么区别? 面试官真的就只是想听一下什么是单元测试么?如果真的这么想是大错特错。问出这个问题的面试官考察面试者以下3点: 对软件工程的生命周期是否熟悉,是否充分理解测试阶段各种手段(单测、集成测试、冒烟测试等)和重要性。 面试者是否有足够的责任心,做好测试工作是对自己代码充分负责。 优秀的单测用例也体现出开发者的设计和编码的基本素质。 基于以上3点,我们需要思考什么是有效的单元测试? 题外话,《有效的单元测试》是一本非常值得推荐看的书 整个软件工程的生命周期,大致为: 需求分析阶段:需求调研、设计、评审 设计阶段:这里指架构设计阶段 开发阶段:开始正式编码 测试阶段:完成编码,包括: 自测:单元测试->集成测试 提测:QA介入集成测试,多轮测试 发布:QA完成测试,可以进行上线,包括: 预发布:部署到线上环境,QA进行回归测试,没问题放一部分流量进来,观察是否存在异常 上线:若预发布没问题,则代码正式上线,当然也会根据各种灰度或ab实验策略控制新功能流量占比,稳定跑一段时间没问题再放开全部流量 我们再来仔细分析每个阶段发现bug的成本,该成本主要是指从发现到解决问题的人力时间成本。 需求分析阶段:若大家评审设计不合理,可以不做,只占用小时级别的会议时间 设计阶段:架构设计也需要进行评审,同样只占用小时级别的会议时间 开发阶段:若前两个阶段没有发现问题,通常小功能是小时级别,大功能是天级别(甚至是星期、月),可能导致开发出无效的功能,重新做设计,重新开发的返工局面 测试阶段:无论是自测还是提测的集成测试,修改一个bug就意味着需要重新部署代码,大型项目启动时间可能是分钟级别。无论是自测还是提测,意味着会阻塞当前测试进度,bug多了反复部署累计出来的时间会非常高。而单元测试一个case通常是毫秒或秒级别的,做好单元测试可大幅度提升效率。很多公司非常注重单测覆盖率,有效性,甚至把单测放入CI/CD中,所有单测都跑成功才能部署。同时QA也会非常注重阻塞测试进度的情况。 发布阶段:通常进入发布阶段都是经过QA严格测试过的代码,不会出现明显的bug,但不代表没有。有一些bug可能在真实用户请求或流量大的时候才能出现,bug逃过了测试和预发布环境的测试,但到线上会直接出现。灰度和ab实验的一部分目标也是为了将线上的问题产生的影响降低到最小。这也是为啥各大互联网公司那么多大佬,依然会出现事故。这时候不只是时间成本,一个致命的bug可能带来直接的经济损失和用户流失。程序员一旦出了事故,就是个故事了。所以很多公司非常注重bug逃逸率,即在测试阶段没测出来的问题。 上面说了这么多,也是为了突出单元测试的重要性。以下总结一下写好单测的方法: 单测代码与正式代码一样重要,所以要层次清晰,命名符合实际用例场景,要有适当的注释,《代码整洁之道》的技巧同样可用于单测代码中。很重要的一点:case要让人能看懂 **单测不要盲目追求覆盖率,但要尽量测出所有可能的场景 ** 单测要保持可用,纳入CI/CD的流程,如果所有case跑不通,不允许部署 确保case每次运行是确定的,不依赖外部变化和不确定因素,包括不限于: 随机事件:如随机数,如果有最好Mock掉 IO:无论是磁盘IO、网络IO(数据库、外部接口)都需要隔离掉,否则IO抖一抖case就会失败,如果有最好Mock掉 必须有断言,否则单测没有意义,不要想着打印一下结果肉眼看一下就可以了,在跑全部case的时候谁会经常想着要看一眼? 验证边界和异常,这俩个是最容易被忽略的。边界可包括: 传入错误参数会怎么样 依赖(内部或外部接口、数据库环境等)返回不正确的结果会怎么样 异常包括: 外部异常:依赖(内部或外部接口、数据库环境等)向调用者抛出异常会怎么样 内部异常:代码自身抛出RuntimeException会怎么样 正式业务代码保证单一职责,高内聚低耦合可让单测更轻松,测试粒度更细,覆盖率也更高。一个方法一个类只干一个事,单测case就可以只关注当前要测试方法的有效性就行了,不需要关注方法之间的调用,如果你的每个方法都测通了,组合起来也都会通的。当然一个case也要只干一件事! 还有一个好的方法是采用TDD(测试驱动开发),即先列出所有可能的用例,再实现逻辑代码。这样产出的代码可快速构建出单测。不过国内很少有采用TDD开发模式的公司。 题外话,DDD(领域驱动设计)中提倡明确的边界划分,事件风暴,防腐层的设计都给TDD和单元测试做了很好的铺垫。 上面说使用Mock屏蔽IO和随机事件,当然也可以用在各种依赖上(如spring bean之间的依赖,工具类、各种内部接口的依赖),Mock是将所依赖的资源造假,我们假装依赖都是成功或失败的,这样只要测试自身代码对其产生了什么结果。 ✅什么是Mock?怎么做单测的Mock? Java工程也可以集成spock framework进行单测,spock使用groovy语言编写单测。由于是动态语言非常灵活,很适合编写轻量的单测代码。同时spock不只局限于Mock,还提供各种高效的功能(这些都是传统Junit、Mockito无法实现的): Spy,只对部分资源进行Mock,可以很方便对同一个类内相互调用的方法进行Mock和验证 Mock,对依赖资源进行Mock,同时验证依赖的资源调用多少次,比如:测试一个Redis写功能,可以对Redis客户端进行Mock,验证传入的方法参数是否符合预期,验证Redis写入方法的调用次数 Stub,对依赖资源进行Mock一个结果,不关注调用次数、传参是否符合预期 直接无视待验证方法的成员封装等级,可以直接测试private声明的方法和变量 基于数据驱动的测试,可以在一个case里通过where:关键词和数据表格的方式对要测试的参数预期返回值的所有可能情况进行验证 可以很方便的验证要抛出的异常 可以很方便的集成spring,包括可单测spring mvc、spring boot的http接口层,不需要启动web容器 以上是如何做好单测的思路,请记住: 1. 有用户访问的项目和无用户访问的项目是不一样的,相同的代码甚至在极端的用户流量下会产生不一样的效果。对于极端的用户流量,改一行代码上线都是如履薄冰 2. 敬畏每一次上线和线上操作 所以能够写出好的单测代码,是一个优秀程序员的基本素养。

March 22, 2026 · 1 min · santu

单测覆盖率是如何统计的?原理是什么?

典型回答 我们在跑单元测试的时候,经常会有一个覆盖率的指标,甚至很多发布过程要求覆盖率一定要达到一定的百分比。 那么,单测覆盖率是如何统计出来的呢?底层的实现原理是什么呢? 单测覆盖率的统计原理其实是通过字节码插桩实现的, 即在编译时在代码中插入一些特殊的监控代码,来记录测试执行过程中代码的执行情况,进而得出代码的覆盖情况。这些监控代码可以在运行时记录代码被执行的情况,也可以在编译时生成代码覆盖率报告。 常见的单测覆盖率统计工具有JaCoCo、Emma、Cobertura等,这些工具可以在编译时或者运行时对代码进行插桩,记录代码的执行情况,并生成覆盖率报告。 扩展知识 字节码插桩 Java字节码插桩技术是指在编译期或运行期,通过修改Java字节码的方式,向代码中插入额外的代码,它可以在不改变Java源代码的情况下,对Java应用程序的运行时行为进行监控、调试、分析和优化等。例如实现性能监控、代码覆盖率检测、代码安全扫描等。 字节码插桩技术通常包括以下几个步骤: 生成目标类的字节码,这可以通过Java编译器(如javac)或其他工具(如AspectJ)完成。 解析字节码,识别需要插桩的代码区域(如方法、循环、异常处理等)。 插入额外的字节码,这些字节码通常是通过编写Java代码来实现的,并通过字节码生成库(如ASM、Javassist等)生成对应的字节码。 将修改后的字节码重新写回到磁盘或内存中,以便后续使用。 假设我们需要对一个Java方法进行性能监控,我们可以在方法的入口和出口处分别插入计时器,来统计方法的执行时间。这可以通过以下代码实现: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Monitor { public static void start() { long startTime = System.nanoTime(); // 将起始时间记录到ThreadLocal中,以便在方法返回时进行计算 ThreadLocalHolder.set("startTime", startTime); } public static void end() { long endTime = System.nanoTime(); // 获取起始时间 long startTime = (long) ThreadLocalHolder.get("startTime"); // 计算方法执行时间 long elapsedTime = endTime - startTime; System.out.println("Method execution time: " + elapsedTime + "ns"); } } public class Example { public void method() { Monitor.start(); // 执行方法逻辑 Monitor.end(); } } 但是,如果需要对多个方法进行性能监控,就需要在每个方法中分别插入Monitor.start()和Monitor.end(),这样会导致代码重复,可读性差,并且容易漏掉一些方法。这时,我们就可以使用字节码插桩技术,在编译期或者运行期,自动向每个方法的入口和出口处插入Monitor.start()和Monitor.end(),来实现代码的统一性和可维护性。 ...

March 22, 2026 · 2 min · santu

如何对JDBC这一层做单元测试?

典型回答 在我们的业务逻辑代码中,很重要的一部分是数据库的增删改查的逻辑,那么,如果对这部分代码进行单元测试呢? 如果直连数据库的话,不仅操作比较慢,还会造成大量的脏数据,而且数据库中已有的一些历史数据也可能会影响单元测试的结果,那么,有什么好的办法呢? 那就是可以通过内存数据库来帮助我们解决上面的问题,内存数据库就是主要依靠内存存储数据的数据库管理系统,在单元测试时使用内存数据库就可以在单测运行时初始化数据库及表结构及数据,在运行结束后直接回收掉。特别的方便。 H2 数据库是一个比较好用的内存数据库,它可以随着程序启动去创建数据表和数据,随着程序关闭而销毁,非常方便,可以作为单元测试数据库的替代品; 关于内存数据库的更多细节,面试的时候一般不太会追问的更多,这里就不展开介绍了,大家感兴趣的话,可以去 官网地址:http://www.h2database.com/html/main.html github地址:https://github.com/h2database/h2database 查看更多资料。

March 22, 2026 · 1 min · santu

留言给博主