一、程序编码
程序编码是设计的继续,将软件设计的结果翻译成用某种程序设计语言描述的源代码。
程序编码涉及到方法、工具和过程。
程序设计风格和程序设计语言的特性会深刻地影响软件的质量和可维护性。
要求源程序具有良好的结构性和设计风格。
程序设计风格
(1)源程序文档化
程序内部的文档包括恰当的标识符、适当的注释和程序的视觉组织等。
标识符应选取含义鲜明的名字,有一定实际意义,名字不是越长越好,必要时采用缩写。
注释作为程序员与读者之间通信的重要手段决不是可有可无的,分为序言性注释和功能性注释。
利用空格、空行和移行,提高程序的可视化程度。
(2)数据说明
设计阶段已经确定了数据结构的组织及其复杂性;在编写程序时,则需要注意数据说明的风格。
为了使程序中数据说明更易于理解和维护,必须注意以下几点:
数据说明的次序应当规范化
说明语句中变量安排有序化
使用注释说明复杂数据结构
(3)语句构造
设计阶段确定软件的逻辑结构,编码阶段的任务是构造语句。语句构造力求简单、直接,不能为片面追求效率而使语句复杂化。
在一行内只写一条语句 , 采取适当的移行格式,使程序的逻辑和功能变得更加明确。
程序编写首先应当考虑清晰性 不要刻意追求技巧性。
程序要直截了当地说明程序员的用意
尽量采用三种基本控制结构来编写程序,避免不必要的转移,尽量少用GOTO语句。
利用括号使表达式的运算次序清晰直观。
避免大量使用循环嵌套和条件嵌套。
避免采用过于复杂的条件测试,尽量少用“否定”条件的条件语句。
尽可能使用库函数。
尽可能用通俗易懂的伪码描述程序流程,然后再翻译成必须使用的语言。
确保模块独立,模块内信息隐蔽,耦合清晰可见。
(4)输入和输出
输入和输出信息是与用户的使用直接相关的。输入和输出的方式和格式应当尽可能方便用户的使用。一定要避免因设计不当给用户带来的麻烦。
因此,在软件需求分析阶段和设计阶段,就应基本确定输入和输出的风格。系统能否被用户接受,有时就取决于输入和输出的风格。
设计和实现考虑原则:
对所有的输入数据都要进行检验,识别错误的输入,以保证每个数据的有效性;
检查输入项各种组合的合理性,必要时报告输入状态;
输入的步骤和操作尽可能简单,保持简单的输入格式;
输入数据时,应允许使用自由格式;
应允许缺省值;
交互式输入时,使用提示符明确提示交互输入的请求;
最好使用输入结束标志,不要由用户指定输入数据数目;
给所有的输出加注解,并设计输出报表格式。
(5)程序效率
程序的效率是指程序的执行速度及程序所需占用的内存存储空间。
程序编码是最后提高运行速度和节省存储的机会,在此阶段不能不考虑程序的效率。
讨论程序效率的三条原则:
①效率是性能要求,应在需求分析阶段确定这方面要求;
②效率是靠好的设计来提高的;
③程序的效率和程序的简单程度是一致的。
程序设计语言
程序编码阶段的任务是将软件的详细设计转换成用程序设计语言实现的程序代码,即把用PDL等工具描写的算法,翻译成计算机能接受的诸如C++、Java等程序设计语言的程序。 因此,程序设计风格和设计语言的性能对于程序设计的效率和质量有着直接的关系。
(1)程序设计语言的基本成分
①数据部分:程序中构造的数据类型,描述程序中使用的各种类型的数据,如变量、数组、指针等。
②运算部分:程序中允许执行的运算,用以描述程序中所需执行的运算。
是哪控制部分:程序中允许使用的控制结构,用它们构造程序的控制逻辑。
④传输部分:传输数据的方式,如输入/输出语句。
(2)分类
①按抽象级别
低级语言,包括机器语言和汇编语言,与特定计算机硬件相关。
高级语言,不反映特定计算机体系结构,表示方法更接近待解决的问题,包括FORTRAN、C/C++、Java等。
②按应用范围
通用语言,适用多种应用,包括FORTRAN、C、C++、Java等。
专用语言,为特殊应用而设计,具有特定的语法形式、结构及词汇,与问题的相应范围密切相关。例如PROLOG、Lisp等。
③按对用户的要求
过程性语言,描述计算过程,如 C、FORTRAN等。
非过程性语言,不显式指定处理细节,如查询语言、原型语言等。
(3)语言选择原则
为特定开发项目选择程序设计语言时,应从技术角度、工程角度、心理学角度评价和比较各种语言的适用程度,考虑现实可能性,作出合理的折衷。
①编译程序应具有较高的效率② 尽可能应用代码生成的工具 ③源程序应具有可移植性④工程的规模⑤ 软件的应用领域⑥用户的要求 ⑦程序员的知识
二、软件测试
尽管在分析、设计和实现过程中,使用了许多保证软件质量的方法,但难免还会犯错误。 软件开发过程必须伴有质量保证活动。 软件测试是在软件投入运行前,对分析、设计和编码的最终复审。
(1)目的
测试是为了发现程序中的错误而执行程序的过程;一个成功的测试是发现了至今为止尚未发现的错误的测试;一个好的测试方案是极可能发现迄今为止尚未发现的错误的测试方案。
以最少的时间和人力,系统地找出软件中潜在的各种错误和缺陷。如果成功地实施了测试,就能发现软件中的错误。
测试不能表明软件不存在错误,只能说明软件中存在错误。测试的附带收获是,它能够证明软件的功能和性能与需求说明相符。
收集的测试结果数据为软件可靠性分析提供依据。
(2)准则
①所有的测试都应追溯到用户要求,并将“尽早地、不断地进行软件测试”作为软件开发者的座右铭。
②应从“小规模”测试开始,逐步进行“大规模”测试。
③远在测试开始之前就制定出测试计划并严格地执行,排除测试的随意性。
④穷尽测试是不可能的,应精心地设计测试方案。
⑤为达到最佳测试效果,应由独立的第三方从事测试工作,限制开发人员检查自己的程序。
⑥全面检查每个测试结果,妥善保存测试计划、测试用例、出错统计和分析报告,为维护提供方便。
(3)信息流
三类输入:软件配置、测试配置、测试工具
输出测试结果,发生错误对软件进行调试,并修改程序,修改好再次返回测试。
输入预测结果,根据结果对出错数据进行可靠性分析
(4)测试与各阶段的关系
软件测试不等于程序测试
所有的测试都应追溯到用户需求
问题的根源可能在开发前期的各阶段 解决、纠正错误也必须追溯到前期工作
软件测试贯穿软件定义与开发的整个期间 各阶段所得的文档都应成为测试的对象
(5)软件测试方法
静态测试方法:人工测试方法,计算机辅助静态分析
动态测试方法:黑盒测试和白盒测试
黑盒测试
将测试对象看做一个黑盒,测试人员不考虑程序内部的逻辑结构和内部特性,只依据需求规格说明,检查程序的功能是否符合其规格说明。
黑盒测试又称功能测试或数据驱动测试。
测试人员充当客户来使用它。
黑盒测试——等价类划分
典型的黑盒测试方法,不考虑程序的内部结构,只依据程序的规格说明来设计测试用例。
把所有可能的输入数据,即程序的输入域划分成若干部分,然后从每一部分中选取少数代表性数据做为测试用例。
设计测试用例要经历划分等价类(列出等价类表)和选取测试用例两步。
等价类指某个输入域的子集,该集合中,各个输入数据对于揭露程序中的错误都是等效的。 测试某等价类的代表值等价于测试该类的其他值。 等价类的划分有两种情况:①有效等价类:对于程序规格说明来说,是合理、有意义的输入数据构成的集合。 ② 无效等价类:对于程序的规格说明来说,是不合理、无意义的输入数据构成的集合。
确立了等价类之后,建立等价类表,列出所有划分出的等价类。
按有效等价类的一个测试用例尽可能多地覆盖尚未被覆盖的有效等价类和无效等价类的一个测试用例仅覆盖一个尚未被覆盖的无效等价类的原则从划分的等价类中选择测试用例。
黑盒测试——边界值分析
另一种黑盒测试方法,是等价类划分的补充。人们从长期测试的经验得知,大量的错误发生在输入或输出范围的边界上,而不是在输入范围的内部。
首先应确定边界情况,针对各种边界情况设计测试用例,可以查出更多的错误。
应选取正好等于,刚刚小于或刚刚大于边界的值做为测试数据,而不是选取等价类中的典型值或任意值做为测试数据。
与悬崖很类似
黑盒测试——因果图
等价类划分方法和边界值分析方法,都着重考虑输入条件,但未考虑输入条件间的联系。
若测试时必须考虑输入条件的各种组合,可使用一种适合于描述对于多种条件的组合,相应产生多个动作的形式来设计测试用例,这就需要因果图。
因果图方法最终生成的是判定表,它适合于检查程序输入条件的各种组合情况。
黑盒测试——错误推测法
人们可以靠经验和直觉推测程序中可能存在的各种错误,从而有针对性地编写检查这些错误的例子,这就是错误推测法。
错误推测法基本想法:列举程序中所有可能的错误和容易发生错误的特殊情况,根据它们选择测试用例。例如,数据测试中的缺省值、空值、零值等。
白盒测试
将测试对象看做一个玻璃盒,测试人员利用程序内部的逻辑结构及有关信息,设计或选择测试用例,对程序所有逻辑路径进行测试,提高测试覆盖率。
白盒测试又称结构测试或逻辑驱动测试。
在不同点检查程序的状态,确定实际的状态是否与预期的状态一致。
利用程序内部的逻辑结构及有关信息,设计或选择测试用例,测试所有逻辑路径,对软件的过程性细节做细致的检查。
白盒测试——逻辑覆盖
逻辑覆盖以程序内部逻辑结构为基础,是一系列测试过程的总称,逐渐进行越来越完整的通路测试。
①语句覆盖:语句覆盖就是设计若干测试用例,运行被测程序,使每一个可执行语句至少执行一次。
②判定覆盖:又称分支覆盖,设计若干测试用例,运行被测程序,使程序中每个判断的取真分支和取假分支至少经历一次。
③条件覆盖:设计若干测试用例,运行被测程序,使程序中每个判断的每个条件的可能取值至少执行一次。
④判定-条件覆盖:为兼顾判定覆盖和条件覆盖,提出了判定-条件覆盖,即设计足够多的测试用例,使得: 每个条件的所有可能取值至少执行一次; 同时每个判断的每个分支至少执行一次。
⑤条件组合覆盖:设计足够的测试用例,运行被测程序,使每个判断的所有可能的条件取值组合至少执行一次。
⑥路径覆盖:设计足够的测试用例,覆盖程序中所有可能的路径。
白盒测试——基本路径测试
基本路径测试方法把覆盖的路径数压缩到一定限度内,程序中的循环最多只执行一次。
使用基本路径测试技术设计测试用例的步骤:1、根据详细设计结果画出相应的流图; 2、计算流图的环路复杂度; 3、确定基本路径集合; 4、设计执行基本路径集中每条路径的测试用例。
控制流图的基础上分析控制构造的环路复杂度,用来度量程序的逻辑复杂性,计算如下:控制流图G中的区域数; 环路复杂度V(G)=E-N+2,其中E是流图中边数,N是节点数; 环形复杂度V(G)=P+1,其中P是流图中判定节点的数目。
环路复杂度给出了基本路径集中的独立路径的数量,这是确保程序中所有语句至少被执行一次所需的测试数量的上界。 独立路径是指至少引入一个以前未处理的新语句或新条件的一条路径。用流图术语描述,独立路径至少包含一条在定义该路径前不曾用过的边。 注意,基本路径集不是唯一的。
三、软件测试过程
(1)单元测试
单元测试又称模块测试,针对软件设计的最小单位——程序模块,进行检验的测试工作。
目的在于发现各模块内部可能存在的错误。
单元测试从程序内部结构出发设计测试用例。
多个模块可以平行、独立进行单元测试。
单元测试内容
测试者依据详细设计说明书和源程序清单,了解该模块的逻辑结构和模块的I/O条件。包括模块接口、局部数据结构、重要执行通路、出错处理、边界条件
单元测试环境
模块并不是一个独立的程序,在考虑测试模块时,同时要考虑它和外界的联系。 用一些辅助模块去模拟与被测模块有联系的其他模块。包括驱动模块和桩模块
(2)集成测试
又称组装测试或联合测试,在单元测试基础上,将模块按要求组装为系统,发现与接口有关的问题:连接时,穿越模块接口的数据是否会丢失; 一个模块的功能是否对另一模块产生不利影响; 全局数据结构是否有问题; 各子功能组合起来,能否达到预期的父功能; 单个模块的误差累积起来是否会放大。
一次性集成
也叫整体拼装,首先对每个模块分别进行单元测试,然后把所有模块一次全部集成进行测试,最终得到要求的软件系统。
增量式集成
又称渐增式组装,首先对每个模块测试,然后将这些模块逐步组装成较大的系统。 在组装的过程中边连接边测试,以发现连接过程中产生的问题。通过增量逐步加入,组装成为要求的软件系统。
①自顶向下增量:按系统程序结构,将模块沿控制层次自顶向下进行组装。 自顶向下的增量方式在测试过程中较早地验证了主要的控制和判断点。 两种组装策略:广度优先和深度优先。
②自底向上增量:从程序模块结构的最底层的模块开始集成和测试。 模块自底向上进行组装,对于一个给定层次的模块,因为其子模块(包括子模块的所有下属模块)已经集成并测试完成,需要从子模块得到的信息可以直接得到,所以不再需要桩模块。 低层的多个模块可以并行测试,提高测试效率。
③混合式增量:对软件结构中的中、上层模块采用自顶向下的集成测试方法; 对下层模块和关键算法模块采用自底向上的集成测试方法; 形成从上下向中间逼近的混合式测试方法。
集成测试完成标志:集成测试应由专门的测试小组进行,测试小组由有经验的系统设计人员和程序员组成,测试活动要在评审人员出席的情况下进行。完成后提交 集成测试计划 、 集成测试规格说明 、 集成测试分析报告。
(3)确认测试
目标是验证软件的有效性,验证软件的功能和性能等特性是否与用户的要求一致,复查软件配置。
有效性测试
任务是验证被测软件是否满足需求规格说明书列出的需求,是确认测试的基础,通常运用黑盒测试方法。经过确认测试,有下述两种可能的结果:测试结果与预期结果相符和 测试结果与预期结果不符
复查软件配置
软件配置复查的目的是保证:软件配置的所有成分都齐全; 各方面的质量都符合要求; 具有维护阶段所必需的细节; 已编排好分类的目录。
除了按合同规定的内容和要求人工审查软件配置外,确认测试过程还应该严格遵循用户指南及其他操作程序。
确认测试必须有用户积极参与(当软件为特定用户开发时,通过验收测试,让用户验证所有的需求是否已经满足/当软件是为了多个用户开发的商品软件时,让每个用户逐个执行验收测试是不切实际的)或者以用户为主进行,由用户设计测试用例,使用生产中的实际数据进行测试。
α测试
由用户在开发环境下进行的,也可以是开发机构内部的用户在模拟实际操作环境下进行的测试。
α测试是在受控环境下进行的测试,开发者坐在用户旁边参与测试,随时记下错误情况和问题。
目的是评价软件的功能、可使用性、可靠性、性能和支持,尤其注重产品的界面和特色。
α测试可以从编码结束或单元测试完成后开始,也可以在确认测试过程中产品达到一定的稳定和可靠程度之后再开始。
β测试
由用户在实际使用环境下进行,开发者通常不在测试现场。
β测试是在开发者无法控制的环境下进行的软件现场应用,由用户记下遇到的问题(真实的以及主观认定的),定期向开发者报告。
β测试衡量产品的功能、可使用性、可靠性、性能和支持,着重于产品的支持性。
β测试处在整个测试的最后阶段,当α测试达到一定的可靠程度时才开始β测试,所有手册文本在此阶段完全定稿。
确认测试后需要提交的文档:确认测试计划、 确认测试规格说明、 确认测试分析报告、 最终的用户手册和操作手册、 项目开发总结报告。
四、软件调试
调试在成功测试阶段后开始,任务是进一步诊断和改正程序中潜在的错误。
调试活动由两部分组成:① 确定程序中错误的位置和性质; ②对程序(设计,编码)进行修改,排除错误。
调试是通过现象找出原因的一个思维分析的过程,是一个具有很强技巧性的工作。
调试过程:①确定出错位置;②找出错误原因;③修改排除错误;④重复进行暴露该错误的测试。
调试方法
①强行排错(蛮干法) ②回溯法 ③原因排除法(对分查找、归纳法、演绎法)
强行排错法
这种方法目前使用较多,不需要过多的思考,比较省脑筋,效率较低。
内存全部打印调试:在大量数据中寻找出错位置;
程序特定部位设置打印语句:把打印语句插在出错的源程序的各关键变量改变部位、重要分支部位、子程序调用部位,跟踪程序执行,监视重要变量的变化;
自动调试工具:利用某些语言的调试功能或专门的调试工具,分析程序的动态过程。
回溯法
在小程序中常用的一种有效的调试方法。 一旦发现了错误,首先分析错误征兆,确定最先发现“症状”的位置。然后,人工沿程序的控制流程,向回追踪源程序代码,直到找到错误根源或确定错误产生的范围。
对分查找法
如果已知每个变量在程序中若干个关键点的正确值,可以在程序中点附近“注入”这些变量的正确值,然后运行程序并检查输出。若输出结果正确,则错误原因在程序的前半部分;反之,错误原因在程序的后半部分。 对错误原因所在部分重复使用上述方法,直至把出错范围缩小到容易诊断的程度为止。
调试原则
1、确定错误性质和位置的原则:①分析与错误征兆相关信息,避免用试探法(最后手段);② 避开死胡同;③ 将调试工具作为辅助手段(帮助思考但不能代替思考)。
2、修改错误的原则:①注意错误的群集现象,出错多的地方很可能还有错误;② 不能只修改错误的表象,而没有修改错误的本身; ③修正一个错误的同时,避免引入新的错误;④ 改错过程暂时回到程序设计阶段,注意程序设计风格。