饭饭TXT > 学习管理 > 《技术领导力:程序员如何才能带团队(出版书)》作者:周明耀【完结】 > 技术领导力:程序员如何才能带团队 (周明耀).txt

第3章 产品开发过程管理 .2

作者:周明耀 当前章节:15428 字 更新时间:2026-6-23 09:06

6)系统出错处理设计:说明故障出现后可能需要采取的变通措施,例如热备技术、看门狗技术等。

2.总结

在这个阶段,设计者会大致考虑并照顾模块的内部实现,但主要仍集中于模块划分、分配任务、定义调用关系。模块间的接口与传参在这个阶段要制订得十分细致明确,需要编写严谨的数据字典,避免后续设计产生误解。概要设计一般不是一次就能做到位,而是需要反复进行结构调整的。典型的调整是合并功能重复的模块,或者进一步分解出可以复用的模块。在概要设计阶段,应最大限度地提取可以重用的模块,建立合理的结构体系,节省后续环节的工作量。

注意,如果是服务端和客户端都存在的系统,概要设计时需要区分服务端概要设计和客户端概要设计,不能合并在一个文件内。

概要设计文档最重要的部分是分层数据流图、结构图、数据字典以及相应的文字说明等。以概要设计文档为依据,各个模块的详细设计就可以并行展开了。

3.2.6 详细设计

详细设计阶段就是依据概要设计阶段的分解,设计每个模块内的算法、流程,为每个模块完成的功能进行具体的描述,把功能描述转变为精确的、结构化的过程描述。

在详细设计阶段中,各个模块可以分给不同的人并行设计。设计者的工作对象是一个模块,根据概要设计赋予的局部任务和对外接口,设计并表达出模块的算法、流程、状态转换等内容。注意,如果发现需要结构调整(如分解出子模块等),必须返回到概要设计阶段,将调整反映到概要设计文档中,而不能就地解决。详细设计文档最重要的部分是模块的流程图、状态图、局部变量及相应的文字说明等。一个模块对应一篇详细设计文档。

详细设计的目的是描述某一个模块内部的处理流程、开发方法和编码技巧。一般来说,详细设计由项目简介、模块说明(具体说明每一个模块内部的流程、功能、逻辑、消耗以及未解决问题)、接口设计(包括内部接口和外部接口)、数据结构设计(包括物理结构和逻辑结构)、特殊处理等几个部分构成。软件的详细设计,最终是将软件系统的各个部分的具体设计方法、逻辑、功能采用文字方式进行表述。这样在实现过程中,编码人员原则上严格按此进行代码实现即可。详细设计阶段常用的描述方式有:流程图、N-S图、PAD图、伪代码等。

3.2.7 编写代码

1.编码技巧

(1)优先考虑核心模块的压力测试

在对整个项目做设计、规划的时候,首先要对系统负载最高、开销最大,或者可能请求量最高的部分编写最基本的数据结构和操作逻辑,然后进行压力测试。也就是说,对一些没有把握且非常重要的部分,提前做压力测试、性能测试,确保都OK,然后正式完成数据结构设计和系统的设计。如果发现有问题,就一直调整,测试到符合预期为止。

我觉得很多程序员,习惯把东西做完,然后等着快上线的时候才做性能测试,如果前面代码实现上出了问题,这个时候就很麻烦了。当然,后期快上线的时候也要做性能测试,但前期的测试我认为还是很重要的。

做好这一点,需要懂一些业务,你要知道业务压力在哪里,业务请求的重心在哪里,很多时候,产品经理不主动告诉你,你也要问清楚。

(2)确保过程可控

我们以日志分析为例:

第一,先从小数据量开始执行测试,验证分析的代码和测试数据返回结果,验证是否符合预期,策略和步骤是否合理。

第二,代码执行时定时跟踪,比如说,每处理10万条日志,写一条状态日志,记录处理的日志条目数和当前的执行时间,有的时候也记录一下资源开销(有时候跑不完资源会崩溃掉,比如内存溢出,这时候需要回访一下,评估一下并想想如何优化调整)。这个执行时间记录也很关键,基本上可以在执行的时候,预估大概的结束时间,中间时间可以比如出去喝个咖啡,或者和别人讨论一个需求。如果一个分析程序跑着,一直没有进度展示,又不知道几个小时可以跑完,可能会非常浪费时间。

第三,断点可续,因为日志的数据量非常大,内存有大量的中间数据需要保存,有时候,代码跑着跑着,内存就崩溃了。如果每次都从头去跑,那反复花费的时间还是相当长的,所以,要分析程序,必要的话尽可能做到可以在中断的地方,或者上一个可控的断点重新开始跑。

