很多人刚开始学测试,都会产生一个困惑:书上一会儿讲单元测试、一会儿讲黑盒测试、一会儿又冒出冒烟测试、回归测试、α 测试、β 测试……这些名字放在一起,像一锅乱炖,完全理不清谁跟谁是兄弟、谁跟谁压根不在一个维度上。

这个困惑的根源在于:测试分类的"尺子"不止一把。你看到的那些名词,其实是用不同的维度去切同一头大象。就好比一个人,按年龄分他是"青年",按职业分他是"程序员",按籍贯分他是"四川人"——这三个标签并不冲突,因为它们是从三个不同角度在描述同一个人。测试分类也一样:"单元测试"是按阶段分,"白盒测试"是按方法分,"回归测试"是按目的分,"自动化测试"是按是否人工分,它们互相正交,可以同时成立。

这篇文章,我要把这"几把尺子"一次讲齐。我用一张大表先给你全景,再逐类掰开揉碎,带你走过每个概念的来龙去脉、适用场景,以及最容易踩的坑。下面每一节我都会留思考题,而且都当场给出详解答案,你不用翻到文末挠头。

先看这张分类总表,它就是这篇文章的地图:

分类维度分出的类型一句话解释通常在什么时候做主要执行者
按测试目标(测什么特性)界面测试、功能测试、性能测试、可靠性测试、安全性测试、易用性测试针对软件的不同"质量属性"逐项验证主要落在系统测试阶段测试人员(偏黑盒)
按是否运行程序静态测试、动态测试静态"不跑程序靠分析",动态"跑程序看结果"全流程都会用到静态多配合开发,动态测试人员为主
按测试方法(看不看内部)黑盒测试、白盒测试、灰盒测试黑盒不看内部,白盒死盯内部,灰盒折中黑盒偏系统/验收,白盒偏单元黑盒归测试,白盒归开发
按测试阶段单元测试、集成测试、系统测试、验收测试从"最小单元"一路放大到"整个系统"依次递进单元给开发,系统验收给测试/用户
按测试目的冒烟测试、回归测试等不是新阶段,是"为了什么目标"而跑穿插在各阶段反复出现开发、测试都做
按是否手工执行手工测试、自动化测试人肉执行 vs 机器脚本执行长期并存,各有分工手工全员,自动化偏资深
按实施组织α 测试、β 测试、第三方测试按"谁来测、在哪测"分版本正式发布前后内部用户、外部用户、独立机构
按测试地域国际化测试、本地化测试跨语言跨地区 vs 特定地区面向全球发布时测试人员

看到没,"尺子"不同,出的类型就不同。下面我一把把讲。

为什么要对软件测试进行分类

先回答最朴素的问题:为什么不直接说"测试"两个字,非要分这么多类?

两个理由。第一个是降低复杂度。软件测试贯穿软件的整个生命周期,是一个高度复杂的活动。如果把所有测试动作混在一起管理,你没法知道"当前这个阶段该测什么、用什么方法测、测到什么程度算到位"。分类,本质上是把这座大山切成一块块可以单独规划、单独执行、单独验收的小山包,这样在不同层次、不同阶段就能更有效地组织和管理测试工作。

第二个理由是明确责任与分工。单元测试该开发写还是测试写?系统测试的依据文档是哪份?验收测试到底谁说了算?如果这些边界不清楚,开发之间就会互相"踢皮球"。分类给了每一类测试一个明确的主责人、依据文档和验收标准,这让团队协作有据可依。

一句话收束:分类不是为了把名词背下来,而是为了让测试可管理、可执行、可验收。 带着这个目的去学后面每一个分类,你就不会困在术语里。

按测试目标划分:软件到底帮用户做了哪些事

这一把尺子,回答的是"这一轮测试,我要验证软件的哪个质量特性"。源代码里把这些统称为质量属性(Quality Attribute)——软件除了"功能能用"之外,还必须跑得快、稳得住、扛得下攻击、用着舒服。下面逐一展开。

界面测试(UI 测试)

软件只是一种工具,它跟人最直接的交流窗口就是界面(UI,User Interface,用户界面)。界面设计决定了用户对你这个产品的第一印象;界面就像人的一张脸,设计合理,能给用户带来轻松愉悦的感受。反过来,如果不严格按 UI 设计稿来测,页面上可能会出现一堆匪夷所思的排版问题。

界面测试,按教材的定义,就是根据界面的需求(一般是 UI 设计稿)和界面的设计规则,对软件界面所展示的全部内容进行测试和检查。它一般包括这几个检查点:

  • 内容显示:验证界面内容显示的完整性、一致性、准确性、友好性。比如界面内容对屏幕大小的自适应、文字换行是否正确、内容是否全部清晰展示出来、有没有截断或溢出。
  • 布局排版:验证整个界面的布局和排版是否合理,不同板块的字体设计、图片展示是否符合需求。
  • 控件行为:对界面不同控件进行测试,比如对话框、文本框、滚动条、选项按钮是否都能正常使用,有效态和无效态(比如按钮置灰)是否设计合理。
  • 风格趋势:界面的布局和色调是否符合当下的设计趋势(也就是"看起来是不是过时")。

一句话总结界面测试的关注点:它看的不是一个功能"能不能跑通",而是这个页面"好不好看、对不对、顺不顺眼"。这也是界面测试和下面要讲的功能测试最根本的区别。

思考题 1:界面测试和功能测试都会去"点一个按钮",它们关注的东西有何不同? 详解:功能测试关心的是"点了这个按钮之后,业务逻辑是否正确执行"——比如点"登录",它关心账号密码校验、会话建立、跳转是否正确;界面测试关心的是"这个按钮本身"——它有没有按设计稿放在该放的位置、颜色形状对不对、在禁用状态下有没有正确置灰、文字有没有溢出按钮边界。用一句话区分:功能测试测"行为对不对",界面测试测"长相符不符合设计稿"。同一个操作,两个维度都要覆盖。

