复习1
多维度视图和质量目标
构建时
Code-level view:源码、类结构;代码变化表示代码的逻辑组织
Component-level view:文件、目录、包、库、静态链接、测试项;配置项、版本表示代码的物理组织
Moment view:源码、类结构;文件、目录、包、库、静态链接、测试项表现源码和组件在特定时刻的软件形态
Period view:代码变化;配置项、版本表现软件形态随时间的变化
源码组织在文件中,进而组织在目录中
文件存档在包中,并逻辑上归属于组件和子系统
可重用的模块以库的形式存在,库存储在磁盘文件中
静态链接:库被拷贝进代码形成整体,执行的时候无需提供库文件
运行时
Code-level view:代码快照、内存转储;堆栈轨迹、并发线程逻辑实体在内存中如何呈现
Component-level view:包、库、动态链接、数据库、网络、硬件;事件日志、多进程、分布式程序物理实体在物理硬件环境中如何呈现
Moment view:代码快照、内存转储;包、库、动态链接、数据库、网络、硬件逻辑/物理实体在内存/硬件环境中特定时刻的形态
Period view:堆栈轨迹、并发线程;事件日志、多进程、分布式程序逻辑/物理实体在内存/硬件环境中的形态随时间的变化
动态链接:库文件不会在构建阶段被加入可执行软件,仅仅做出标记;程序运行时,根据标记装载库至内存;发布软件时,将程序所依赖的所有动态库都复制给用户
运行时
分布式程序:需要多个运行程序,分别部署于多个计算机物理环境
代码快照:描述程序运行时内存里变量层面的状态
内存转储(Memory dump):一个包含进程内存拷贝的磁盘文件,包含程序异常退出时的寄存器、调用栈、程序数据等,调试器可以加载转储文件并显示信息
执行跟踪(Execution tracing):软件层面,用日志方式记录程序执行的调用次序
事件日志:系统层面的日志
外部质量因素
- 正确性
正确性:按照预先定义的规约执行,是最重要的质量指标
测试和调试:发现不正确、消除不正确
防御式编程:在写程序的时候就确保正确性
形式化方法:通过形式化验证发现问题
- 健壮性/鲁棒性
健壮性:针对异常情况的处理,关键在于出现异常时不要崩溃
健壮性是对正确性的补充,正确性保证了软件的行为要严格符合规约中定义的行为;健壮性保证了出现规约定义之外的情形的时候,软件要做出恰当的反应。
“正常”和“异常”是主观而非客观的,未被规约覆盖的情况即为“异常情况”。
-
可扩展性
可扩展性:对软件的规约进行修改,是否足够容易
软件规模越大,扩展起来越不容易。 -
可复用性
可复用性:一次开发,多次使用 -
兼容性
兼容性:不同的软件系统之间相互可容易的集成
核心是保持设计的同构性,关键在标准化。 -
性能
除非有足够的正确性,否则性能毫无意义。对性能的关注要与其他质量属性进行折中,过度的优化会导致软件不再适应变化和复用。 -
可移植性
可移植性:软件可方便的在不同的技术环境之间移植,包括硬件和操作系统 -
易用性
易用性:对用户而言容易学、安装、操作、监控
提升易用性的方法是给用户提供详细的指南。 -
功能性(Functionality)
程序设计中一种不适宜的趋势,即软件开发者增加越来越多的功能,企图跟上竞争,其结果是程序极为复杂、不灵活、占用过多的磁盘空间,忽视整体质量,失去可持续性。 -
及时性(Timeliness)
及时性:在用户有对应需求前放出新版本
内部质量因素
源码方面:行数(LoC)、逻辑复杂度
结构方面:耦合、内聚
除此之外还有代码可读性、易于理解、清晰、大小等。
软件测试
从静态/动态上看
静态测试:在编写代码的阶段由程序员或是代码编辑器、编译器等工具进行检查(如语法检查、代码评审)
动态测试:通过测试用例实际执行了编写的代码动态测试可能在程序完全编写完成前就用于测试代码的特定节
测试用例及编写
测试用例:输入 + 执行条件 + 期望结果测试的动机:让代码出错,出错越快越好
编写测试的过程
先写规约,再写符合规约的测试用例
写代码、执行测试、有问题再改、再执行测试用例,直到通过它测试驱动开发(TDD):将需求转化为具体的测试用例,然后软件经过改进,通过新的测试
黑盒测试关键在于检查程序是否符合规约,完全从函数规约导出测试用例,不考虑函数内部实现。用尽可能少的测试用例,尽快运行,并尽可能大的发现程序的错误。
等价类划分
基于等价类划分的测试:将被测函数的输入域划分为等价类,从等价类中导出测试用例每个等价类代表着对输入约束加以满足/违反的有效/无效数据的集合。
等价类的假设:对于相似的输入,将会展示相似的行为故可从每个等价类中仅选一个代表作为测试用例,从而降低测试用例数量。
典型的划分原则
考虑输入数据是否限定了数值范围,或是指明了特定的值,或是对不同范围的值可能采用不同的计算方案。
边界值分析
边界值分析方法是对等价类划分方法的补充。
原理:大量的错误发生在输入域的“边界”而非中央
0 是正数和负数的边界
基本类型的边界,如 INT_MAX、INT_MIN
集合中的第一个和最后一个元素
两种覆盖方式
笛卡尔积:全覆盖
将多个划分维度上的多个取值组合起来,每个组合都要有一个用例。不过并非所有组合情况都可能。测试完备,但用例数量多,测试代价高。
覆盖每个取值:最少 1 次即可
每个维度的每个取值至少被 1 个测试用例覆盖一次即可。测试用例少,代价低,但测试覆盖度未必高。
白盒测试根据程序执行路径设计测试用例,一般较早执行。独立/基本路径测试:对程序所有执行路径进行等价类划分,找出有代表性的最简单的路径(例如循环只需执行 1 次),设计测试用例使每一条基本路径被至少覆盖 1 次
覆盖度测试
代码覆盖度:已有的测试用例有多大程度覆盖了被测程序,通常用百分比衡量代码覆盖度越低,测试越不充分;但要做到很高的代码覆盖度,需要更多的测试用例,测试代价高。
语句覆盖:每⼀条语句至少执行一次分支覆盖:判定中每个条件的所有可能结果至少出现一次,并且每个判定本身的所有可能结果也至少出现一次路径覆盖:每条可能执行到的路径至少执行一次
测试效果:路径覆盖 > 分支覆盖 > 语句覆盖测试难度:路径覆盖 > 分支覆盖 > 语句覆盖
语句覆盖:每⼀条语句至少执行一次分支覆盖:判定中每个条件的所有可能结果至少出现一次,并且每个判定本身的所有可能结果也至少出现一次路径覆盖:每条可能执行到的路径至少执行
测试策略:根据什么来选择测试用例
——非常重要,需要在程序中显式记录下来
目的:在代码评审过程中,其他人可以理解你的测试,并评判你的测试是否足够充分
分析工具:
Junit、EclEmma、VisualVM、AppPerfect:动态检查,需要执行代码
CheckStyle、SpotBugs、PMD:静态检查,无需执行代码