(3)预留地方写注释

很多时候,对自己写的代码也不是很满意,比如某个处理效率不够优化,某个处理的方法不够简洁,或者扩展性比较差,代码写的很弱智,但可能短时间没有办法想清楚最合理的解决方案,考虑到上线初期这里并不需要重点关注,所以也不会特意去优化它,但这种情况下往往需要提前写上注释,并说明下一步优化的可能思路是什么,或者想到的可行方案是什么。

当然,可能超过一半的预留注释都没有真正被改进过,但一旦需要修改,这个注释就很重要。因为时间一长,当时编写的逻辑、思路可能会不记得了,需要调优或者改进的时候,发现以前已经有预留方案,或者能了解当初设计的原委,这些都是很有帮助的事情。

(4)用人人看得懂的逻辑

我们经常说代码的可读性。我的一位朋友毕业到一个公司写程序,他很喜欢写特别绕、特别啰嗦的逻辑,觉得自己很出色、成就感满满,却让接手的人看着一头雾水。后来他慢慢意识到那些啰嗦的逻辑其实都是坑,性能低下、毫无意义,而且非常难以维护,再往后就越写越简单。这好比我们说话,把逻辑复杂的长句,分成几个简单的短句,这样更容易理解,也更容易表达。写代码也是同理,很复杂的逻辑,拆解一下,分成几个简单的逻辑写出来,很清楚,也很有效率。

(5)不要沉迷于框架

很多人写程序,言必称框架,似乎没有框架就写不了程序,其实没必要。比如,就常见的PHP来说,本身就可以认为是一种框架了,你再套一层,相当于伤害了这门语言的活力。

团队开发,用框架也不是说不可以。但你要知道你需要的是什么,你遇到的障碍是什么。

框架最大的问题是什么?是过于繁冗的嵌套。为什么我不建议用框架?因为经常遇到这样的情况,需要对一秒钟几千次请求的处理场景进行调优,此时我只能从数不清的框架中寻找数据处理的逻辑,寻找性能卡点,可能改动代码只有两行,但是找问题需要两天。

框架并不是越高端越好,所谓高端的框架,往往结构更加复杂,而且让人很难理解其中的数据调用过程。

所以,你的技术能力绝对不能被框架约束。

(6)使用熟悉、成熟的技术

听说有这么一家创业公司,因为技术负责人一时兴起,用了MongoDB,然后就进坑了。当然,不是说MongoDB不好,而是相关负责人只是听说还不错,指标很好,然后就兴冲冲地使用了。

现在很多技术人员,习惯性评论技术好坏,你知道这些技术的极限性能吗?如果你用到了极限性能,发现不行,你可以去用别的,或者你的业务场景,确实这些不适合,你也可以去用别的,这都合理。但很多人根本没搞明白自己的障碍和问题在哪里,根本不知道相关技术产品的优势和劣势在哪里,看一堆第三方的数据测评后脑子一热就去学新技术,然后掉进坑里出不来,如果是创业公司,可能项目就死在里面了。所以在使用新技术前,建议全面了解该技术的特征、适用范围,以及不适用的范围。

(7)细节没优化前,别谈架构

系统访问负载升高,系统稳定性较差,遇到这种情况,别急着换服务器或者技术,先把问题搞清楚,看看具体原因到底在哪里,单服务器的瓶颈和压力在哪里,再来谈这个问题。

(8)多留日志

经常会产品上线后遇到诡异的问题,而且核查起来很复杂,回到公司又无法复现。这类Bug是最难处理的。所以,建议在代码里面、Web服务里面,以及系统运维的脚本里面,多增加一些日常的日志输出,对异常进行自动的信息采集。这样,出问题的时候,可以查看问题的信息,对定位问题、分析问题、解决问题帮助都很大。

2.日志规范

日志作为重现系统场景的手段,对于定位系统问题具有非常重要的作用。我们应该明确在何处打印日志,打印多少日志,而且需要按照严格的格式和统一的风格,绝不能随意。

日志打印少了,开发者会觉得一头雾水,缺少关键信息,就没有充足的证据明确问题。日志打印太多,会影响系统性能,毕竟日志文件也会涉及磁盘IO操作,而且日志过多会导致循环覆盖,丢失关键信息。

一般来说,日志组件会采用Log4j这样的稳定组件,通过使用这类组件,可以实现以下问题:

1)日志级别动态调整,无须重新编译程序。

2)日志大小可定制,实现循环覆盖,无须定制时可以压缩打包。

日志约束条件一般包括如下:

1)单个日志文件大小限制,可以设置上限、下限,这样有利于日志总大小计算。

2)日志数量限制。

3)日志统一路径、命名规则。

4)日志备份机制设计,可以预防丢失日志。

日志格式一般为:时间|函数地址|等级|模块|行数|日志详情

日志等级一般分为ERROR、WARN、INFO、DEBUG和TRACE,不同场景下使用不同的日志等级,不能随意使用。

1)严重的系统错误,会导致流程中断或异常流程,需要使用等级最高的ERROR等级。例如数据库操作失败、服务异常等场景。

2)异常但不影响系统的正常流程,使用WARN等级。例如,查询不存在的数据。

3)系统或流程的关键状态变化,使用INFO等级。例如,系统启动或退出步骤执行。

4)业务流程点或调试信息,使用TRACE等级。例如,函数执行过程中的调试信息。

5)实际生产场景下日志等级一般是INFO。

日志详细规范包括:

1)日志语言必须是英文,保证日志是ASCII编码。

2)日志中不能打印安全敏感信息,如用户名、密码、序列号等。

3)日志不能暴露系统业务处理逻辑。

4)日志必要时需要记录上下文信息,确保不缺失定位问题需要的关键信息。

5)不同模块的日志格式和风格保持一致。

6)抛出异常后需要把异常详细信息记录到日志中。

7)日志一定要通顺简洁,不能暴露关键函数和变量。

日志数量较多的情况下,可以通过专门的日志分析工具,例如OtrosLog-Viewer进行批量分析。

3.针对代码编写的十条建议

原则1——简化控制流程

使用尽可能精简的控制流程构造编写程序——不要使用setjmp或longjmp构造、goto语句,以及直接或间接的recursion。

原因:简化控制流程有助于提高代码清晰度,增强代码可验证能力。不使用递归,便不会产生循环的函数调用图,这样也可证明所有本应有界的执行实际上都是有界的。

原则2——为循环设置上限次数

所有循环必须有固定次数的上限。我们可以通过验证工具静态地证明,为循环中迭代数量所设立的上限次数未被超越。如果无法以静态方式加以证明,则可认为未遵守该原则。

原因:为循环设置次数界限,避免使用递归,这些做法有助于预防代码失控。然而该原则无法适用于本就不应终止的迭代(例如进程调度器)。此时将沿用该原则的逆向原则:必须能够静态地证明迭代不能终止。

原则3——不使用动态内存分配

不要在初始化完成后进行动态内存分配。

原因:诸如malloc等内存分配机制,以及垃圾回收器、通常会产生无法预知的行为,进而可能会对性能产生影响。更重要的是,还有可能因为程序员的失误造成内存错误,例如:

1)试图分配超过可用物理内存数的内存。

2)忘记释放内存。

3)继续使用已被释放的内存。

4)对已分配内存进行越界使用:应强制所有模块位于固定大小、预先分配的存储区域中,借此可避免此类问题,并简化内存使用情况的验证工作。

对于堆中未分配内存的情况,动态请求内存的唯一方式是使用栈内存。

原则4——不使用冗长的函数

任何函数的长度不应超过使用标准参考格式(每个声明最多一行,每个语句最多一行)打印的纸张上一页纸所能容纳的字符数。这意味着函数的代码不应超过60行。

原因:过长的函数通常意味着结构并非最优。每个函数都应是可理解且可验证的单一逻辑单位。如果在计算机显示器上需要多屏界面才能完整显示,这样的逻辑单位通常会极难理解。

原则5——低断言密度

程序的断言密度(Assertion Density)应平均保持为每个函数最少两个断言。断言可用于检查现实运行过程中本来绝不应出现的异常状况,因此应定义为Boolean测试。当断言失败后,应执行明确的恢复操作。如果静态检查工具证明断言绝对不会Fail或Hold,则可认为未遵守该原则。

原因:业界的代码编写工作统计报告显示,通过单元测试可发现,通常我们所编写的每10~100行代码中至少会存在一处缺陷。随着断言密度的增高,拦截缺陷的机会也会增大。断言的另一个重要之处在于,它是防御性编程(Defensive Coding)策略的重要组成部分。我们可以使用断言验证函数执行前后的状况,函数的执行参数和返回值,以及循环不变式(Loop-invariant)。在完成性能关键代码的测试工作后,可将断言选择性地禁用。

原则6——以最小范围级别声明数据对象

该原则同时也是数据隐蔽(Data Hiding)的基本原则。所有数据对象均必须以尽可能最小的范围级别进行声明。

