[{"data":1,"prerenderedAt":1471},["ShallowReactive",2],{"post:\u002Fposts\u002Fswdesign\u002Fsw-design-notes-14":3,"posts:ordered":1169},{"id":4,"title":5,"body":6,"category":1151,"cover":1152,"date":1153,"description":1154,"extension":1155,"featured":1156,"meta":1157,"navigation":1158,"path":1159,"seo":1160,"series":1161,"seriesOrder":1162,"stem":1163,"tags":1164,"__hash__":1168},"posts\u002Fposts\u002FswDesign\u002Fsw-design-notes-14.md","软件分析与设计笔记 14：软件测试：层级、方法与等价类划分",{"type":7,"value":8,"toc":1117},"minimark",[9,16,25,30,33,58,61,78,88,92,97,100,111,118,173,180,187,191,194,208,211,222,226,233,237,329,336,340,347,354,358,362,368,374,377,406,413,417,420,430,434,438,449,453,456,462,470,484,490,498,504,507,518,522,548,551,630,637,641,645,648,678,685,689,700,704,708,715,734,740,746,749,773,776,836,839],[10,11,12],"blockquote",{},[13,14,15],"p",{},"这是我在学习软件分析与设计时整理的系列笔记。概念部分是我按自己的理解写的，示例与图示都是自己编、自己画的。理解有偏差的地方欢迎指出。",[10,17,18],{},[13,19,20,24],{},[21,22,23],"strong",{},"贯穿案例","：片区家电维修派单系统。这一篇讲怎么证明「它真的做到了」—— 重点是 27 篇考试里那道 4 分的测试用例题。",[26,27,29],"h2",{"id":28},"_0-我们测过了那测的是什么","0 「我们测过了」——那测的是什么",[13,31,32],{},"交付前总会听到这句话。但接着问三个问题，答案往往就不那么确定了：",[34,35,36,44,51],"ul",{},[37,38,39,40,43],"li",{},"测了哪些",[21,41,42],{},"层级","？只测了单个方法能跑，还是连起来也测过？",[37,45,46,47,50],{},"用了哪些",[21,48,49],{},"方法","？只测了正常输入，还是异常输入也试过？",[37,52,53,54,57],{},"依据什么",[21,55,56],{},"数据","？随手试的，还是照着约束设计出来的？",[13,59,60],{},"放到贯穿案例里试一次。如果你做了「申请配件」这个功能，随口说「测过了」，那下面这些情况测了吗：",[34,62,63,66,69,72,75],{},[37,64,65],{},"数量填 0 会怎样？",[37,67,68],{},"数量填 21（上限是 20）会怎样？",[37,70,71],{},"工单状态不是「进行中」时提交会怎样？",[37,73,74],{},"供应商接口超时了会怎样？",[37,76,77],{},"两个工程师同时申请同一个配件（只剩 1 个）会怎样？",[13,79,80,83,84,87],{},[21,81,82],{},"这五条里只要有一条没测，「测过了」就不成立","。这一篇要把这三件事讲清楚。先建立一个基本认识：",[21,85,86],{},"测试的目的是发现缺陷（defect），不是证明没有缺陷","。测试通过只能说明「这组输入下没出问题」，不能说明系统是对的。",[26,89,91],{"id":90},"_1-为什么只测不够评审与测试","1 为什么只测不够：评审与测试",[93,94,96],"h3",{"id":95},"_11-评审与测试的分工","1.1 评审与测试的分工",[13,98,99],{},"有一类缺陷，光靠运行程序是发现不了的：",[34,101,102,105,108],{},[37,103,104],{},"需求漏写了一个分支 —— 程序按现在的需求跑得很好。本例：「工程师请假了，已派的单怎么办」需求里没写；",[37,106,107],{},"设计里两个模块职责重叠 —— 代码能跑，只是以后改起来很痛苦；",[37,109,110],{},"文档里的术语前后不一致 —— 甚至没有代码。",[13,112,113,114,117],{},"对付这类问题要用",[21,115,116],{},"评审（review）","。它和测试的分工是：",[119,120,121,136],"table",{},[122,123,124],"thead",{},[125,126,127,130,133],"tr",{},[128,129],"th",{},[128,131,132],{},"评审",[128,134,135],{},"测试",[137,138,139,151,162],"tbody",{},[125,140,141,145,148],{},[142,143,144],"td",{},"是否运行程序",[142,146,147],{},"否，属于静态测试（static testing）",[142,149,150],{},"是，属于动态测试（dynamic testing）",[125,152,153,156,159],{},[142,154,155],{},"主要发现",[142,157,158],{},"需求、设计、文档层面的缺陷",[142,160,161],{},"实现层面的缺陷",[125,163,164,167,170],{},[142,165,166],{},"时机",[142,168,169],{},"越早越好",[142,171,172],{},"代码可运行之后",[13,174,175,176,179],{},"评审之所以重要，是因为",[21,177,178],{},"缺陷发现得越晚，修复成本越高"," —— 在需求阶段改一句话，和在测试阶段改一句话，代价差一个数量级。",[13,181,182,183,186],{},"本例里有个具体的账：如果「工程师请假怎么办」这个缺口没在需求评审时发现，等到上线后才发现，改的不只是一个分支，而是",[21,184,185],{},"工单状态机要加一个状态、还要能回退"," —— 那是两三天的活。",[93,188,190],{"id":189},"_12-验证与确认","1.2 验证与确认",[13,192,193],{},"再补充两个容易混的词：",[34,195,196,202],{},[37,197,198,201],{},[21,199,200],{},"验证（verification）"," —— 我们有没有正确地构建产品？（符合规格说明）",[37,203,204,207],{},[21,205,206],{},"确认（validation）"," —— 我们有没有构建正确的产品？（符合用户需要）",[13,209,210],{},"前者是「照着图纸做对了吗」，后者是「图纸本身对吗」。",[13,212,213,214,217,218,221],{},"放到本例：「查询响应 2 秒内完成」测过了 → 这是",[21,215,216],{},"验证","。但客服小林用了之后还是抱怨「找不到要派的单」→ 说明",[21,219,220],{},"确认","没过，因为图纸本身画错了（她需要的不是快，而是筛选）。",[26,223,225],{"id":224},"_2-五个测试层级","2 五个测试层级",[13,227,228],{},[229,230],"img",{"alt":231,"src":232},"测试层级的金字塔与两类测试方法：从下到上是单元测试、集成测试、系统测试、验收测试，越往上覆盖范围越大、发现问题代价越高；下方对比白盒测试与黑盒测试，以及黑盒测试选输入用的等价类划分","\u002Fimages\u002Fsw-design-notes-14-testing-levels.svg",[93,234,236],{"id":235},"_21-五个层级各自验证什么","2.1 五个层级各自验证什么",[119,238,239,254],{},[122,240,241],{},[125,242,243,245,248,251],{},[128,244,42],{},[128,246,247],{},"验证什么",[128,249,250],{},"谁来测",[128,252,253],{},"本例的例子",[137,255,256,274,288,301,315],{},[125,257,258,261,264,267],{},[142,259,260],{},"单元测试（unit testing）",[142,262,263],{},"单个方法或类的行为",[142,265,266],{},"开发",[142,268,269,273],{},[270,271,272],"code",{},"PartUsage.calculateAmount()"," 算得对不对",[125,275,276,279,282,285],{},[142,277,278],{},"集成测试（integration testing）",[142,280,281],{},"模块之间的接口与协作",[142,283,284],{},"开发 \u002F 测试",[142,286,287],{},"申请配件时，库存扣减与出库单创建是不是同一个事务",[125,289,290,293,296,298],{},[142,291,292],{},"系统测试（system testing）",[142,294,295],{},"整个系统的功能与非功能表现",[142,297,135],{},[142,299,300],{},"早高峰 300 人同时在线，派单是否还成功",[125,302,303,306,309,312],{},[142,304,305],{},"验收测试（user acceptance testing, UAT）",[142,307,308],{},"是否满足业务需要",[142,310,311],{},"用户 \u002F 业务方",[142,313,314],{},"小林真的能用它派完一天 80 单吗",[125,316,317,320,323,326],{},[142,318,319],{},"部署测试（deployment testing）",[142,321,322],{},"在真实环境里能否正常安装运行",[142,324,325],{},"运维 \u002F 测试",[142,327,328],{},"工程师的手机在 4G 弱网下能不能回填",[13,330,331,332,335],{},"第四行特别值得注意。",[21,333,334],{},"小林是唯一能判断 UAT 过没过的人","，因为她才是那个一天派 80 单的人。如果让没派过单的人去验收，通过与否没有意义。",[93,337,339],{"id":338},"_22-金字塔形状说明了什么","2.2 金字塔形状说明了什么",[13,341,342,343,346],{},"金字塔的形状本身就是结论：",[21,344,345],{},"底层的测试应该最多、最便宜、跑得最勤；上层的测试少而贵","。一个健康项目的表现是「大部分缺陷在单元测试阶段就被拦住」。反过来，如果缺陷总是到 UAT 才暴露，那就说明底层测试做得太少。",[13,348,349,350,353],{},"本例里最容易缺底层的正是",[21,351,352],{},"金额与时长的计算","。配件金额、上门时长、超时判定这三块逻辑如果没单元测试，等到 UAT 才发现算错，改起来要重跑一整套流程。",[26,355,357],{"id":356},"_3-白盒与黑盒","3 白盒与黑盒",[93,359,361],{"id":360},"_31-白盒与黑盒的差别","3.1 白盒与黑盒的差别",[13,363,364,367],{},[21,365,366],{},"白盒测试（white box testing）"," 看内部逻辑：语句有没有都执行到、分支有没有都走到。它适合验证「代码写对了没有」，实施者需要懂代码。",[13,369,370,373],{},[21,371,372],{},"黑盒测试（black box testing）"," 只看输入和输出，不看实现。它适合从使用者角度验证「行为对不对」，业务方也能参与。",[13,375,376],{},"本例里「申请配件」这段逻辑，两种测法的分工很清楚：",[119,378,379,388],{},[122,380,381],{},[125,382,383,385],{},[128,384],{},[128,386,387],{},"测什么",[137,389,390,398],{},[125,391,392,395],{},[142,393,394],{},"白盒",[142,396,397],{},"「库存充足」和「库存不足」两个分支都走到了吗？异常处理有代码覆盖吗？",[125,399,400,403],{},[142,401,402],{},"黑盒",[142,404,405],{},"数量填 0 \u002F 21 \u002F 5，结果对不对？不关心代码怎么写的",[13,407,408,409,412],{},"两者有一个共同局限：",[21,410,411],{},"都只能覆盖到你想到的情况","。想不出某个输入组合，就永远测不到它。所以测试用例的设计方法比测试工具更重要。",[93,414,416],{"id":415},"_32-测试覆盖率的分量","3.2 测试覆盖率的分量",[13,418,419],{},"这也解释了测试覆盖率（test coverage）这个指标的分量：它衡量你设计的用例触及了多少代码或需求。但要注意，覆盖率只说明「被执行过」，不等于「被验证正确」—— 覆盖率 100% 也不能保证没有缺陷。",[13,421,422,423,426,427],{},"一个具体的反例：某段代码 ",[270,424,425],{},"if (qty > 20) return error;"," —— 你用 qty=25 测了一次，覆盖率是 100%，但你没测 qty=20（边界，应该通过）。",[21,428,429],{},"代码跑过了，但不代表跑对了。",[26,431,433],{"id":432},"_4-把无限输入变成有限用例等价类划分","4 把无限输入变成有限用例：等价类划分",[93,435,437],{"id":436},"_41-它的基本思路","4.1 它的基本思路",[13,439,440,441,444,445,448],{},"一个输入框能接受的值是无限的，不可能一个个试。",[21,442,443],{},"等价类划分（equivalence class partitioning）"," 的思路是：把输入空间划成若干段，",[21,446,447],{},"每段里任意一个值的效果是一样的"," —— 那每段测一个代表值就够了。",[93,450,452],{"id":451},"_42-三种划分方式","4.2 三种划分方式",[13,454,455],{},"常见的三种划分方式：",[13,457,458,461],{},[21,459,460],{},"① 按值域划分。"," 本例「申请数量」允许 1–20，那么：",[34,463,464,467],{},[37,465,466],{},"有效等价类：1–20 内取一个（例如 12）；",[37,468,469],{},"无效等价类：小于 1（例如 0）、大于 20（例如 21）。",[13,471,472,475,476,479,480,483],{},[21,473,474],{},"注意有效类里最好取三个：边界值 1、中间值 12、边界值 20","。因为 ",[270,477,478],{},">"," 写成 ",[270,481,482],{},">="," 这类错误只在边界暴露。",[13,485,486,489],{},[21,487,488],{},"② 按输入个数划分。"," 例如一张工单至少要有一条配件消耗：",[34,491,492,495],{},[37,493,494],{},"有效：1 条、5 条；",[37,496,497],{},"无效：0 条、极大条数（例如 200 条）。",[13,499,500,503],{},[21,501,502],{},"③ 按异常条件（exception condition）划分。"," 空值、超长字符串、非法字符、并发提交、重复提交。",[13,505,506],{},"本例里第 ③ 类最容易被漏掉，但它恰恰最真实：",[34,508,509,512,515],{},[37,510,511],{},"同一张工单被两个客服同时派给不同的人；",[37,513,514],{},"工程师连点两次「提交申请」（网络慢，他以为没反应）；",[37,516,517],{},"配件编号输入了不存在的型号。",[93,519,521],{"id":520},"_43-边界值分析","4.3 边界值分析",[13,523,524,525,528,529,479,532,535,536,539,540,543,544,547],{},"在值域的边界上还有个配套技巧 —— ",[21,526,527],{},"边界值分析（boundary value analysis）","：出错往往发生在边界上（",[270,530,531],{},"\u003C",[270,533,534],{},"\u003C=","、",[270,537,538],{},"0"," 和 ",[270,541,542],{},"1"," 的差别），所以在每个等价类的",[21,545,546],{},"边界上下","各取一个值，命中率特别高。",[13,549,550],{},"本例「数量 1–20」的完整取法：",[119,552,553,566],{},[122,554,555],{},[125,556,557,560,563],{},[128,558,559],{},"值",[128,561,562],{},"属于",[128,564,565],{},"期望结果",[137,567,568,578,594,604,620],{},[125,569,570,572,575],{},[142,571,538],{},[142,573,574],{},"下边界外",[142,576,577],{},"报错",[125,579,580,584,589],{},[142,581,582],{},[21,583,542],{},[142,585,586],{},[21,587,588],{},"下边界上",[142,590,591],{},[21,592,593],{},"通过",[125,595,596,599,602],{},[142,597,598],{},"12",[142,600,601],{},"中间",[142,603,593],{},[125,605,606,611,616],{},[142,607,608],{},[21,609,610],{},"20",[142,612,613],{},[21,614,615],{},"上边界上",[142,617,618],{},[21,619,593],{},[125,621,622,625,628],{},[142,623,624],{},"21",[142,626,627],{},"上边界外",[142,629,577],{},[13,631,632,633,636],{},"五个值，覆盖三件事：两个边界内外、一个中间值。",[21,634,635],{},"这比随手填一个 999 有价值得多"," —— 因为 999 和 21 是同一个等价类，多测一遍没有新信息。",[26,638,640],{"id":639},"_5-测试准备四样东西","5 测试准备：四样东西",[93,642,644],{"id":643},"_51-四样东西","5.1 四样东西",[13,646,647],{},"设计好用例之后，还需要准备：",[34,649,650,656,662,672],{},[37,651,652,655],{},[21,653,654],{},"测试用例（test case）"," —— 输入、预期输出、前置条件，三者缺一不可；",[37,657,658,661],{},[21,659,660],{},"测试数据（test data）"," —— 符合用例要求的实际数据。边界数据要专门准备（例如「刚好 20 个」）；",[37,663,664,667,668,671],{},[21,665,666],{},"测试环境（test environment）"," —— 与生产尽量接近的环境。",[21,669,670],{},"环境不一致造成的假问题，非常消耗排查时间","；",[37,673,674,677],{},[21,675,676],{},"测试步骤（test procedure）"," —— 用例的执行顺序。有些用例有先后依赖（先建工单，才能测申请配件）。",[13,679,680,681,684],{},"第二样本例里有个坑：",[21,682,683],{},"测「库存不足」需要一个库存真的不足的配件","。如果你在测试环境里用的是充足的数据，这条用例根本跑不到那个分支。所以测试数据要专门造「只剩 1 个」的库存。",[93,686,688],{"id":687},"_52-功能测试与非功能测试","5.2 功能测试与非功能测试",[13,690,691,692,695,696,699],{},"测试分两类：",[21,693,694],{},"功能测试（functional testing）"," 验证系统做什么，",[21,697,698],{},"非功能测试"," 验证做得多好（性能、安全、可靠性，见笔记 15）。",[26,701,703],{"id":702},"_6-测试在敏捷里怎么放","6 测试在敏捷里怎么放",[93,705,707],{"id":706},"_61-测试成为迭代的一部分","6.1 测试成为迭代的一部分",[13,709,710,711,714],{},"迭代式开发里，测试不再是项目末尾的一个阶段，而是",[21,712,713],{},"每个迭代的一部分","，也就是所谓的敏捷测试周期（Agile testing lifecycle）：",[34,716,717,720,727],{},[37,718,719],{},"单元测试是「完成定义」的一部分（笔记 03）—— 没测试就不算完成；",[37,721,722,723,726],{},"每次改动后都要跑一遍已有的测试，确认没弄坏别的东西，这叫",[21,724,725],{},"回归测试（regression testing）","。它通常靠自动化来保证；",[37,728,729,730,733],{},"测试用例的来源变了：以前从需求文档推，现在直接从",[21,731,732],{},"验收标准","推 —— 一条验收标准对应至少一个测试用例。",[13,735,736,737],{},"这个变化带来一个好处：",[21,738,739],{},"如果一条验收标准写不出对应的测试用例，那多半是这条需求本身没写清楚。",[13,741,742,743],{},"本例里 07 篇那条验收标准「每个时段标注空闲\u002F已排\u002F满档三态」，直接就能翻出三条测试用例（各验一态）+ 一条边界（无排班数据）。",[21,744,745],{},"翻不出来就说明标准没写清。",[26,747,748],{"id":748},"本节自测",[750,751,752,755,758,761,764,767,770],"ol",{},[37,753,754],{},"验证（verification）和确认（validation）的区别是什么？",[37,756,757],{},"哪些缺陷只能靠评审发现，测试发现不了？举出本例中的一个。",[37,759,760],{},"测试金字塔为什么是「下宽上窄」？如果缺陷老是在 UAT 才暴露，说明什么？",[37,762,763],{},"某字段允许 1–120，用等价类划分和边界值分析，至少要准备哪些测试数据？",[37,765,766],{},"为什么说「写不出测试用例的验收标准，说明需求有问题」？",[37,768,769],{},"本例「申请配件」的数量上限是 20，为什么只测 21 不够，还要测 20？",[37,771,772],{},"本例要测「库存不足」这个分支，测试数据上有什么特别要求？",[93,774,775],{"id":775},"参考答案",[750,777,778,786,793,804,811,818,829],{},[37,779,780,782,783,785],{},[21,781,216],{},"是「照着图纸做对了吗」（符合规格），",[21,784,220],{},"是「图纸本身对吗」（符合用户需要）。本例：「2 秒内完成」测过是验证；但小林仍抱怨「找不到要派的单」说明确认没过（§1.2）。",[37,787,788,789,792],{},"需求、设计、文档层面的缺陷。本例中最典型的是「",[21,790,791],{},"工程师请假了，已派的单怎么办","」—— 需求里没写，程序按现有需求跑得很好，测不出问题（§1.1）。",[37,794,795,796,799,800,803],{},"因为底层测试",[21,797,798],{},"多、便宜、跑得勤","，上层少而贵；健康项目的缺陷大部分在单元层被拦住。若缺陷总在 UAT 暴露，说明",[21,801,802],{},"底层测试做得太少","（§2.2）。",[37,805,806,807,810],{},"至少：",[21,808,809],{},"0（下边界外）、1（下边界上）、60（中间）、120（上边界上）、121（上边界外）"," —— 五个值，覆盖两个边界内外加一个中间值（§4.3）。",[37,812,813,814,817],{},"因为验收标准与测试用例是",[21,815,816],{},"一对一推导","关系。一条标准翻不出用例，说明它本身含糊、没有可观测的结果（§6.1）。",[37,819,820,821,828],{},"因为 ",[21,822,823,479,825,827],{},[270,824,478],{},[270,826,482],{}," 这类错误只在边界暴露","。测 21 只能证明「超上限会报错」，测 20 才能证明「正好到上限时应通过」（§4.3、§3.2）。",[37,830,831,832,835],{},"必须",[21,833,834],{},"专门造一个库存不足的配件","（例如只剩 1 个）。如果测试数据里库存都充足，这条用例根本跑不到那个分支（§5.1）。",[26,837,838],{"id":838},"术语表",[119,840,841,854],{},[122,842,843],{},[125,844,845,848,851],{},[128,846,847],{},"术语",[128,849,850],{},"中文",[128,852,853],{},"一句话解释",[137,855,856,866,876,886,897,908,919,930,941,952,963,974,985,996,1007,1018,1029,1040,1051,1062,1073,1084,1095,1106],{},[125,857,858,861,863],{},[142,859,860],{},"verification",[142,862,216],{},[142,864,865],{},"是否按规定构建了产品，对着规格检查",[125,867,868,871,873],{},[142,869,870],{},"validation",[142,872,220],{},[142,874,875],{},"是否构建了正确的产品，对着用户需要检查",[125,877,878,881,883],{},[142,879,880],{},"review",[142,882,132],{},[142,884,885],{},"不运行程序、靠人检查发现缺陷的方式",[125,887,888,891,894],{},[142,889,890],{},"static testing",[142,892,893],{},"静态测试",[142,895,896],{},"不执行代码的检查，如评审与静态分析",[125,898,899,902,905],{},[142,900,901],{},"dynamic testing",[142,903,904],{},"动态测试",[142,906,907],{},"实际运行程序来发现缺陷",[125,909,910,913,916],{},[142,911,912],{},"unit testing",[142,914,915],{},"单元测试",[142,917,918],{},"验证单个方法或类的行为",[125,920,921,924,927],{},[142,922,923],{},"integration testing",[142,925,926],{},"集成测试",[142,928,929],{},"验证模块之间的接口与协作",[125,931,932,935,938],{},[142,933,934],{},"system testing",[142,936,937],{},"系统测试",[142,939,940],{},"验证整个系统的功能与非功能表现",[125,942,943,946,949],{},[142,944,945],{},"user acceptance testing（UAT）",[142,947,948],{},"验收测试",[142,950,951],{},"由业务方确认是否满足需要",[125,953,954,957,960],{},[142,955,956],{},"deployment testing",[142,958,959],{},"部署测试",[142,961,962],{},"验证在真实环境中的安装与运行",[125,964,965,968,971],{},[142,966,967],{},"white box testing",[142,969,970],{},"白盒测试",[142,972,973],{},"基于内部逻辑设计的测试",[125,975,976,979,982],{},[142,977,978],{},"black box testing",[142,980,981],{},"黑盒测试",[142,983,984],{},"只看输入输出的测试",[125,986,987,990,993],{},[142,988,989],{},"equivalence class partitioning",[142,991,992],{},"等价类划分",[142,994,995],{},"把输入空间分段，每段取一个代表值",[125,997,998,1001,1004],{},[142,999,1000],{},"boundary value analysis",[142,1002,1003],{},"边界值分析",[142,1005,1006],{},"在等价类边界上下取值测试",[125,1008,1009,1012,1015],{},[142,1010,1011],{},"exception condition",[142,1013,1014],{},"异常条件",[142,1016,1017],{},"空值、非法输入、并发等异常情形",[125,1019,1020,1023,1026],{},[142,1021,1022],{},"test case",[142,1024,1025],{},"测试用例",[142,1027,1028],{},"输入、预期输出与前置条件的组合",[125,1030,1031,1034,1037],{},[142,1032,1033],{},"test data",[142,1035,1036],{},"测试数据",[142,1038,1039],{},"执行用例所需的实际数据",[125,1041,1042,1045,1048],{},[142,1043,1044],{},"test environment",[142,1046,1047],{},"测试环境",[142,1049,1050],{},"尽量接近生产的运行环境",[125,1052,1053,1056,1059],{},[142,1054,1055],{},"test procedure",[142,1057,1058],{},"测试步骤",[142,1060,1061],{},"用例的执行顺序与依赖",[125,1063,1064,1067,1070],{},[142,1065,1066],{},"functional testing",[142,1068,1069],{},"功能测试",[142,1071,1072],{},"验证系统做什么",[125,1074,1075,1078,1081],{},[142,1076,1077],{},"regression testing",[142,1079,1080],{},"回归测试",[142,1082,1083],{},"改动后重跑既有测试，确认没弄坏别处",[125,1085,1086,1089,1092],{},[142,1087,1088],{},"test coverage",[142,1090,1091],{},"测试覆盖率",[142,1093,1094],{},"测试触及代码或需求的比例",[125,1096,1097,1100,1103],{},[142,1098,1099],{},"defect",[142,1101,1102],{},"缺陷",[142,1104,1105],{},"与预期不符的系统行为",[125,1107,1108,1111,1114],{},[142,1109,1110],{},"Agile testing lifecycle",[142,1112,1113],{},"敏捷测试周期",[142,1115,1116],{},"测试融入每个迭代而非独立阶段",{"title":1118,"searchDepth":1119,"depth":1119,"links":1120},"",3,[1121,1123,1127,1131,1135,1140,1144,1147,1150],{"id":28,"depth":1122,"text":29},2,{"id":90,"depth":1122,"text":91,"children":1124},[1125,1126],{"id":95,"depth":1119,"text":96},{"id":189,"depth":1119,"text":190},{"id":224,"depth":1122,"text":225,"children":1128},[1129,1130],{"id":235,"depth":1119,"text":236},{"id":338,"depth":1119,"text":339},{"id":356,"depth":1122,"text":357,"children":1132},[1133,1134],{"id":360,"depth":1119,"text":361},{"id":415,"depth":1119,"text":416},{"id":432,"depth":1122,"text":433,"children":1136},[1137,1138,1139],{"id":436,"depth":1119,"text":437},{"id":451,"depth":1119,"text":452},{"id":520,"depth":1119,"text":521},{"id":639,"depth":1122,"text":640,"children":1141},[1142,1143],{"id":643,"depth":1119,"text":644},{"id":687,"depth":1119,"text":688},{"id":702,"depth":1122,"text":703,"children":1145},[1146],{"id":706,"depth":1119,"text":707},{"id":748,"depth":1122,"text":748,"children":1148},[1149],{"id":775,"depth":1119,"text":775},{"id":838,"depth":1122,"text":838},"学习",null,"2026-11-05","「我们测过了」这句话问题在哪：为什么静态评审和动态测试缺一不可，五个测试层级各自验证什么，白盒与黑盒的差别，以及用等价类划分和边界值把无限输入变成有限用例的方法。","md",false,{},true,"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-14",{"title":5,"description":1154},"软件分析与设计笔记",14,"posts\u002FswDesign\u002Fsw-design-notes-14",[1165,1166,1167],"软件测试","质量保证","学习笔记","NWy5RBbW4rA8m5JYbTpB1q_Us83CDaMCAJq6Pv50HLw",[1170,1176,1182,1188,1194,1200,1206,1212,1218,1224,1230,1236,1242,1248,1254,1255,1261,1267,1273,1279,1285,1291,1297,1303,1309,1315,1320,1325,1331,1337,1341,1345,1349,1353,1357,1361,1365,1369,1373,1377,1381,1387,1391,1397,1401,1405,1409,1413,1417,1421,1425,1430,1434,1438,1443,1448,1454,1458,1463,1467],{"path":1171,"title":1172,"description":1173,"date":1174,"series":1161,"seriesOrder":1175},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-28","软件分析与设计笔记 28：draw.io 作图手册：五张图的符号位置、落笔顺序与自检","不模拟软件界面，只讲清三件事：每个 UML 符号在 draw.io 里搜什么词、一张图该按什么顺序落笔、画完拿什么标准自查。覆盖用例图、活动图、顺序图、类图、界面草图五种图纸，末尾附「最容易画反的十二笔」总检表。","2026-11-19",28,{"path":1177,"title":1178,"description":1179,"date":1180,"series":1161,"seriesOrder":1181},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-27","软件分析与设计笔记 27：Paper 1 应试：从读案例到交卷的一次完整演算","拿一份完整的案例，从头到尾走一遍：怎么读案例列角色、怎么在四个小问之间分配时间、每一张图从哪一句话推出来、两张表怎么填、交卷前拿什么清单自查。附 C25 真题与样卷的题型分值对照。","2026-11-18",27,{"path":1183,"title":1184,"description":1185,"date":1186,"series":1161,"seriesOrder":1187},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-26","软件分析与设计笔记 26：总结：把整条链路串起来","系列收尾：从需求到代码的完整链路回顾、每一环的产出与验收方式、六条贯穿全系列的原则，以及一份可以直接拿去做项目自查的清单。","2026-11-17",26,{"path":1189,"title":1190,"description":1191,"date":1192,"series":1161,"seriesOrder":1193},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-25","软件分析与设计笔记 25：代码重构：坏味道与手法","重构不改变外部行为、只改善内部结构：四种常见坏味道与对应手法、为什么动手之前必须先有测试、什么时候该重构，以及重构和前面那些设计模型是什么关系。","2026-11-16",25,{"path":1195,"title":1196,"description":1197,"date":1198,"series":1161,"seriesOrder":1199},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-24","软件分析与设计笔记 24：设计原则：内聚、耦合与多态","好设计怎么衡量：内聚与耦合分别在说什么、一次从「差设计」到「多态重设计」的完整对照、抽象类与接口的选择依据，以及复用为什么会从优势变成风险。","2026-11-15",24,{"path":1201,"title":1202,"description":1203,"date":1204,"series":1161,"seriesOrder":1205},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-23","软件分析与设计笔记 23：设计建模：从对象模型到能实现的方案","分析模型能用但还不能直接开工：对象模型怎么映射到关系模型、继承映射的三种方案怎么选、数据访问层与 Data Broker 解决什么问题，以及三层架构与 MVC 的分工。","2026-11-14",23,{"path":1207,"title":1208,"description":1209,"date":1210,"series":1161,"seriesOrder":1211},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-22","软件分析与设计笔记 22：分析建模：边界、实体与控制对象","不经过分析直接写代码会踩什么坑、三种分析对象各管一段、怎么用顺序图完成一次用例实现，以及从顺序图导出类的属性、操作与关系的方法。","2026-11-13",22,{"path":1213,"title":1214,"description":1215,"date":1216,"series":1161,"seriesOrder":1217},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-21","软件分析与设计笔记 21：UML：类图与顺序图","为什么需要一种共同的画法：类符号三格怎么画、五种关系的符号与设计含义、每种关系落到 Java 是什么写法，顺序图的三要素怎么摆；以及这三种最容易找不到的符号在 draw.io 里各搜什么词。","2026-11-12",21,{"path":1219,"title":1220,"description":1221,"date":1222,"series":1161,"seriesOrder":1223},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-20","软件分析与设计笔记 20：面向对象概念：对象、关系与多态","面向对象不是「用某个语言写代码」：对象与类的区别、封装真正在保护什么、五种关系（依赖、关联、聚合、组合、继承）各自何时用，以及多态为什么能减少条件判断。","2026-11-11",20,{"path":1225,"title":1226,"description":1227,"date":1228,"series":1161,"seriesOrder":1229},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-19","软件分析与设计笔记 19：规范化与逻辑数据模型","一次改地址引发的数据不一致，讲清规范化的真正动机；UNF 到 3NF 每一步在解决什么问题，派生字段为什么不能存，优化为什么和规范化方向相反，以及逻辑数据模型在 3NF 之上补了什么。","2026-11-10",19,{"path":1231,"title":1232,"description":1233,"date":1234,"series":1161,"seriesOrder":1235},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-18","软件分析与设计笔记 18：数据库分析：实体、关系与数据字典","为什么不能拿到需求就动手建表：实体与关系怎么判定、基数为什么必须由业务方确认、数据命名要遵守什么规则，以及数据字典四列里最容易被空着的那一列。","2026-11-09",18,{"path":1237,"title":1238,"description":1239,"date":1240,"series":1161,"seriesOrder":1241},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-17","软件分析与设计笔记 17：用生成式 AI 做设计与开发：能用的场景与要防的坑","生成式 AI 在工作流里真正站得住的三个场景 —— 造示例数据、解释陌生代码、起草初稿；它为什么会复现社会偏见、为什么会编得像真的，以及哪些内容绝不能直接采用。","2026-11-08",17,{"path":1243,"title":1244,"description":1245,"date":1246,"series":1161,"seriesOrder":1247},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-16","软件分析与设计笔记 16：从 persona 到 URS：一份交付物的完整骨架","把前面十几篇串成一份能交出去的东西：用户需求规格说明书的九个部分、每一章来自哪个环节、由谁确认，以及面向决策者的提案该讲清哪三件事。","2026-11-07",16,{"path":1249,"title":1250,"description":1251,"date":1252,"series":1161,"seriesOrder":1253},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-15","软件分析与设计笔记 15：非功能需求：把「快、稳、安全」变成数字","为什么「系统要快」不是一条需求：非功能需求怎么量化，决定性能的用户量、交易量、数据量三张表怎么估，安全需求要写清什么，以及可靠性的「不可用」该怎么定义。","2026-11-06",15,{"path":1159,"title":5,"description":1154,"date":1153,"series":1161,"seriesOrder":1162},{"path":1256,"title":1257,"description":1258,"date":1259,"series":1161,"seriesOrder":1260},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-13","软件分析与设计笔记 13：需求获取：五种调查方法与一场访谈","需求从哪里来：五种事实调查方法各自适合回答什么、代价是什么，访谈从准备到记录的全过程，问卷设计里最容易犯的错，以及访谈记录必须写下来的那几项信息。","2026-11-04",13,{"path":1262,"title":1263,"description":1264,"date":1265,"series":1161,"seriesOrder":1266},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-12","软件分析与设计笔记 12：前端设计：把流程变成屏幕","界面设计不是「画得好看」：从活动图走到屏幕的三步，CRUD 任务流怎么当检查表，界面必须回答的三个问题，信息架构与五种导航类型，视觉一致性与留白，以及移动端与 Web 端的差别。","2026-11-03",12,{"path":1268,"title":1269,"description":1270,"date":1271,"series":1161,"seriesOrder":1272},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-11","软件分析与设计笔记 11：Agentic AI 与业务流程自动化","AI agent 到底是什么：五项能力与四个核心组件，什么场景该用定时任务、什么该用程序、什么才值得用 agent，两种实现路径的差别，必须提前防住的四类失败模式，以及设计方法从提示词到编排的演进。","2026-11-02",11,{"path":1274,"title":1275,"description":1276,"date":1277,"series":1161,"seriesOrder":1278},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-10","软件分析与设计笔记 10：精炼用例模型：include、extend 与泛化","三种精炼用例关系的判断标准：include 什么时候用、它和 extend 的区别到底在哪、参与者泛化与用例泛化各解决什么问题，以及精炼到什么程度就该停手。","2026-11-01",10,{"path":1280,"title":1281,"description":1282,"date":1283,"series":1161,"seriesOrder":1284},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-09","软件分析与设计笔记 09：功能需求（二）：从流程到业务实体","活动图画完之后，怎么从流程里筛出真正的业务实体：三条判断标准、哪些名词其实不该建实体、属性从哪里来，以及领域模型和后面的数据模型分别负责什么。","2026-10-31",9,{"path":1286,"title":1287,"description":1288,"date":1289,"series":1161,"seriesOrder":1290},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-08","软件分析与设计笔记 08：功能需求（一）：用例模型与活动图","从用户故事到用例的翻译过程：什么才算「好」的参与者，系统边界怎么划，用例描述必须写清哪四件事，活动图怎么把分支、并行与泳道画清楚，以及这两个图在 draw.io 里各用哪个形状、最容易画错哪一笔。","2026-10-30",8,{"path":1292,"title":1293,"description":1294,"date":1295,"series":1161,"seriesOrder":1296},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-07","软件分析与设计笔记 07：用户故事：把需求写成可验收的一句话","用户故事的三段结构，怎么从用户画像和数据地图里把它提取出来，一条故事多大算合适、时序为什么也是需求的一部分，以及验收标准怎么写才算真的可验收。","2026-10-29",7,{"path":1298,"title":1299,"description":1300,"date":1301,"series":1161,"seriesOrder":1302},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-06","软件分析与设计笔记 06：范围界定（数据侧）：用数据校验你的判断","访谈结论和数字对不上时该怎么办：分析的四个层次分别在回答什么问题、为什么不能跳级、数据准备与搜集怎么做成一张应用地图，以及数据最容易在哪几个地方骗你。","2026-10-28",6,{"path":1304,"title":1305,"description":1306,"date":1307,"series":1161,"seriesOrder":1308},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-05","软件分析与设计笔记 05：范围界定（用户侧）：用户画像与客户旅程图","为什么「所有用户」是一个陷阱，项目边界该怎么划，用户画像要写哪五个要素、最容易写成什么废纸，客户旅程图怎么把阶段、想法和情绪画在同一张纸上，以及怎么从这两样东西一步步推到可验证的需求。","2026-10-27",5,{"path":1310,"title":1311,"description":1312,"date":1313,"series":1161,"seriesOrder":1314},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-04","软件分析与设计笔记 04：接手陌生案例的第一步：三问与一张表","拿到一个完全陌生的业务案例，怎么从零开始不跑偏：只问 Who、What、Why 三个问题，把「问题—事实—假设—理由」记成一张四列表，说清为什么假设必须写明理由，以及带着问题进访谈为什么比空手去有效得多。","2026-10-26",4,{"path":1316,"title":1317,"description":1318,"date":1319,"series":1161,"seriesOrder":1119},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-03","软件分析与设计笔记 03：Agile 与 Scrum：迭代、角色与产出物","从「三周做完交付，客户说这不是我要的」讲起，说清瀑布式与迭代式的根本差别，Scrum 的三个角色和三种产出物，一个 sprint 里四次固定活动各自在解决什么问题，以及敏捷里最常见的几个误解。","2026-10-25",{"path":1321,"title":1322,"description":1323,"date":1324,"series":1161,"seriesOrder":1122},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-02","软件分析与设计笔记 02：设计思维、用户驱动与数据驱动","从「客服要一个能一次看完所有排期的界面」这个翻车案例讲起，说清设计思维到底是什么、用户驱动与数据驱动各自能看见什么又看不见什么、为什么真正的落点是把两者合起来的 data-informed design，以及访谈中怎么问出有用的信息。","2026-10-24",{"path":1326,"title":1327,"description":1328,"date":1329,"series":1161,"seriesOrder":1330},"\u002Fposts\u002Fswdesign\u002Fsw-design-notes-01","软件分析与设计笔记 01：课程地图与从需求到设计的主干","这一篇不写具体方法，先把整条链路铺开：软件分析与设计到底要解决什么问题，从需求到设计分成哪两段，中间有哪十几个环节，一份完整的交付物里应该有什么，以及后面 27 篇笔记怎么排、新人该按什么顺序读。","2026-10-23",1,{"path":1332,"title":1333,"description":1334,"date":1335,"series":1336,"seriesOrder":1330},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-01","Spring 学习笔记 01：IoC、依赖注入与第一个 Spring Boot 应用","从「手写 new 出来的三层结构」的痛点讲起，说清 IoC 容器、Bean 与依赖注入是怎么回事，再用 Maven 与 Spring Initializr 跑起第一个能返回 JSON 的应用。","2026-10-15","Spring 学习笔记",{"path":1338,"title":1339,"description":1340,"date":1335,"series":1336,"seriesOrder":1122},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-02","Spring 学习笔记 02：Spring Data JPA 基础——实体映射与 Repository","用 ORM 把类映射到表：@Entity、@Id、@GeneratedValue 怎么用，CrudRepository 三层接口怎么选，H2 与 MySQL 怎么配，以及 ddl-auto 到底该写哪个值。",{"path":1342,"title":1343,"description":1344,"date":1335,"series":1336,"seriesOrder":1119},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-03","Spring 学习笔记 03：JPA 查询——派生方法、JPQL 与分页排序","Repository 自带的方法只够按主键查，这篇讲三种自己造查询的方式：方法名派生、@Query 写 JPQL、原生 SQL，以及 Pageable 分页排序和投影返回。",{"path":1346,"title":1347,"description":1348,"date":1335,"series":1336,"seriesOrder":1314},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-04","Spring 学习笔记 04：JPA 关联映射与仓储测试","一对多、多对多怎么映射，谁负责维护外键，FetchType 与 CascadeType 该怎么选，顺带用 @DataJpaTest 与 AssertJ 把仓储层测起来。",{"path":1350,"title":1351,"description":1352,"date":1335,"series":1336,"seriesOrder":1308},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-05","Spring 学习笔记 05：Spring MVC 与 Thymeleaf 入门","一次页面请求在 Spring MVC 里走了哪几步，@Controller 怎么把数据交给模板，@PathVariable 与 @RequestParam 怎么取参数，以及控制器为什么必须是无状态的。",{"path":1354,"title":1355,"description":1356,"date":1335,"series":1336,"seriesOrder":1302},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-06","Spring 学习笔记 06：表单绑定与服务端校验","把表单字段绑到对象上，用 Bean Validation 注解做服务端校验，把错误信息显示回用户刚填的那一页，再补上自定义校验注解与全局异常处理。",{"path":1358,"title":1359,"description":1360,"date":1335,"series":1336,"seriesOrder":1296},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-07","Spring 学习笔记 07：Thymeleaf 片段复用与国际化","用 th:fragment 与 th:insert\u002Fth:replace 把导航栏、页脚抽成片段，页面套用统一布局，再用 messages.properties 与 LocaleResolver 做中英文切换。",{"path":1362,"title":1363,"description":1364,"date":1335,"series":1336,"seriesOrder":1290},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-08","Spring 学习笔记 08：HTTP Session 与会话状态管理","HTTP 无状态带来的问题，Cookie 与 Session 怎么分工，Spring 里读写会话的几种方式，以及会话固定、超时、多实例共享这些必须想清楚的事。",{"path":1366,"title":1367,"description":1368,"date":1335,"series":1336,"seriesOrder":1284},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-09","Spring 学习笔记 09：拦截器 HandlerInterceptor","登录检查、权限判断、请求耗时统计这类横切逻辑该放哪：HandlerInterceptor 的三个回调各在什么时机执行，怎么注册与排除路径，以及它和过滤器的分工。",{"path":1370,"title":1371,"description":1372,"date":1335,"series":1336,"seriesOrder":1278},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-10","Spring 学习笔记 10：Service 层与事务","一组数据库操作要么全成功要么全失败，靠 @Transactional 怎么做到；传播行为、隔离级别、回滚规则怎么选，以及自调用失效、乐观锁悲观锁这些实战里的坑。",{"path":1374,"title":1375,"description":1376,"date":1335,"series":1336,"seriesOrder":1272},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-11","Spring 学习笔记 11：REST API 基础——把 Service 暴露成接口","同一套 Service 怎么同时服务页面和客户端：@RestController 的路由与方法语义、状态码怎么给、DTO 怎么定，以及 MockMvc 端到端和切片测试的两种写法与各自的坑。",{"path":1378,"title":1379,"description":1380,"date":1335,"series":1336,"seriesOrder":1266},"\u002Fposts\u002Fjavaspring\u002Fspring-notes-12","Spring 学习笔记 12：REST 进阶——版本、更新、跨域与文档化","接口能跑通之后要处理的几件事：URL 与响应结构怎么定、版本怎么演进、更新怎么做到可安全重试、CORS 怎么配、HATEOAS 与 Spring Data REST 该不该用。",{"path":1382,"title":1383,"description":1384,"date":1385,"series":1386,"seriesOrder":1314},"\u002Fposts\u002Fjava\u002Fjava-functionalprogramming-notes","Java 函数式编程笔记：Lambda、函数式接口与 Streams","学习 Java 函数式编程时的个人笔记：Lambda 表达式的五步简写与方法引用、常见函数式接口、Stream 流水线与惰性求值，以及创建流、中间操作、终止操作与收集器的用法。示例代码均为自写，运行结果来自本地实测。","2026-10-13","Java 学习笔记",{"path":1388,"title":1389,"description":1390,"date":1385,"series":1386,"seriesOrder":1119},"\u002Fposts\u002Fjava\u002Fjava-generics-notes","Java 泛型笔记：泛型方法、有界类型与泛型类","学习 Java 泛型时的个人笔记：从重复的重载方法与多态尝试讲起，再到泛型方法与类型推断、有界泛型类型、Comparable 与 Number 边界，以及泛型类在集合中的用法。示例代码均为自写，运行结果来自本地实测。",{"path":1392,"title":1393,"description":1394,"date":1395,"series":1396,"seriesOrder":1314},"\u002Fposts\u002Fmysql\u002Fmysql-learning-04","MySQL 学习笔记 04：数据操纵（增删改）","MySQL 学习笔记第四篇：INSERT 插入（含批量插入与 INSERT ... SELECT）、UPDATE 更新、DELETE 删除，以及各自的坑。","2026-10-10","SQL 学习笔记",{"path":1398,"title":1399,"description":1400,"date":1395,"series":1396,"seriesOrder":1308},"\u002Fposts\u002Fmysql\u002Fmysql-learning-05","MySQL 学习笔记 05：DDL 概览","MySQL 学习笔记第五篇：数据定义语言概览——Schema、Table、View、Index、Constraints 这几类对象分别对应哪些 CREATE \u002F ALTER \u002F DROP 命令。",{"path":1402,"title":1403,"description":1404,"date":1395,"series":1396,"seriesOrder":1302},"\u002Fposts\u002Fmysql\u002Fmysql-learning-06","MySQL 学习笔记 06：CREATE TABLE 与数据类型","MySQL 学习笔记第六篇：CREATE TABLE 的完整语法、常用数据类型的选型建议，以及外键的两种定义方式。",{"path":1406,"title":1407,"description":1408,"date":1395,"series":1396,"seriesOrder":1296},"\u002Fposts\u002Fmysql\u002Fmysql-learning-07","MySQL 学习笔记 07：索引","MySQL 学习笔记第七篇：索引的作用与 B+ 树结构、单列与复合索引的创建、索引的代价与适用场景。",{"path":1410,"title":1411,"description":1412,"date":1395,"series":1396,"seriesOrder":1290},"\u002Fposts\u002Fmysql\u002Fmysql-learning-08","MySQL 学习笔记 08：DROP 与 ALTER","MySQL 学习笔记第八篇：DROP 删除数据库对象、ALTER 修改表结构，以及常见的坑。",{"path":1414,"title":1415,"description":1416,"date":1395,"series":1396,"seriesOrder":1284},"\u002Fposts\u002Fmysql\u002Fmysql-learning-09","MySQL 学习笔记 09：约束与数据完整性","MySQL 学习笔记第九篇：SQL 约束——NOT NULL、CHECK、PRIMARY KEY、FOREIGN KEY 等如何保证数据完整性，以及它们的对比。",{"path":1418,"title":1419,"description":1420,"date":1395,"series":1396,"seriesOrder":1278},"\u002Fposts\u002Fmysql\u002Fmysql-learning-10","MySQL 学习笔记 10：视图","MySQL 学习笔记第十篇：视图（Views）的定义与特性、和表的对比、创建与使用，以及可更新视图的限制。",{"path":1422,"title":1423,"description":1424,"date":1395,"series":1396,"seriesOrder":1272},"\u002Fposts\u002Fmysql\u002Fmysql-learning-11","MySQL 学习笔记 11：存储过程","MySQL 学习笔记第十一篇：存储过程（Stored Procedures）的概念、创建与调用、带参数的写法，以及优缺点。",{"path":1426,"title":1427,"description":1428,"date":1429,"series":1396,"seriesOrder":1330},"\u002Fposts\u002Fmysql\u002Fmysql-learning-01","MySQL 学习笔记 01：MySQL 基础知识","MySQL 学习笔记第一篇：MySQL Server 与 Workbench 的安装，数据库与 DBMS 的基本概念，关系模型、ACID 事务、常见数据类型，以及后续的学习路线。","2026-10-09",{"path":1431,"title":1432,"description":1433,"date":1429,"series":1396,"seriesOrder":1122},"\u002Fposts\u002Fmysql\u002Fmysql-learning-02","MySQL 学习笔记 02：SELECT 查询与过滤","MySQL 学习笔记第二篇：SELECT 的基本语法与逻辑执行顺序，WHERE 过滤（比较、LIKE、IN、BETWEEN、NULL）、各种 JOIN、UNION 与子查询。",{"path":1435,"title":1436,"description":1437,"date":1429,"series":1396,"seriesOrder":1119},"\u002Fposts\u002Fmysql\u002Fmysql-learning-03","MySQL 学习笔记 03：聚合、分组与输出控制","MySQL 学习笔记第三篇：COUNT\u002FSUM\u002FAVG 等聚合函数、GROUP BY 分组、HAVING 过滤、ORDER BY 与 LIMIT，以及一条 SELECT 的完整逻辑执行顺序。",{"path":1439,"title":1440,"description":1441,"date":1442,"series":1386,"seriesOrder":1122},"\u002Fposts\u002Fjava\u002Fjava-oop-notes","Java 面向对象笔记：类、继承、多态与异常","学习 Java 面向对象编程时的个人笔记：类与对象、封装与访问修饰符、继承与多态、集合、抽象类与接口、异常处理。示例代码均为自写，运行结果来自本地实测。","2026-10-06",{"path":1444,"title":1445,"description":1446,"date":1447,"series":1386,"seriesOrder":1330},"\u002Fposts\u002Fjava\u002Fjava-basics-notes","Java 基础语法笔记：从环境安装到方法与数组","学习 Java 基础语法时的个人笔记：JDK 安装、输入输出、变量与运算符、条件与循环、字符串、数组与方法。示例代码均为自写，运行结果来自本地实测。","2026-10-05",{"path":1449,"title":1450,"description":1451,"date":1452,"series":1453,"seriesOrder":1330},"\u002Fposts\u002Fgit\u002Fgit-install-and-basics","Git 安装与基本使用","从装好 Git 到跑通日常提交流程，把最常用的几条命令和它们背后的三个区域讲清楚。","2026-10-04","Git 入门",{"path":1455,"title":1456,"description":1457,"date":1452,"series":1453,"seriesOrder":1122},"\u002Fposts\u002Fgit\u002Flocal-remote-repository","本地远程仓库的部署","用自己的机器当 Git 服务器：建一个裸仓库、走 SSH 连上去，把本地提交推上去。",{"path":1459,"title":1460,"description":1461,"date":1452,"series":1462,"seriesOrder":1330},"\u002Fposts\u002Fguide\u002Fguide-file-layout","文件都放在哪","文章、图片、封面分别放在仓库的什么位置，哪些目录可以放手改、哪些碰了会出问题。","博客使用指南",{"path":1464,"title":1465,"description":1466,"date":1452,"series":1462,"seriesOrder":1122},"\u002Fposts\u002Fguide\u002Fguide-frontmatter","frontmatter 每个字段是什么意思","文章开头那段被横线包住的字段，逐个说明作用、默认行为和写错的后果。",{"path":1468,"title":1469,"description":1470,"date":1452,"series":1462,"seriesOrder":1119},"\u002Fposts\u002Fguide\u002Fguide-markdown","正文怎么写 —— Markdown 与排版效果","标题、链接、图片、代码、表格分别怎么写，以及本站对它们做了哪些自定义处理。",1793268185235]