功能测试

功能测试就是对产品的各个功能进行验证,根据功能测试用例,逐项测试,检查产品是否达到用户要求的功能。它的核心是回答一件事:程序是否以期望的方式运行、是否满足需求规格书(Requirements Specification)里写明的功能要求。

功能测试怎么设计用例?答案是用黑盒的设计方法——这里先埋个伏笔,黑盒我们下一大节专讲,你现在只需要建立印象:功能测试站在用户视角,不考虑内部实现,用等价类、边界值、判定表、正交试验、场景法、错误推测等方法,对着需求文档把每种输入情况都覆盖到。

本地化软件还有一句附加要求:要通过合适的平台、浏览器和测试脚本,保证目标用户的体验足够好,就像这个软件是专门为当地市场开发的一样。这句说的其实是功能测试要做的兼容维度——同一个功能在不同浏览器、不同设备上都能正常工作。

性能测试

你有没有遇到过这些情况:网页打开越来越慢,查询数据要等很久才出列表,软件用着用着越来越卡。这些都是性能问题。

性能测试的目的,就是验证软件在一定的负载下,其响应时间、吞吐量、资源占用等性能指标是否满足要求。性能测试不是"测一次"就完事,而是一个闭环:先分析性能需求,再基于系统需求和系统架构完成性能测试的设计与执行,最后持续做性能调优。

一个很典型的场景可以帮你建立直觉:同样是博客系统,一个网站在 100 人同时访问时页面秒开,另一个 10 人就卡了——前者的性能就明显好于后者。性能往往不是"对不对"的问题,而是"够不够快、扛不扛得住"的问题。

可靠性测试

可靠性(Availability,也叫可用性)指系统正常运行的能力或程度,一般用正常向用户提供软件服务的时间占总时间的百分比来表示:

可靠性 = 正常运行时间 ÷ (正常运行时间 + 非正常运行时间) × 100%

这个公式想读懂,得先分清两个词:公式里的是"正常运行时间"与"非正常运行时间"的比值。让你有体感的,是下面这个生活化的例子——为了不侵权,我换个人名讲:有个人叫老李,你请他吃饭十回,他回回都来,那他就是个"可靠"的朋友;要是请他十回只来一回,那他的"可靠性"就是 10%,这朋友你觉得靠谱吗?系统的非正常运行时间,可能是硬件故障、软件故障、网络故障或断电等原因造成的,这些因素让系统停止工作、连接中断、或者性能急剧下降到没法正常使用服务。

可靠性指标通常要求达到"4 个 9"或"5 个 9",也就是 99.99% 或 99.999%。这两个数字看着差别不大,换算成年停机时间却天差地别。我按公式算给你看——一年按 365 天、每天 24 小时、每小时 60 分钟算,一年共 525600 分钟:

可用性年度允许停机时间(计算)实际约值
99.9%(3 个 9)525600 × 0.001 = 525.6 分钟约 8.76 小时
99.99%(4 个 9)525600 × 0.0001 = 52.56 分钟约 52.56 分钟(不到一小时)
99.999%(5 个 9)525600 × 0.00001 = 5.256 分钟约 5.26 分钟

(说明:这几组数字我专门核对过权威资料——一年总时长按 525600 分钟算,4 个 9 换算成约 52.56 分钟、5 个 9 约 5.26 分钟,是行业通行的结果。有些旧教材里会把"525600"误抄成别的数,你以这个计算为准。)

看出来了吗?从 4 个 9 提到 5 个 9,"只"提升了 0.009%,但全年允许出故障的时间却从接近一小时被压缩到只剩 5 分多钟。99.99% 意味着一个 7×24 小时不间断运行的系统,一年里可以"坏掉"不到一小时;99.999% 则意味着一年里几乎只能"闪断"五次。

不同系统对可靠性的要求差别很大:非实时性的信息系统或一般性网站,99% 到 99.5% 就够了;而军事系统、金融支付、核心通信这类对连续性要求极高的系统,通常要往 4 个 9、5 个 9 去追。可靠性不是越高越好,而是匹配业务容忍度——因为每多一个 9,背后的冗余架构和运维成本是指数级增长的。

安全性测试

安全性指信息安全,是计算机系统或网络保护用户数据隐私与完整性、保护数据正常传输、抵御黑客与病毒攻击的能力。安全性测试属于非功能性测试里非常重要的一块。

系统常见的安全漏洞与威胁包括:

  • 输入域攻击:在输入框里输入恶意或带病毒的脚本、超长字符串,试图破坏系统或窃取数据(比如 XSS 跨站脚本)。
  • 代码注入:SQL 注入、XML 注入等,攻击者通过构造特殊输入,让后端的查询或解析逻辑被"劫持"。
  • 不安全的存储与传输:敏感数据明文存放在数据库、或未加密就在网络上传输。
  • 文件风险:数据文件、邮件文件、系统配置文件里含有危害系统的信息或数据。
  • 访问控制缺陷:权限分配有问题,普通用户能访问到本不该访问的资源。
  • 身份欺骗与篡改:假冒他人 ID 越权操作,或对传输中的数据进行恶意修改、破坏完整性。

安全性测试的方法主要有代码评审、渗透测试、安全运维等。工具有两个阵营:静态安全测试(不运行程序,直接分析源码找漏洞),常用工具有 Coverity、IBM AppScan Source、HP Fortify;动态安全测试(运行程序并主动注入攻击输入),常用工具有 OWASP 的 ZAP、HP WebInspect 等。其中静态安全测试是最常用的方法。这里又出现了"静态/动态"两个字眼,别急,下一大节就专门拆它。