原因:如果某对象不在范围内,意味着其值将无法引用或已损坏。该原则不鼓励使用多种可能导致故障诊断工作变得更复杂的互斥意图重用变量。

原则7——检查参数和返回值

应在每次调用函数后检查非空函数的返回值,并应在每个函数内部检查参数的合法性。在最严格的形式下,该原则意味着就算printf语句和文件close语句的返回值也应进行检查。

原因:如果对一个错误结果的响应与对成功结果的响应本没有任何区别,那么很明显需要检查返回值。通常对close和printf的调用便符合这种情况。此时一种可行的方法是将函数的返回值明确抛给void,这意味着开发者明确(而非意外地)决定忽略该返回值。

原则8——限制预处理程序的使用

预处理程序(Preprocessor)应仅限用于头文件和宏定义。递归的宏调用、令牌传递,以及变量参数列表均不允许使用。就算在大型应用程序开发工作中,标准样板文件(Boilerplate)之外也可能有必要使用一两个以上的条件编译指令,这是为了避免将同一个头文件包含多次。这种用法必须通过工具检查器添加标记,并通过代码阐述原因。

原因:C语言预处理程序是一个强大但较为含糊的工具,有可能会彻底破坏代码的清晰度,并让很多基于文本的检查器产生混淆。就算具备正式的语言定义,包含无界限预处理程序代码的构造也会显得非常难以解读。有关条件编译的注意事项同样很重要。就算只使用10个条件编译指令,代码也可能会产生1024(2^10)个版本,这会导致测试工作量剧增。

原则9——限制指针的使用

指针的使用必须加以限制。通常只允许不超过一层的解引用(Dereferencing)。指针解引用操作不应隐藏在typedef声明或宏定义内部。此外函数指针也是不允许使用的。

原因:指针很容易被滥用,就算专家也难以彻底避免。指针的存在会使得我们难以跟踪或分析程序中数据的流动,尤其是在使用基于工具的静态分析器执行这些操作时。函数指针还会对静态分析器所能执行的检查类型产生限制,因此除非有非常必要的理由,否则一般情况下不推荐使用。如果使用函数指针,通常无法通过工具证明递归的缺席,此时只能提供其他方法弥补这种分析能力的缺失。

原则10——编译所有代码

从开发工作第一天开始时,就必须对所有代码进行编译。必须启用编译器的警告功能,并使用最细致的检查选项。代码必须能通过这样的设置,在不产生任何警报的情况下顺利编译完成。所有代码必须每天一次,使用至少一种(多种则更好)最新型的静态源代码分析器进行检查,并且必须顺利通过分析器的整个检查过程而不产生任何警告。

原因:市面上有很多效果卓越的源代码分析器,其中很多甚至是以免费软件的形式发布的。对于这样可以直接使用的现成技术,任何软件开发工作都没理由不加以充分利用。

3.2.8 代码审核

众所周知,在团队中进行代码审查(Code Review)可以提升代码质量,分享项目知识、明确责任,最终构建更好的软件、更好的团队。当然也有许多方法可以进行代码审查,例如在GitHub中提到的Pull Request,或使用像JetBrains的Upsource之类的工具。然而即使拥有清晰的流程和正确的工具,还是遗留了一个大问题需要解决——我们需要找寻哪些问题。

1.代码审核重要性

代码审核及其重要,一般来说每周都要做一次代码审核。代码审核有利于跟踪项目进展情况,我们能真实地看到其他人的工作进展如何,并且能更早发现他们是否误入歧途。有时候,手下人会说“完成得差不多了!”你去看代码时发现什么都没有,诸如此类,总之离完成还很遥远。在管理中,这种情况是最让人讨厌的,所以我认为代码审查是避免这种麻烦的最佳途径。

2.代码审核内容

一般来说,代码审核内容由如下部分组成:

1)前置条件:代码是否可以正常运行、开发工具或者运行过程当中有没有严重警告灯。

2)代码规范性:主要由注释、排版、命名规则等组成,注释又可以进一步分为类定义、函数头、函数内代码实现等相关注释说明,以及注释风格统一要求。排版主要包括文件顺序(各类变量定义、函数实现等)、代码行格式要求(代码行最多字符数量、运算符外侧空格、缩进等)、对齐(函数、变量、注释等)。命名规则主要包括对于常量、类名、变量名、函数,以及通用命名规则的遵循。

3)代码逻辑:包括函数、控制结构、内存管理、类/结构体、其他高级特性、并发处理、设计模式使用、跨平台设计等。

此外,在做代码审核的时候,我们关注的应该是新增或修改的代码行数,而不是整个库的代码量变化。

3.代码审核关注点

1)设计

