-调试 Lecture 7 - Debugging

官方信息技术老师·11 页·深入(追求细节与边界)·0 次浏览·3 天前
调试策略断点机制调用栈边界场景

-调试 Lecture 7 - Debugg

掌握断点机制、调用栈追踪与边界 Bug 的排查逻辑

按 空格/→ 演示下一步

1 / 11 页

全部页面点击任意一页,跳回舞台从这页播放

调试策略断点机制调用栈边界场景

-调试 Lecture 7 - Debugg

掌握断点机制、调用栈追踪与边界 Bug 的排查逻辑

1第 1 页 · -调试 Lecture 7 - Debugg

Lecture 7 Introduction

你写的代码逻辑看着对,结果却错——说明你的「心智模型」和程序的「实际行为」之间有缝隙。调试,本质上是在这条缝隙上架桥。

调试的本质
不是「让代码跑通」,而是定位「期望」与「实际」偏差的根因
科学方法循环
观察现象 → 形成假设 → 设计实验 → 验证 → 修正假设
调试 ≠ 测试
测试判定「是否有 bug」;调试回答「bug 在哪、为什么」
工具层次
printf/日志 → IDE 断点 → gdb/PDB → 静态分析与 sanitizer
边界与难点
并发 bug、Heisenbug(观测时消失)需超越常规思路
医生看诊对应 →调试

主诉=症状;查体=日志;化验=单步;诊断=定位根因;治疗=修复

2第 2 页 · Lecture 7 Introduction

Testing and Debugging

写完作业通读一遍检查——这是 testing;发现第3题算错了,翻回去找错在哪——这是 debugging。两者谁也替不了谁。

Testing 测试
主动运行程序并验证输出是否符合预期
Debugging 调试
从已知失败症状出发,反向定位并消除根因
测试层次
单元→集成→系统→验收,越往上越接近真实场景
调试手段
日志打印、断点暂停、二分回溯、橡皮鸭
互补闭环
测试发现问题、调试解决问题,二者缺一不可
质检员与维修工对应 →Testing 与 Debugging

质检员按标准主动扫描找缺陷,维修工凭工单根治故障

3第 3 页 · Testing and Debugging

Test Suites

上页我们讲了 testing 和 debugging 的关系——测试是为了尽早抓 bug。但一个真实项目动辄几百上千个测试用例,怎么组织?怎么挑着跑?这就要用「测试套件」。

套件定义
把相关测试用例归为一组,作为一个单元来运行和管理的集合
层级嵌套
套件可包含子套件,形成树形结构(如「登录模块 > 密码错误场景」)
共享设置
套件级统一执行 setup/teardown,进入前自动准备环境,退出后自动清理
选择执行
通过 tag/标签筛选,只跑特定子集(如只跑 smoke 冒烟测试)
结果聚合
报告按套件汇总,一眼看出「哪个模块挂了」
体检套餐对应 →Test Suite

基础套餐/心脏专项 对应 不同套件;多项检查共享前后准备(空腹、登记);结果按套餐汇总

4第 4 页 · Test Suites

Black-Box Testing

上几页我们谈了「先写测试再写代码」以及测试套件的组织。但每个测试用例具体怎么写?有一种视角:你完全不知道程序内部长什么样,只管喂输入、看输出对不对——这就是黑盒测试。

关注行为
只关心输入与输出是否符合规格要求,无视代码内部如何实现
不知内部
测试者无需掌握源码、设计结构或算法逻辑,把自己当作用户
典型技术
等价类划分、边界值分析、错误猜测、判定表驱动
适用场景
系统级验收测试、独立 QA 团队需求验证、用户场景模拟
边界与局限
难以触及内部逻辑漏洞,需与白盒测试互补使用
顾客点菜品尝对应 →黑盒测试

顾客只看菜品对不对味,不知后厨怎么做;测试者只看输入输出对不对,不知代码怎么实现

5第 5 页 · Black-Box Testing

Glass-Box Testing

上一页黑盒测试把程序当成密封的盒子,只看输入输出。玻璃盒测试反其道而行——打开盒子看代码,按内部的脉络去决定怎么测。

定义
基于源代码与内部结构的测试,又称白盒或结构测试
覆盖准则
语句、判定、条件、路径覆盖——衡量测得有多彻底
代码可见
测试者必须能读源码,理清控制流与数据流
擅长场景
揪出死代码、漏分支、隐性的边界与逻辑错误
局限
找不到「根本没写的需求」;代码量大时代价高
拆发动机逐件检查对应 →玻璃盒测试看代码