思考题 2:我家个人电脑到底要不要装杀毒软件? 详解:这是个拿自己开涮的问题,答案是"看情况但态度要认真"。个人电脑面对的最大威胁不是"被国家级的攻击者盯上",而是勒索病毒、木马、钓鱼链接这类常见攻击,装一款合格的杀毒/安全软件、保持系统更新、不从不明来源下载软件,成本很低、收益很大。而你写代码这件事本身就要有安全意识——很多系统漏洞正是由开发者在代码里留下的,这也是为什么质量体系里单独把安全性测试拎出来讲。

易用性测试

许多产品都应用人体工程学的成果,让产品用起来更灵活、更舒适;软件产品也始终关注用户体验。针对软件易用性方面的测试,就叫易用性测试。在 ISO 25020 标准里,易用性被归纳为容易发现、容易学习、容易使用。我们主要从这几个方面展开:

  1. 标准性和规范性:现在的软件运行平台,其 UI 标准往往已经成了大家的共识——比如安装软件界面的外观、什么场合用什么样的对话框。界面信息必须符合规范和习惯,否则用户用着不舒适、得不到认同。测试人员有责任把那些"与标准、规范、习惯不一致"的问题作为缺陷上报。
  2. 直观性:界面要易懂、清晰,布局合理,操作响应在用户预期之内。比如数据统计结果用条形图、扇形图等报表形式清晰展示。
  3. 灵活性:软件能提供不同选项满足不同使用习惯的用户完成同一功能。但灵活性要把握好度——选项太多,会增加设计复杂性和实现难度。比如手机键盘支持九宫格、全键盘,还支持手写。
  4. 舒适性:界面友好、美观,操作过程顺畅,色彩运用恰当,按钮有立体感等。

给这一小节收个尾:界面、功能、性能、可靠性、安全、易用,这六类统称通常叫"按测试目标"或"按测试内容"的分类。你要记住的关键是——"目标"这一把尺子,本质是在回答"测软件的哪一项质量特性",后面几把尺子则回答完全不同的问题。

按是否运行程序划分:静态测试与动态测试

这一把尺子的判据非常干净,就一句话:测试时,到底有没有真正运行被测程序。

静态测试

静态测试(static testing)就是不实际运行被测软件,只静态地检查程序代码、界面或文档中可能存在的错误。它不靠测试数据去执行程序,而是通过分析被测对象——检查源程序的设计、内部结构、逻辑、代码风格和规格——来判断正确性。

常见的静态测试方式有代码走查(开发人员一起逐行读代码找问题)、代码审查、以及代码扫描工具(用自动化工具在编译前扫出潜在缺陷和风格问题)。它们的共同点是"程序根本没跑起来"。

动态测试

动态测试(dynamic testing)指的是实际运行被测程序,输入相应的测试数据,检查实际输出结果与预期结果是否相符。

判断一个测试属于动态还是静态,唯一的标准就是"是否运行了程序"。绝大多数软件测试工作都属于动态测试——因为只有真正跑起来,才能确认程序在真实输入下的行为是否正确。

这里有个初学者经常踩的坑,我提前给你点破:"静态/动态"和"代码写没写、是不是自动化"是两码事。举个例子,你用 JUnit 写了一个测试类,跑 mvn test 让程序真正执行起来,这是动态测试;你写的是完整可运行程序。相反,你只是对着源代码人肉看一遍逻辑、没点运行,哪怕你心里想得再多,那也是静态测试。所以别把"静态"理解成"没自动化"或"靠人工",它的定义口径只有一个:跑没跑程序。

思考题 3:我写了一个 JUnit 测试,但只是让它做了一次空跑(比如只断言 true),这算静态还是动态? 详解:算动态测试。因为只要测试实际执行(invoke)了被测程序,并在运行状态下产生了执行结果,无论断言多简陋,它都属于动态测试的范畴。静态测试的核心特征恰恰是"程序根本没运行"。这个题用来帮你把"是否运行程序"这一判据钉死——不是看内容多丰富,而看程序有没有真正跑起来。

按测试方法划分:黑盒、白盒与灰盒

这一把尺子问的是:测试时,我到底看不看程序内部的结构和实现? 由此分出黑盒、白盒与灰盒三种测试方法。

我要先敲掉一个流传很久的误解:"黑盒""白盒"不是指程序或者盒子的颜色。这两个词是借用"盒子是否透明"这个比喻得来的——"黑盒"代表你看不见盒子内部、"白盒"(也叫"透明盒")代表盒子内部对你一览无余。把这个比喻记住,下面的每一个定义都是顺理成章的。

黑盒测试

黑盒测试就是在完全不考虑程序逻辑和内部结构的情况下,检查系统功能是否按照需求规格说明书的规定正常使用——能不能适当地接收输入数据、输出正确的结果。因为它只关注输出和输入、只注重功能,所以也叫数据驱动测试(Data-driven Testing)。

黑盒测试的优点:

  • 不需要了解程序内部的代码和实现,不关注内部结构,门槛相对低;
  • 从用户角度出发设计用例,很容易知道用户会用到哪些功能、遇到哪些问题,能锻炼测试人员的产品思维;
  • 用例基于需求文档设计,不容易遗漏需求中要求测试的功能点。

黑盒测试的缺点也很明确:不可能覆盖到所有代码——因为你看不到代码,有些隐蔽的代码分支你是测不到的。

黑盒测试用到的用例设计方法:等价类划分、边界值分析、因果图(判定表)、场景法、错误推测法等。我提这些名字,你先混个脸熟,具体方法那是另一篇文章的事。

白盒测试

白盒测试又称结构测试或逻辑测试,它是基于程序内部结构来设计测试用例的。它的目的是通过检查软件内部的逻辑结构,对软件中的逻辑路径进行覆盖测试;在程序的不同地方设立检查点,检查程序的状态,以确定实际运行状态与预期是否一致。