·如何让新代码与全局的架构保持一致?

·代码是否遵循SOLID原则,是否遵循团队使用的设计规范,如领域驱动开发等?

·新代码使用了什么设计模式?这样使用是否合适?

·基础代码是否使用了一些标准或设计样式,新的代码是否遵循当前的规范?代码是否正确迁移,或参照了因不规范而淘汰的旧代码?

·代码的位置是否正确?比如涉及订单的新代码是否在订单服务相关的位置?

·新代码是否重用了现存的代码?新代码是否可以被现有代码重用?新代码是否有重复代码?如果是的话,是否应该重构成一个可被重用的模式,还是当前还可以接受?

·新代码是否被过度设计了?是否引入现在还不需要的重用设计?团队如何平衡可重用和YAGNI(You Ain’t Gonna Need It)这两种观点?

2)可读性和可维护性

·字段、变量、参数、方法、类的命名是否真实反映它们所代表的事物。

·是否可以通过读代码理解它做了什么?

·是否理解测试用例测了什么?

·测试是否很好地覆盖了用例的各种情况?它们是否覆盖了正常和异常用例?是否有忽略的情况?

·错误信息是否可被理解?

·不清晰的代码是否被文档、注释或容易理解的测试用例所覆盖?

3)功能

·代码是否真的达到了预期的目标?是否有自动化测试来确保代码的正确性,测试的代码是否真的可以验证代码达到了协定的需求?

·代码看上去是否包含了不明显的Bug,比如使用错误的变量进行检查,或误把and写成or?

4)其他

·是否需要满足相关监管需求?

·是否需要创建公共文档或修改现存的帮助文档?

·是否检查了面向用户的信息的正确性?

·是否有会在生产环境中导致应用停止运行的明显错误?代码是否会错误地指向测试数据库,是否存在应在真实服务中移除的硬编码的stub代码?

·对性能的需求是什么,是否考虑了安全问题?

3.2.9 单元测试

1.什么是单元测试?

要认识单元测试,首先要明白什么是“单元(Unit)”。所谓“单元”指的是代码调用的最小单位,实际上指的是一个功能块(Function)或者方法(Method)。所以单元测试指的就是对这些代码调用单元的测试。

单元测试是一种白盒测试,就是必须要对单元的代码细节很清楚才能做的测试。所以,单元测试的编写和执行都是由软件工程师来做的。除了单元测试,还有集成测试。集成测试基本都是黑盒测试,主要是由测试人员根据软件的功能手册来进行测试,需要有专门的测试环境配合。集成测试又分功能测试、回归测试等。

2.单元测试质疑

有人会问,如果方法就是单元,一个软件包含那么多方法,那单元测试岂不是要写很多?确实会存在这个问题,这也是为什么很多人认为单元测试不可行的原因之一。

另一个原因是,一提单元测试就有人提出必须要做模拟(Mock)。因为要把代码跑起来就必须要模拟上下文,这样代码才可以跑起来测试。

这么多单元要测,并且跑起来还要那么多外部环境要模拟,仅维护这么多测试就要比开发量还要大了,这不是找罪受吗?更不要说单元测试也是代码,单元测试本身是不是也要测啊?因此很多软件工程师反感单元测试,即便有强制规定也是敷衍了事。

3.如何做单元测试

需要单元测试的代码实际上是开发人员自己写的逻辑,测试逻辑所依赖的环境是否正常不是单元测试的目的。在环境访问代码中引入逻辑,只会让逻辑更难测试,导致逻辑代码无法进行单元测试。因此,可单元测试的代码,才能够采用单元测试。有一个方法可以判断是否为可测试的代码,就是看这个方法能否用一个main函数直接运行,如果可以的话就是可单元测试的代码。可测试的代码还有另一个特征,就是该方法单元的参数,开发人员可以自由模拟,不需要依赖外部环境。

如果代码里有逻辑,但是不可单元测试的话,就需要改造代码。改造的方法,就是要确保逻辑代码和外部环境相关代码隔离,这个逻辑代码就是可单元测试的。而隔离的办法就是把代码的执行顺序,也就是单元的执行生命周期,做架构的拆分。访问代码的核心就在于传递上下文的数据,因此先把逻辑需要的数据从上下文环境中取出来,给逻辑代码单独另建一个单元,再把从环境中取出来的参数作为入参传入新建的逻辑代码单元。这样新建的逻辑代码单元就只依赖入参而不再依赖环境,从而使开发人员可以自由地模拟输入参数做单元测试了。

4.测试用例编写

单元测试主要是由开发人员对自己编写的代码进行自测或相互进行交叉测试,用以检查代码是否符合编码规范,是否存在逻辑错误。