试驾=黑盒只知能否跑;拆开=白盒逐个零件验

6第 6 页 · Glass-Box Testing

Test Drivers and Stubs

白盒测试能看清一个模块的内部逻辑,可它只回答『这段代码对不对』,回答不了『它和别的模块拼起来对不对』。模块还没拼好时,怎么单独测它?

Stub 桩
替身被调模块:A 调用 B,B 还没写好,桩就用预设的假数据假装回应。
Driver 驱动
替身调用模块:B 没人调它来测,驱动就充当调用者,喂输入、收输出。
增量集成测试
自顶向下用桩、自底向上用驱动,逐步替换真实模块,每步都能跑能验。
桩 vs Mock
桩只回预设值不管调用方式;Mock 还会断言『你真的这样调我吗』。
发动机台架测试对应 →模块隔离测试

台架(驱动)供油读转速;假变速箱(桩)假装被带动;发动机就是被测模块。

7第 7 页 · Test Drivers and Stubs

Debugging

测试告诉你「这里出了问题」,但代码里有百万行——真正困难的是:问题在哪?为什么发生?怎么改?

调试定义
定位并消除代码中导致测试失败或异常行为的根本原因
与测试区别
测试发现问题,调试解决问题;测试证伪,调试修因
调试循环
复现 → 定位 → 修复 → 验证,环环相扣缺一不可
常用手段
打印日志、断点步进、二分定位、静态分析、橡皮鸭
海森堡虫
Heisenbug——观测行为本身改变了 bug 的触发条件
侦探破案对应 →调试

案发现场=测试失败,证词=日志线索,侦探要从结果回溯到作案手法

8第 8 页 · Debugging

Debugging as Search

你已经知道调试要系统化。但还有更本质的视角:调试其实是一个搜索问题——在一个由症状、位置、原因组合成的巨大空间里,把搜索范围一步步压到唯一那条正确路径上。

搜索的三维空间
症状位置 × 错误成因 × 候选修法,三者的笛卡尔积构成整个搜索空间
两个搜索维度
先找位置(代码哪一行)再找原因(为什么错),两步不可跳跃
搜索空间爆炸
中等项目可能有 10⁴×10² 种假设组合,靠穷举调试根本不现实
剪枝策略
二分分裂假设区间、追踪最近变更、利用症状-成因邻近性、设断点验证
高手=强剪枝
调试效率差距的核心不在工具,而在快速排除错误分支、把范围量级压下去的能力
侦探破案抽丝剥茧对应 →调试的搜索过程

现场线索=程序症状,嫌疑人=可能成因,推理排查=假设检验,锁定真凶=定位bug

$$T_{\text{binary}} = O(\log_2 N) \quad \text{vs} \quad T_{\text{naive}} = O(N)$$
9第 9 页 · Debugging as Search

本节要点

  • 测试找 Bug,调试找根因,方向不同
  • 测试要兼顾黑盒规格与白盒路径
  • 桩与驱动让不可执行的代码也能测
  • 调试本质是带证据的搜索,不是猜
  • 先定位根因再改代码,别急着动刀
延伸主题:断点与单步执行日志与插桩调试变异测试检验用例质量
10第 10 页 · 本节要点

课后思考

先自己思考再对照参考答案——三个问题分别考察回顾、应用与迁移。

1为什么调试常被看作'搜索'问题?调试者具体在搜索什么?

参考答案调试搜的不是'bug',而是程序状态空间——哪些代码路径、输入数据可能产生这个症状。结合二分定位和症状、堆栈、日志等线索缩小范围。

2若一个黑盒测试间歇性失败,如何决定继续加黑盒测试还是改用白盒测试来定位缺陷?

参考答案间歇失败往往根植于时序、资源或并发等非确定性因素。先多跑黑盒复现,若仍无法稳定触发,则需白盒插桩(print、assert、断点)观察内部状态定位触发条件。

3测试中用桩模块代替未完成的底层模块——调试时能否也'替换'被怀疑出错的模块为已知正确的版本,以此定位问题?

参考答案可以,对照替换是合法调试手段。前提是接口、状态、副作用与原模块一致;症状消失则定位成功,否则继续上溯。

11第 11 页 · 课后思考