白盒测试能力的一个硬前提是:你得能看懂代码。你需要在了解内部实现的基础上,去设计用例来"逼"每一条逻辑路径都跑一遍。

白盒测试主要分为静态测试和动态测试两种,这跟你刚学的"按是否运行分"正好喝上了同一碗汤:

  • 白盒的静态测试:即代码走查、代码审查、代码扫描这类"不运行只看代码"的活动;
  • 白盒的动态测试:即运行程序,用一系列逻辑覆盖的方法来设计用例,共六种:语句覆盖、判定覆盖、条件覆盖、判定条件覆盖、条件组合覆盖、路径覆盖。

这里我插入一句总结性的话,也帮你记住白盒测试在生命周期中的位置:白盒测试主要应用于单元测试阶段;设计用例时一般是"先做静态设计,再补动态用例";设计用例的思路,通常以路径测试为主,重点模块再叠加逻辑覆盖方法。

白盒的动态逻辑覆盖:六种覆盖法

这六种方法侧重点各不相同,我要把它们逐一讲清,并且用同一个例子走到底,你才能看出它们"强度"的差别。先约定一个小程序段的逻辑结构——它包含两个判定:

  • 判定一:A and B(A、B 是两个"条件")
  • 判定二:C or D(C、D 是两个"条件")

注意区分两个词:条件是逻辑表达式里最基本的、不可再分的原子判断(A、B、C、D 各是一个条件);判定是由条件组合而成、最终能取真或假的那个整体结果(A and B、C or D 各是一个判定)。这个区分是理解六种覆盖法的一把钥匙。

语句覆盖:要求每个可执行语句至少执行一次。它是最弱的一种覆盖。针对 A and B,要让它为真,需 A 为真且 B 为真;针对 C or D,要让它为真,C 为真或 D 为真即可。所以我们得出用例 1:A 为真、B 为真、C 为真、D 为假,程序从头到尾的语句都走了一遍。但问题来了——如果某个分支里的语句因为条件组合没走到,语句覆盖根本测不出来,所以它是最弱的。

判定覆盖(也叫分支覆盖):要求每个判定的取真分支和取假分支至少都经历一次。它比语句覆盖强。看这个例子:

  • 要让 A and B 为真(判定一取真分支)=> A 真、B 真;
  • 要让 A and B 为假(判定一取假分支)=> A 真 B 假、或 A 假 B 真、或 A 假 B 假;
  • 要让 C or D 为真(判定二取真分支)=> C 真 D 任意、或 C 任意 D 真;
  • 要让 C or D 为假(判定二取假分支)=> C 假、D 假。

于是得出两个用例:

  • 用例 1:A 真、B 真、C 真、D 假 —— 覆盖了判定一的真分支、判定二的真分支;
  • 用例 2:A 真、B 假、C 假、D 假 —— 覆盖了判定一的假分支、判定二的假分支。

两个用例,四个分支全跑到了。但注意:判定覆盖盯着"判定整体",不关心 A and B 里面 A、B 各自有没有取过假值——这也是它还不够强的原因。

条件覆盖:要求每个判定里的每个原子条件其真、假取值至少各满足一次。它关注的是条件,而不是判定整体:

  • A 要取到真、假;
  • B 要取到真、假;
  • C 要取到真、假;
  • D 要取到真、假。

于是得出两个用例:

  • 用例 1:A 真、B 真、C 真、D 真 —— 把四个条件取真的情况覆盖了;
  • 用例 2:A 假、B 假、C 假、D 假 —— 把四个条件取假的情况覆盖了。

到这里埋一个大坑,特别爱考:满足条件覆盖,不一定满足判定覆盖。 比如上面的用例 1(A 真 B 真 C 真 D 真)还覆盖了判定的真分支,但用例 2 里 A and B 因为 A 假整个判定为假、C or D 因为 C 假且 D 假整个判定为假——看,两个用例合起来,判定一的两个分支(真、假)和判定二的两个分支(真、假)其实都覆盖了。但换一组用例(比如 A 真、B 假 与 A 假、B 真),条件覆盖达标了,判定一的两个分支就可能漏掉一个。所以教材上会说:条件和判定覆盖没有绝对的强弱关系,侧重点各不相同。

判定条件覆盖:同时满足判定覆盖和条件覆盖的要求。继续沿用上面的用例即可:

  • 用例 1:A 真、B 真、C 真、D 真(判定一真、判定二真,且四个条件都取真);
  • 用例 2:A 假、B 假、C 假、D 假(判定一假、判定二假,且四个条件都取假)。

两个用例,判定分支和条件真/假全都覆盖到了。它是判定覆盖与条件覆盖的"合体",但依然不一定覆盖到"条件的每一种组合"——那正是下一种覆盖要解决的。

条件组合覆盖:要求每个判定中所有条件的各种取值组合都至少出现一次。条件越多,组合是指数级增长的(n 个条件最多 2^n 种组合)。对 A and B 来说,A、B 的组合有:真真、真假、假真、假假;对 C or D 来说,C、D 的组合同样有真真、真假、假真、假假。于是:

ABCD
真真真真
真假真假
假真假真
假假假假

这四行,每行就是一个用例——一共四个用例,条件的所有取值组合都覆盖到了。(这里有个小前提:当程序中条件的组合彼此独立时,按行配对设计即可;如果条件存在逻辑依赖,实际组合数会少一些,设计时要结合程序真实结构。)

路径覆盖:要求覆盖程序中所有可能执行的路径。这是强度最高的一种,但碰到循环、多重判断,路径会呈指数级暴涨,往往难以完整实现。举一个常见的小例子,程序里有两条判定:

P1: if (x > 0 && y > 0)     // 记为判定 P1
P2: if (z < 0)              // 记为判定 P2

我们把四个原子条件分别记为:C1 是 x > 0,C2 是 y > 0,C3 是 z < 0。由于 && 有短路特性,程序的可能路径可以这样梳理:

  • 路径 1(P1 真 → P2 真):x > 0 为真 且 y > 0 为真,然后 z < 0 为真;
  • 路径 2(P1 真 → P2 假):x > 0 为真 且 y > 0 为真,然后 z < 0 为假;
  • 路径 3(P1 假,直接跳过 P2):x <= 0 或 y <= 0,P1 为假,整段 if 体被跳过,程序直接走后续代码。

所以路径覆盖下,至少要设计下面三个用例(取值举例):

用例xyz走到的路径
用例 111-1P1 真、P2 真
用例 2111P1 真、P2 假
用例 3-11任意P1 假(短路跳过 P2)

(说明:用例 3 里因为 x > 0 已为假,&& 短路,y、z 的取值不影响路径;我把 z 标成"任意"。三组取值把三条路径全部走通了。)

到这里六种覆盖法就齐了。把它们按强度从弱到强排成一排,你就能看出这条"覆盖的光谱":语句覆盖(最弱)→ 判定覆盖 → 条件覆盖 → 判定条件覆盖 → 条件组合覆盖 → 路径覆盖(最强)。覆盖得越强,用的用例往往越多、代价越大,所以实际工程里不可能每个模块都无脑做到路径覆盖,通常是关键、复杂的模块才叠加更强的覆盖方法。

灰盒测试

讲完黑白,再看灰。灰盒测试是介于白盒测试与黑盒测试之间的一种测试:它既关注输入、输出的正确性,同时也关注程序内部的部分情况,但它对内部的关注远不如白盒详细和完整。灰盒测试多用于集成测试阶段(模块之间连接的场景),因为集成时你既想从外部验证行为,又需要根据模块间的接口和内部架构来定位问题。

要特别强调两个边界,免得你理解反了:

  • 灰盒测试基本不能替代黑盒测试。黑盒测试是对产品覆盖范围最广的测试——因为它是纯用户视角,能覆盖到整个产品的所有对外功能;而灰盒介入内部之后,要覆盖同样的范围,需要设计非常多的用例,代价巨大。
  • 白盒测试又比灰盒更"细"。灰盒只关注内部"部分情况",远达不到白盒那样对每条逻辑路径的逐一覆盖。

所以这三者不是谁替代谁,而是各就各位。

思考题 4(高频面试题):你都知道哪些测试方法?哪种用得比较多? 详解:这是一个几乎必被问到的开放题,回答要有层次。先说分类:常见的测试方法有黑盒测试、白盒测试和灰盒测试。再说使用主体和场景:开发人员在单元测试、集成测试阶段更多使用白盒测试和灰盒测试——因为代码是他们写的,最了解内部结构;测试人员在系统测试、验收测试阶段主要使用黑盒测试——站在用户视角验证行为。最后给结论:对测试人员来说,相较于白盒测试,黑盒测试用得更多一些。因为绝大多数正式的系统/验收测试,都是基于需求文档、以用户视角来验证软件功能的。回答时把"谁在哪个阶段用什么方法"讲清楚,是拿高分的关键。

按测试阶段划分:单元、集成、系统与验收

这一把尺子是按测试发生的开发阶段来分的。它最能体现"从小到大、逐步放大"的递进关系。为了让阶段琐碎的知识点好记,我沿用天气语文的经典类比——但要换个不受版权干扰的说法,用"盖一栋房子"来讲透这四个阶段。

造房子,得先从一块块砖开始——单块砖本身质量得过关(单元测试);砖合格了,砌成一面墙——墙与墙、墙与梁咬合得对不对(集成测试);整栋房子立起来了——住进去之前,整体验房(系统测试);最后业主收房——业主验收满不满意(验收测试)。跟着这个类比,下面每一类都顺理成章。

单元测试

单元测试(Unit Test)针对软件的最小组成单元进行测试。这里的"最小单元"实际上是人为主观定义的——一个方法、一个类都可以理解为"最小单元"。它与编码同步进行,主要采用白盒测试方法,从被测对象的内部结构出发设计测试用例。

  • 测试阶段:编码后或编码前(TDD,Test-Driven Development,测试驱动开发——先写测试再写实现);
  • 测试对象:最小模块;
  • 测试人员:白盒测试工程师或开发工程师(现实中很多时候就是开发自己);
  • 测试依据:代码和注释 + 详细设计文档;
  • 测试方法:白盒测试;
  • 测试内容:模块接口测试、局部数据结构测试、路径测试、错误处理测试、边界测试。

为了让"单元测试"落地,我用一个 Java 的例子说话。下面是一个冒泡排序方法,以及一个正经的单元测试类。代码都是完整可用的,你复制下来就能跑。

先是被测的排序方法(放在 BubbleSort.java):

public class BubbleSort {
    // 对数组做升序冒泡排序(原地排序,直接修改传入数组)
    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        for (int i = 0; i < n; i++) {
            // 每轮把当前未排序区段里最大的数冒泡到末尾
            for (int j = 0; j < n - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }
}

再是测试类(BubbleSortTest.java,使用 JUnit 5):

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 
public class BubbleSortTest {
 
    @Test
    public void sortUnsortedArray() {
        // 乱序数组:让最普通的场景走通
        int[] arr = { 5, 3, 9, 1, 7 };
        int[] expected = { 1, 3, 5, 7, 9 };
        BubbleSort.bubbleSort(arr);
        assertArrayEquals(expected, arr);
    }
 