针对程序的规范和逻辑,可以引入自动检查编码规范和编码逻辑代码的检查工具进行测试。

一般来说,单元测试用例主体包括每个模块的详细测试用例、修改记录这两项。修改记录比较容易理解,就是历次变更的时间和内容,而每个模块的详细测试用例的范例如下:

然后自己根据实际用例一条一条地填写即可。

5.单元测试优点

刚开始编写单元测试代码确实存在一定的代码量,会觉得有点占用时间。但是随着代码的深入,测试工作量会越来越小,因为单元测试做到位了,代码可以直接运行。反观手工测试,刚开始确实很快(需要测试的内容少),随着代码增多,手工测试工作量加大,反而花得时间更多,因为以前测试过的内容还需要重新再测,无法把自己的重复工作自动化。

一旦养成单元测试的习惯你就离不开单元测试了,因为一旦上瘾,会觉得没写单元测试代码就很不安。大部分时候,单元测试覆盖了100%,只要业务没有太大的变化,单元测试就不需要维护,即一次投入会持续受益。

3.2.10 集成测试

1.什么是集成测试

集成测试,也叫组装测试或联合测试。在单元测试的基础上,将所有模块按照设计要求组装成为子系统或系统,进行集成测试。实践表明,一些模块虽然能够单独工作,但并不能保证连接起来也能正常地工作。一些局部反映不出来的问题,在全局上很可能会全面暴露出来。

集成测试是在软件系统集成过程中所进行的测试,其主要目的是检查软件单位之间的接口是否正确。即根据集成测试计划,一边将模块或其他模块组合成越来越大的系统,一边运行该系统,以分析所组成的系统是否正确,各个组成部分是否合拍。集成测试的策略主要有自顶向下和自底向上两种。也可以理解为在软件设计单元、功能模块组装、集成为系统时,对应用系统的各个部件(软件单元、功能模块接口、链接等)进行的联合测试,以决定他们能否在一起共同工作,部件可以是代码块、独立的应用、网络上的客户端或服务器端程序。

2.集成测试困局

目前很多公司将测试环节推到了集成测试环节,交由测试人员来完成集成测试。这就形成了一个架构拆分,形成了新测试的生命周期,由测试人员来推进测试的生命周期。

大部分软件开发团队仍然完全依赖于人工的集成测试来提升软件质量。而集成测试属于新的分工,需要大量的人工介入,需要比较长的时间周期才能完成测试,也增加了很大的沟通成本。如果软件不经常进行迭代,人工测试问题还不太大,毕竟难得的一次。但是现代的软件开发迭代速度非常快,经常是按周的频率进行迭代。如果每周都要全面的测试,那就要维护一个比开发团队大得多的测试团队。测试因此就成为了迭代速度的瓶颈,会导致迭代速度的减慢,损害软件的更新以及业务的增长。

为了提升集成测试的效率,集成测试也可以进行大量的自动化。比如回归测试就是自动化的重点。另一个提升测试效率的办法,就是让测试回归到软件开发工程师职责范围内,也就是单元测试。当然,单元测试也属于自动化测试。

3.集成测试重点

集成测试重点是模块之间的连接。

将经过单元测试的模块组装成完整的程序。工作任务包括制定集成测试策略,确定集成测试步骤,设计集成测试用例,然后逐一添加模块进行测试。集成测试由测试人员负责,应该在概要设计完成后进行设计工作,并在单元测试完成后执行。

所有的软件项目都不能摆脱系统集成这个阶段。不管采用什么开发模式,具体的开发工作总得从一个一个的软件单元做起,软件单元只有经过集成才能形成一个有机的整体。具体的集成过程可能是显性的也可能是隐性的。只要有集成,总是会出现一些常见问题,工程实践中几乎不存在软件单元组装过程中不出任何问题的情况。一般来说,集成测试需要花费的时间远远超过单元测试,直接从单元测试过渡到系统测试是极不妥当的做法。

4.集成测试优点

·单元测试具有不彻底性,不能保证模块间接口信息内容的正确性,以及各模块间相互调用关系是否符合设计,只能依靠集成测试来保障。

·同系统测试相比,由于集成测试用例是从程序结构出发,目的性、针对性更强,测试项发现问题的效率更高,定位问题的效率也较高。

·能够较容易的测试到系统测试用例难以模拟的特殊异常流程,从纯理论的角度来讲,集成测试能够模拟所有实际情况。

·定位问题较快,由于集成测试具有可重复性强,对测试人员透明的特点,发现问题后,很容易定位,所以能够有效地加快进度,减少隐患。

3.2.11 系统测试

系统测试阶段包括系统测试方案和用例编写、功能性测试、性能测试、稳定性测试。

1.测试方案和用例编写

测试方案和用例是对业务逻辑的一次梳理,从测试角度完整理解一次需求,看看是否有测试场景遗漏,验证业务场景的完整性。测试方案的侧重点是整个业务方案层面的测试,而测试用例则是根据每一个需求所实现的对应具体测试方式,理论上来说它和每一个需求是N:1的关系,即一个需求对应多个测试用例,为什么会是多个?因为每个需求的实现内部都会有很多参数校验、业务分支、异常情况,需要多个用例才能覆盖完整。测试方案和用例是需要评审的,而且必须经过评审。评审过程需要测试负责人和产品、开发等相关人员一起评审。测试用例评审通过后,用例会分成两块,即核心用例和一般用例。

2.测试阶段的分工

测试阶段,开发人员的主要工作是修复Bug或者完成突发需求,测试人员的主要工作是执行测试用例,发现Bug、提交Bug以及验证Bug。很多场景下,开发人员每修复一个Bug,就会通知测试人员进行验证,这种工作方式的效率非常低下。在高效的研发体系中,回归测试是有时间约定的。例如:约定早上9点半提交测试,测试人员进行第一轮测试,然后集中提交Bug到Bug管理系统,开发人员收到指派Bug后,开始进行修复。下午3点,约定第二轮回归测试,由测试人员对已经修复的Bug集中进行验证,有问题,则再次提交Bug,指派给开发人员修改。下午7点,约定第三轮回归测试。在这样的体系下,开发人员专注于修复Bug,到了约定的时间,测试人员集中测试,减少了不断提测、部署环境浪费的时间。

3.功能性测试

功能性测试环节的目标是为了验证需求分析确定的功能是否齐全并被正确实现,同时还要对安装、部署、适应性、安全性、界面等非功能性需求进行测试。

功能性测试一般由独立测试小组采用黑盒方式来测试,主要测试系统是否符合《产品需求规格说明书》。功能性测试阶段又可分为三个步骤:

1)模块测试:测试每个模块的程序是否有错误;

2)组装测试:测试模块之间的接口是否正确;

3)确认测试:测试整个软件系统是否满足用户功能和性能的要求。

该阶段结束后需要交付测试报告,说明测试数据的选择,测试用例以及测试结果是否符合预期结果。测试人员发现问题之后要经过调试找出错误原因和位置,然后进行改正。功能性测试是基于系统整体需求说明书的黑盒类测试,应该覆盖系统所有联合的部件。以阿里双11的全链路压测为例,所有技术人员达成了一个共识,一定要有一套系统能够最真实地模拟双11当天的流量,能够及时发现大压力下线上系统的所有问题和风险,保障真实场景下的用户体验。

4.性能测试目标

阿里双11当天交易峰值较平时增长400倍,平日运转良好的系统面对突发的业务流量,所有的问题都会被重新定义。全链路一体化方案通过逼真化模拟实际大促时的流量特点,以自动化的方式评估、优化和保护整个交易链路,确保了双11的稳定性。这就是性能测试的重要性体现。

性能测试验证系统的稳定性和效率,检查系统是否满足规定的性能要求。性能测试通常选择一些典型的功能,检验这些功能在大量用户同时使用系统时系统是否稳定。性能测试由测试人员负责,可以在系统测试完成后进行,也可以对重要模块先进行性能测试,可以贯穿整个测试周期,目的是尽早发现系统的性能瓶颈并提早解决。

性能测试是一个十分复杂的系统工程,对测试人员的能力水平提出了更高的要求,需要性能测试人员具备非常全面的知识与技能,能够定位应用的性能瓶颈,并提出适当的优化方案。

5.性能测试测试模型与测试指标

在进行场景设计之前我们应该先确定本次性能测试的测试指标与测试模型。测试指标和测试模型是进行场景设计的前提和基础,是场景的输入。根据被测系统的类型不同,可能测试指标的类型略有不同。对于在线Web类的应用,测试指标一般包括在线用户数、最优并发用户数、最大并发用户数、交易平均响应时间、目标TPS等。对于接口调用类的应用测试指标一般包括目标TPS、平均响应时间等。