    @Test
    public void sortEmptyArray() {
        // 空数组:边界情况,排序后应保持不变
        int[] arr = {};
        int[] expected = {};
        BubbleSort.bubbleSort(arr);
        assertArrayEquals(expected, arr);
    }
 
    @Test
    public void sortAlreadySortedArray() {
        // 已有序数组:排序后应保持不变
        int[] arr = { 1, 2, 3, 4, 5 };
        int[] expected = { 1, 2, 3, 4, 5 };
        BubbleSort.bubbleSort(arr);
        assertArrayEquals(expected, arr);
    }
 
    @Test
    public void sortArrayWithDuplicates() {
        // 含重复元素:验证稳定性与正确性均不受影响
        int[] arr = { 4, 2, 4, 1, 3, 2 };
        int[] expected = { 1, 2, 2, 3, 4, 4 };
        BubbleSort.bubbleSort(arr);
        assertArrayEquals(expected, arr);
    }
}

看到这组测试你就理解了单元测试的几个典型口味:正常场景(乱序数组)、边界(空数组)、特殊序列(已有序)、数据特征(重复元素)。每一条都直指排序代码的某个角落。

集成测试

集成测试又称联合测试(联调)、组装测试:将程序模块采用适当的集成策略组装起来,对系统的接口及集成后的功能进行正确性检测。它要回答的核心问题是:模块与模块之间的接口是否正确。

  • 测试阶段:一般在单元测试之后进行;
  • 测试对象:模块间的接口;
  • 测试人员:白盒测试工程师或开发工程师;
  • 测试依据:单元测试通过的模块 + 概要设计文档;
  • 测试方法:黑盒测试与白盒测试相结合(这正好呼应了灰盒常用于集成阶段);
  • 测试内容:模块之间的数据传输、模块之间的功能冲突、模块组装后的功能正确性、全局数据结构、单模块缺陷对系统的影响。

集成测试最容易踩的一个坑是"锅底效应":每个模块单独跑全绿,一拼起来就黑屏报错。原因往往就是接口——比如 A 模块以整形传入,B 模块期待的是字符串;或者 A 改了调用约定而 B 没跟着改。单元测试测不到这些东西,因为单元测试只盯着单个模块内部。这正是集成测试存在且不可跳过的原因。

系统测试

系统测试对通过集成测试的整个系统(软硬件环境一起)做整体测试,验证系统功能性和非功能性需求的实现。

  • 测试阶段:集成测试通过之后;
  • 测试对象:整个系统(软件 + 硬件);
  • 测试人员:黑盒测试工程师;
  • 测试依据:需求规格说明文档;
  • 测试方法:黑盒测试;
  • 测试内容:功能、界面、可靠性、易用性、性能、兼容性、安全性等。

注意系统测试的内容里出现了一个前面没细说的词——兼容性:验证软件在不同操作系统、不同浏览器、不同屏幕分辨率、不同硬件环境上都能正常工作。兼容性实际是"跨环境的功能可用性",这也是为什么花名常归在系统测试里。另外,系统测试把我们在"按目标分类"那一节讲的所有质量属性——功能、性能、可靠、安全、易用、界面——在这个"整机"层面一次性校验,所以它是最见完整的"大综合"。

验收测试

验收测试针对用户需求,对通过系统测试的软件做交付性测试,以确定系统是否满足验收标准,由用户或其他授权机构决定是否接受系统。它是软件部署之前、技术测试的最后一个阶段,也叫交付测试。

  • 测试阶段:系统测试通过之后;
  • 测试对象:整个系统(包括软硬件);
  • 测试人员:主要是最终用户或需求方;
  • 测试依据:用户需求、验收标准;
  • 测试方法:黑盒测试;
  • 测试内容:同系统测试(功能、各类文档等)。

到这里,单元→集成→系统→验收这条主线就走完了。在我看来,这四个阶段最该被记住的不只是各自定义,而是它们之间的衔接关系:

  • 强度递进、顺序依赖:前一个阶段通过,才有资格进入下一个。单元不过,集成大概率白测;集成不过,系统测试无从谈起;系统不过,验收免谈。
  • 粒度由小到大、视角从"代码"到"用户":单元/集成偏内部、多白盒,系统/验收偏整体、纯黑盒。

思考题 5:是不是只要单元测试 100% 通过,就可以直接交付上线,省掉集成与系统测试? 详解:绝对不行。单元测试只保证"每个模块单独正确",它几乎测不到模块之间的接口、组合后的整体行为,也无法验证性能、安全、兼容这些系统级属性。举一个真实例子:A 模块和 B 模块各自的单元测试都绿,但 A 传的日期格式是 yyyy-MM-dd,B 期待的是 yyyy/MM/dd,接口一接就崩。这样的问题只有集成测试才可能暴露。所以四个阶段的顺序是天然的防线,任一跳级,后面线上就会给你补课。

冒烟测试与回归测试

讲完主线的四个阶段,还有两个贯穿在整个流程里、你一定频繁听说的名字:冒烟测试和回归测试。严格说它们不属于"阶段",而是"按目的划分"的类型——或者说,它们是一种"附加在阶段之上、用来快速把关或复查"的测试动作。我把它们放在这一节讲,是因为它们和阶段关系最密切。

冒烟测试(Smoke Test,直译"冒烟")这个术语源自硬件行业:对一个硬件或硬件组件进行更改或修复后,直接给设备加电,如果没冒烟,说明这个组件通过了测试——没烧坏嘛。软件领域沿用这个词,指的是在把代码更改合入产品之前、或拿到一个新编译的版本之后,对基本功能做的一次最快速的验证。它的目的是确认软件的主要功能和核心流程正常,保证后面正式的测试不被"一个根本跑不起来"的版本阻塞。

实用场景很好懂:买电视先通电看能不能亮,买水杯先灌水看漏不漏。工作里,如果有个博客系统提测了,冒烟测试只要测"系统能不能成功打开、主流程(比如注册→登录→写文章→发布)能不能走通"就够。冒烟测试通常发生在开发人员提交版本给测试人员之后、正式系统测试之前:冒烟过了,才开始正式系统测试;冒烟不过,就把版本打回让开发修到过了再测。

回归测试(Regression Test)指修改了旧代码之后,重新进行测试,以确认修改没有引入新的错误、也没有导致其他代码出错。软件开发各阶段都会反复做回归测试,它在整个测试里占的工作量比重非常大——随着系统越来越庞大,回归成本也越来越高,所以选对回归策略(是全量回归、还是只回归受影响模块)对提升效率很关键。

回归测试主要由人工回归和自动化回归共同承担。它有个非常现实的心结:当你一遍又一遍地重复相同的测试时,会极其厌烦——尤其当大量回归必须手工完成时。所以回归测试是自动化最典型的应用场景:让机器去执行那些重复、一致、频繁变化的回归用例,才能保证每次都不漏、不错。为了支持多种回归策略,自动化测试工具应当是通用的、灵活的,以便适配不同的回归目标。

思考题 6:冒烟测试和回归测试都是"再跑一遍测试",它俩到底有什么区别? 详解:区别在"时机"和"目的"。 (1)时机不同:冒烟测试发生在接到新版本、正式开始系统测试之前,是对"这个版本能不能测"的最快把关;回归测试发生在任何一次代码改动之后(改 bug、加功能、合分支都会触发),是对"这次改动是否破坏已有功能"的复查。 (2)目的不同:冒烟回答"这个版本主流程通不通、值不值得往下测";回归回答"我这次改坏了什么没有"。 用生活的话说:冒烟是"新买的电视先通电看看亮不亮",回归是"我拆了电视换个零件,再开机确认没把别的功能拆坏"。两者目的不同,缺一不可。

按是否手工执行划分:手工测试与自动化测试

这一把尺子简单直接:测试是人在执行,还是机器在执行。

手工测试(Manual Testing)就是由人一个个地输入用例、观察结果,再判断是否通过。它属于比较原始但绝对必要的一个步骤——很多探索性、一次性的验证,还得靠人。

自动化测试(Automation Testing)就是在预设条件下运行系统或应用程序并评估结果。简单说,就是把以人为驱动的测试行为,转化为由机器执行的过程。自动化测试可以是功能测试自动化、性能测试自动化、安全测试自动化等;按照测试对象来分,又可分为接口测试、UI 测试等。这里先埋一句话,等学到自动化再展开:接口测试的 ROI(Return On Investment,投入产出比)通常比 UI 测试更高——因为接口测试更稳定、跑得快、维护成本低,UI 自动化则相对脆弱、容易"改个样式就全红"。

两者优缺点对比:

维度自动化测试手工测试
优点节省长期成本;提高测试人员执行效率;保障软件质量一致对技术人员要求没有自动化那么高;可以进行发散性测试,探索未知问题
缺点对测试人员技术要求较高;不擅长发散性、探索性测试;初次编写成本高效率低;重复执行易疲惫、易遗漏;人员、时间成本相对更高

这节的实操心法:自动化不是要"取代"手工,而是把"该重复的重复交给机器、该动脑的发散留给人"。冒烟、回归这类高频重复场景最适合自动化;探索性、界面观感、一次性验证这类更需要人。判断要不要自动化的标准也很朴素——用例够不够稳定、要不要反复跑。

按实施组织划分:α 测试、β 测试与第三方测试

这一把尺子回答的是:由谁、在哪里执行这些测试。 大型通用软件正式发布前,通常要依次经历 α 和 β 两类测试。

α 测试(Alpha Testing)

α 测试也叫内测或 a 测,指公司内部的用户在模拟实际操作环境下进行的测试。它的目的是评价软件产品的 FLURPS——即功能(Functionality)、可用性(Usability)、可靠性(Reliability)、性能(Performance)和支持(Support)。这里有个硬规矩要注意:α 测试不能由程序员或测试员自己完成,而是由内部的"非开发、非测试"人员来当"准用户"。

β 测试(Beta Testing)

β 测试也叫公测或 b 测,指由软件的最终用户在一个或多个真实使用场所进行。换句话说,β 测试是找一批"真实的普通用户",让他们在真实环境里随便用这款软件,目的是通过完全不可控的真实使用,暴露那一系列开发方想不到的问题。通常会给用户发送邀请码,邀请他们参与测试。

α 测试与 β 测试的区别

教材里通常给一张对比表,我总结成四条:

对比维度α 测试β 测试
测试场所公司内部用户环境
环境可控性环境受开发方控制,用户数量较少、时间比较集中环境不受开发方控制,用户数量较多、时间不集中
执行时机通常是 α 测试通过后,再进行 β 测试在 α 之后
持续时间较短较长(用户分散、回收周期长)

(这里补一句对用户更友好的提示:α/β 的实质是"把测试放到不断接近真实的环境中",并在发布前分类收集真实反馈。α 是"仓库里模拟用户",β 是"放出去真刀真枪"。)

第三方测试

第三方软件测试指由独立的第三方公司或组织进行的软件测试活动。它和开发方、使用方都无利益关系,由"中立裁判"来出品质量结论,因而具有客观性和公信力。常见动机有:确保软件质量、节省自建测试团队的成本、并让软件尽快上线。行业里对此有成熟的服务方,如果你有真实项目需要出具具备法律或商务效力的测试报告,通常就是找这类第三方机构来做。

按测试地域划分:国际化测试与本地化测试

最后一把尺子,处理的是"软件要卖到全世界"这种场景。按测试地域,一般会分为国际化测试和本地化测试。

国际化测试(Internationalization,缩写常常写成 i18n)通俗讲,就是测试软件在不同语言和地区能否正常工作。它需要关注的特性包括:布局、时间、日期、数字格式、货币、机器型号等。换句话说,你要验证"这个软件如果给讲英语的人、讲阿拉伯语(从右往左排版)的人用,界面是不是还成立、时间日期货币格式会不会乱"。

本地化测试(Localization,缩写常写成 l10n)则针对某一个特定目标地区做适配验证——确保时间格式用当地习惯、货币符号用当地币种、语言文案完全本地化、习惯和法规符合当地要求。其实前面讲的所有测试,只要是针对某个具体地区做的,都可以归入"本地测试 / 本地化测试"的大筐。

(顺带一提:i18n 是 internationalization 首字母 i + n 之间 18 个字母 + n 的缩写,l10n 同理。这个命名小知识,面试或看框架文档时常会碰到。)

综合自测:把这八把尺子串起来

走到这里,八把"尺子"都见过了。下面这几道综合题,比前面每节的小思考题更偏"串知识点",我每题都附了详解,你做完再对答案。

自测 1:请判断:对"博客系统的登录页面做一次文字检查,看设计稿里的按钮文案和颜色是否一致",这属于静态还是动态测试?为什么?

详解:属于静态测试。因为这一步"不运行程序",只是对着界面/设计稿做肉眼比对和分析,程序本身没有执行。判断静态/动态的唯一标准就是"是否运行了被测程序"——这里没运行,所以是静态。这个例子提醒你:界面测试里面装的内容也是有动静之分的,别因为"它长得像界面测试"就以为一定是动态。

自测 2:开发小明在单元测试阶段重点用白盒测试,测试小红在系统测试阶段主要用黑盒测试。请用"只看黑白盒本身"的标准,分别说说小明的白盒与小红黑盒的本质区别。

详解:两者的本质差别在于"是否关注程序内部结构和实现"。白盒测试要基于程序内部逻辑结构设计用例,去覆盖语句、分支、条件、路径;黑盒测试完全不看内部结构,只从外部根据需求和输入输出判断功能是否正确。这正是"盒子透不透明"的比喻所在——白盒的"盒子"对开发者透明,黑盒的"盒子"对用户不透明。

自测 3:某系统全年(按 525600 分钟计)只允许停机不超过 53 分钟。请判断它承诺的可用性大约是多少个 9?并写出换算过程。

详解:53 分钟对应 99.99%(4 个 9)。计算:52.56 ÷ 525600 ≈ 0.0001,即允许故障比率为 0.0001(0.01%),可用性 = 1 - 0.0001 = 0.9999 = 99.99%。所以"全年停机不超过一小时级别"通常就是 4 个 9 的承诺;若要求 5 个 9,全年只允许约 5.26 分钟故障。这道题考的就是"N 个 9"与"年停机时间"的双向换算。

自测 4:解释"单元测试通过 ≠ 可以直接上生产"。至少说出两条理由,并各配一个例子。

详解:至少有两个层面的理由。(1)粒度:单元测试只验证单模块,不验证模块间的接口与集成行为。例:A 与 B 单测都绿,但 A 输出的 JSON 字段命名和 B 期望的不一致,联调即崩。(2)属性:单元测试基本不覆盖性能、安全、兼容、可靠性等系统级质量属性。例:单测全绿,但并发 1000 时数据库连接池被打满导致超时,这是单元测试测不出来的。再加上验收这一环:最终要以用户、需求方的验收结果为准。所以完整链条:单元 → 集成 → 系统 → 验收,一步都不能省。

自测 5:新版本提测,团队先快速跑了"能打开、能登录、能发布一篇文章",然后才开始正式系统测试。这跑的是什么测试?如果其中"发布文章"挂了,团队应该怎么办?

详解:这是冒烟测试。冒烟测试就是在正式系统测试之前,对新编译版本做的最快速主流程把关。如果"发布文章"挂了,说明这个版本冒烟没过,正确应对是:把版本打回给开发修复,直到冒烟测试通过后,再启动正式的系统测试。千万不要带着一个主流程都跑不通的版本硬往下测,那样测试环境一上来就被阻塞,浪费所有人的时间。

该收尾了

到这里,测试分类这棵大树的全貌就立起来了。我们认识了八把不同的"尺子":按目标(界面/功能/性能/可靠/安全/易用)、按是否运行(静态/动态)、按方法(黑盒/白盒/灰盒)、按阶段(单元/集成/系统/验收)、按目的(冒烟/回归)、按是否手工(手工/自动化)、按实施组织(α/β/第三方)、按地域(国际化/本地化)。

站在分类之外,我想送你三句贯穿始终的心法:

第一,分类是正交的。一个测试可以同时属于"动态""黑盒""系统""手工"等好几个类别——因为它们在不同维度上。别拿"它既算 A 又算 B"当矛盾,那恰恰说明你看全了维度。

第二,别为了分类而分类。分类的最终目的是把测试变得可管理、可执行、可验收。什么时候用哪把尺子,取决于"当前要解决什么问题"。

第三,边界与坑比定义更值钱。各阶段不能跳级衔接、黑盒白盒不是指程序颜色、静态动态看"跑没跑程序"、条件覆盖不一定满足判定覆盖、冒烟看"值不值得测"回归看"改坏没有"——这些反直觉的边界点,往往才是面试和实战里真正卡人的地方。

如果你正好准备入门测试,不妨从今天这一篇开始:试着把自己手头(或同事)正在开发的一个小功能,用八把尺子各切一刀,写出它在这个维度下属于哪种测试。当你真做了一遍,这棵分类大树才真正长进了你脑子里。

我们下一篇文章会钻进某一类测试的内部——去看看黑盒测试那套"等价类、边界值、判定表"到底怎么用。准备好了吗?