测试模型就是被测试系统的各交易在线运行时承受的交易数量(或请求数量)的比例而不是并发用户的比例。为什么不是并发用户的比例呢?因为实际用户的操作具有不确定性,使用测试工具很难模拟真实用户的行为。另外,在进行运营数据分析时很难获取用户的操作行为,而应用的交易记录却很容易通过查询的方式获取。应用实际承受的压力是用户的实际操作请求,在线用户如果没有进行实际操作那么他最多将消耗一个连接线程,而应用CPU并不会有什么资源消耗。100个用户平均每个花费10秒下一个订单和10个用户每1秒钟下一个订单对应用带来的压力是一样的。所以,在场景中能用最少的并发用户来模拟真实的请求是最经济的选择方式。

那么,测试模型到底该如何确定呢?通过需求调研获得。下面介绍的两点最常用的调研方式:

·对于还未上线运营的新系统,一般会让应用的产品经理或负责人给出一个预估的比例,但是这个预估需要我们进行评估,不是随意的。对于一个以提供下单交易为主的应用,通常下单交易是占整个模型的较大比例,如果需求方提出的模型是查询比例较高,那么就有理由怀疑该模型的合理性。对于这种情况,建议选择几个常见的典型的模型来配合需求模型进行场景设计。

·对于已上线运营的应用,一般会分析实际的交易数据来确定交易比例,这样会更加精准。例如一个应用对用户提供下单、查询、退款三个交易,通过DBA在线查询某日的交易数据总量为200000笔,其中交易下单160000笔、查询38000笔,退款2000笔,由此预测各交易的比例是80%、19%、1%,那么这个比例就是测试模型。

6.稳定性测试

稳定性测试和性能测试都必须等到系统基本没问题、趋于稳定时再进行才有效果,否则很难顺利测下去,出现异常也不能定位究竟是系统架构的问题,还是功能的问题。

稳定性测试(亦可称可靠性测试)通过给系统加载一定的业务压力,让系统持续运行一段时间(一般为7×24小时),检测系统是否能够稳定运行。

以一个BI(商业智能)的例子来进行我们的稳定性测试建模,主要步骤如下所示:

1)软件主要业务:从大量元数据中提取(ETL)客户关心的数据并最终生成报表(本文以微软平台BI为例:SSIS,SSAS,SSRS)

2)用户场景:利用SSIS包进行ETL操作将元数据计算转化后导入到数据立方体(Cube)中。

3)典型负载:每小时3000个用户,100000条数据,执行7×24小时。

4)测试环境:需求文档中规定的配置。

5)主要性能指标:

·ETL时间:9分钟,差别:1分钟,方差:<0.1

·系统相关:CPU,Memory,Private Mbytes/sec等

6)稳定性指标模型:

计算公式

稳定性模型

从图表中可以看出:

·ETL上限为12分钟(即如果超过12分钟就证明有瓶颈,需要核查)。

·ETL平均值为9分钟。

·控制线的上下方分别为Avg加减3倍的方差。

·实际使用时间围绕平均值上下分布(标准为同一向不能出现连续7个点:如连续7个实际检测值都在平均值的上方,这时就需要进行调查)。

稳定性测试是用来验证产品在一定的负载下能否长时间地稳定运行,其主要目的是验证系统能力,在能力的验证过程中找到系统不稳定的因素并进行分析解决。

3.2.12 产品发布

产品发布是系统测试结束后的最后一步,通常在软件产品开发过程中不需要产品试制环节,可以直接上线,只需要系统测试员输出系统测试报告并批准产品发布(上线)就可以了。

产品发布前需要通过产品发布说明会的形式,对整个产品开发过程从立项开始回溯过程,总结整个过程中的经验教训。这一会议可以通过正式的会议形式召开,需要召集产品经理、主要开发人员、测试人员、上级领导等参与,必须准备充分,尽最大可能说清楚这个产品发布之后的效果、效益,为上线后的价值评估做准备。这一环节不可缺少,即便在互联网公司,迭代速度很快的情况下,这一环节也需要满足。

3.2.13 开发过程复盘

1.复盘的意义

其实开发过程体系里并没有这一过程,但是我个人认为它非常重要。

所有的总结,只有带着问题去思考才会有收获,这就是复盘。不论我说多少,如果没有类似的经验,就很难有很强的共鸣。我觉得看清一个问题最好的方式,就是你曾经处在一个问题的两个不同的角色中。

当你去问一个老人很多问题的时候,虽然可能很快得到解答,但当有一天,有很多新人来问你问题的时候,你就能体会到什么问题该问,什么问题不该问。

目录
设置
设置
阅读主题
字体风格
雅黑 宋体 楷书 卡通
字体大小
适中 偏大 超大
保存设置
恢复默认
手机
手机阅读
扫码获取链接,使用浏览器打开
书架同步,随时随地,手机阅读
首 页 < 上一章 章节列表 下一章 > 尾 页