atwood-pan

 

软件工程基础

软件工程基础

软件工程基本概念与生命周期

什么是软件工程

软件工程 (Software Engineering,简称为SE)是一门研究用工程化方法构建和维护有效的、实用的和高质量的软件的学科。它涉及到程序设计语言,数据库,软件开发工具,系统平台,标准,设计模式等方面。

在现代社会中,软件应用于多个方面。典型的软件比如有电子邮件,嵌入式系统,人机界面,办公套件,操作系统,编译器,数据库,游戏等。同时,各个行业几乎都有计算机软件的应用,比如工业,农业,银行,航空,政府部门等。这些应用促进了经济和社会的发展,使得人们的工作更加高效,同时提高了生活质量.

软件工程生命周期

软件生存周期:

  • 可行性分析
  • 需求分析
  • 概要设计
  • 详细设计
  • 编码
  • 测试
  • 运行与维护

软件声明周期

软件声明周期由软件定义、软件开发和运行维护 3个时期组成,每个时期又进一步划分成若干个阶段

软件定义时期
软件定义时期的任务:

确定软件开发工程必须完成的总目标
确定工程的可行性
导出实现工程目标应该采用的策略及系统必须完成的的功能
估计完成该项目工程需要的资源和成本,并且制定工程进度表。这个时期又称为系统分析,由系统分析员负责完成
软件定义时期通常进一步划分为3个阶段,即问题定义、可行性研究和需求分析。

开发时期

开发时期具体设计和实现在前一个时期定义的软件,它通常由下述4个阶段组成:

  • 总体设计
  • 详细设计
  • 编码和单元测试
  • 综合测试

其中前两个阶段又称为系统设计,后两个阶段又称为系统实现。

维护时期

维护时期的主要任务是使软件持久地满足用户的需要。

  • 当软件在使用过程中发现错误时应该加以改正
  • 当环境改变时应该修改软件以适应新的环境
  • 当用户有新要求时应该及时改进软件以满足用户的新需要

通常对维护时期不再进一步划分阶段,但是每一次维护活动本质都是一次压缩和简化了的定义和开发过程。

问题定义

问题定义阶段必须回答的关键问题是:“要解决的问题是什么?” 如果不知道问题是什么就试图解决这个问题,显然是盲目的,只会白白浪费时间和金钱,最终得出的结果很可能是毫无意义的。尽管确切地定义问题的必要性是十分明显的,但是在实践中它却可能是最容易被忽视的一个步骤。

通过对客户的访问调查,系统分析员扼要地写出关于问题性质、工程目标和工程规模的书面报告,经过讨论和必要的修改之后这份报告应该得到客户的确认。

可行性研究
这个阶段要回答的关键问题是:“对于上一个阶段所确定的问题有行得通的解决办法吗?” 为了回答这个问题,系统分析员需要进行一次大大压缩和简化了的系统分析员和设计过程,也就是在较抽象的高层次上进行的分析和设计过程。可行性研究应该比较简短,这个阶段的任务不是具体解决问题,而是研究问题的范围,探索这个问题是否值得去解,是否有可行的解决办法。

可行性研究的结果是客户作出是否继续进行这项工程的决定的重要依据,一般说来,只有投资可能取得较大效益的那些工程项目才值得继续进行下去。可行性研究以后的那些阶段将需要投入更多的人力物力。及时终止不值得投资的工程项目,可以避免更大的浪费。

需求分析
这个阶段的任务仍然不是具体地解决问题,而是准确地确定**“为了解决这个问题,目标系统必须做什么”**,主要是确定目标系统必须具备哪些功能。

用户了解它们所面对的问题,知道必须做什么,但是通常不能完整准确地表达出他们的要求,更不知道怎样利用计算机解决他们的问题;软件开发人员知道怎样用软件实现人们的要求,但是对特定用户的具体要求并不完全清楚。因此,系统分析员在需求分析阶段必须和用户密切配合,充分交流信息,以得出经过用户确认的系统逻辑模型。通常用数据流图、数据字典和简要的算法表示系统的逻辑模型。

在需求分析阶段确定的系统模型是以后设计和实现目标系统的基础,因此必须准确完整地体现用户的要求。这个阶段的一项重要任务,是用正式文档准确地记录对目标系统的需求,这份文档通常称为规格说明书(specification)。

总体设计
这个阶段必须回答的关键问题是:“概括地说,应该怎么实现目标系统?” 总体设计又称为概要设计。

首先,应该设计出实现目标系统的几种可能的方案。通常至少应该设计出低成本、中成本和高成本3种方案。软件工程师应该用适当的表达工具描述每种方案,分析每种方案的优缺点,并在充分权衡各种方案的利弊的基础上,推荐一个最佳方案。此外,还应该制定出实现最佳方案的详细计划。如果客户接受所推荐的方案,则应该进一步完成下述的另一项主要任务。

上述设计工作确定了解决问题的策略及目标系统中应该包含的程序,但是,怎样设计这些程序呢?软件设计的一条基本原理就是,程序应该规模化,也就是说,一个程序应该由若干个规模适中的模块按合理的层次结构组织而成。因此,总体设计的另一项主要任务就是设计程序的体系结构,也就是确定程序由哪些模块组成以及模块间的关系。

详细设计
总体设计阶段以比较抽象概括的方式提出了解决问题的办法。详细设计阶段的任务就是把解决办法具体化,也就是回答下面这个关键问题:“应该怎样具体地实现这个系统呢?”

这个阶段的任务还不是编写程序,而是设计出程序的详细规格说明。这种规格说明的作用很类似于其他工程领域中工程师经常使用的工程蓝图,它们应该包含必要的细节,程序员可以根据它们写出实际的程序代码。

详细设计也称为模块设计,在这个阶段将详细地设计每个模块,确定实现模块功能所需要的算法和数据结构。

编码和单元测试
这个阶段的关键任务是写出正确的容易理解、容易维护的程序模块。

程序员应该根据目标系统的性质和实际环境,选取一种适当的高级程序设计语言(必要时使用汇编语言),把详细设计的结果翻译成用选定的语言书写的程序,并且仔细测试编写出的每一个模块。

综合测试
这个阶段的关键任务是通过各种类型的测试(及相应的调试)使软件达到预订的要求。

最基本的测试是集成测试和验收测试。所谓集成测试是根据设计的软件结构,把经过单元测验的模块按照某种选定的策略装配起来,在装配的过程中对程序进行必要的测试。所谓验收测试则是按照规格说明书的规定(通常在需求分析阶段确定),由用户(或在用户积极参加下)对目标系统进行验收。

必要时还可以再通过现场测试或平行等方法对目标系统进一步测试检验。

为了使用户能够积极参加验收测试,并且在系统投入生产性运行以后能够正确有效地使用这个系统,通常需要以正式的或非正式的方式对用户进行培训。

通过对软件测试结果的分析可以预测软件的可靠性;反之,根据对软件可靠性的要求,也可以决定测试和调试过程什么时候结束。

应该用正式文档资料把测试计划、详细测试方案以及实际测试结果保存下来,作为软件配置的一个组成部分。

软件维护
维护阶段的关键任务是,通过各种必要的维护活动使系统持久地满足用户的需要。

通常有4类维护活动:

改正性维护,也就是诊断和改正正在使用过程中发现的软件错误;
适应性维护,即修改软件以适应环境的变化;
完善性维护,即根据用户的要求改进或扩充软件使它更完善;
预防性维护,即修改软件,为将来的维护活动预先做准备。
虽然没有把维护阶段进一步分成更小的阶段,但是实际上每一项维护活动都应该经过提出维护要求(或报告问题),分析维护要求,提出维护方案,审批维护方案,确定维护计划,修改软件设计,修改程序,测试程序,复查验收等一系列步骤,因此实质上是经历了一次压缩和简化的软件定义和开发的全过程。

每一项维护活动都应该准确地记录下来,作为正式文档资料加以保存。

总结
以上根据应该完成的任务的性质,把软件生命周期划分成8个阶段。在实际从事软件开发工作时,软件开发工作时,软件规模、种类、开发环境及开发时使用的技术方法等因素,都影响阶段的划分。事实上,承担的软件项目不同,应该完成的任务也有差异,没有一个适用于所有软件项目的任务集合。适用于大型复杂项目的任务集合,对于小型简单项目而言往往就过于复杂了。

结构化设计方法

结构化设计方法是在传统软件工程中使用最广的一种设计方法,是基于模块化自顶向下结构化分析等技术基础发展起来的,它为软件设计人员给出了一系列在模块层上进行设计的原理与技术。结构化设计方法的基本思想是将系统设计成相对独立、功能单一模块组成的结构

结构化设计方法通常与结构化分析方法衔接起来共同使用

数据流图为基础得到软件的模块结构

在设计过程中,它从整个程序的结构出发,利用模块结构图表述程序模块之间的关系

总体设计与详细设计

总体设计

  1. 总体设计的任务

在总体设计中有3个主要任务

  1. 将系统划分成物理元素,即程序文件数据库和文件
  2. 设计软件结构,即将需求规格转换为体系结构,划分出程序的模块组成、模块间的相互关系。确定系统的数据结构、文件结构、数据库模式,确定测试方法与策略
  3. 编写总体设计说明书、用户手册、测试计划,用结构图来描述软件结构,选择分解功能与划分模块的设计原则

​ 2.总体设计的概念

模块:又称构建,是指能够单独命名并且独立完成一定功能的程序语句的集合

抽象:即抽出事务的本质特性儿展示不考虑它们的细节

信息隐蔽:每一个模块的实现细节对于其他模块来说是隐蔽的,也就是说模块中所包含的信息不允许其他不需要这些信息的模块调用

模块的独立性:模块独立性的概念是模块化、抽象和信息隐蔽的直接结果

模块的独立性可以用两个定性标准度量:耦合、内聚(高内聚、低耦合)

详细设计

根据“由外向里”的思想方法,概要设计完成之后,就要进行详细设计。详细设计确定每个模块的内部特征,即每个模块部内部的执行过程

  1. 详细设计的原则

(1) 由于详细设计的蓝图是给其他人看的,所以模块的逻辑描述要清晰易读、正确可靠,这样别人才能读懂。这也是常说的清晰第一的设计风格。

(2) 采用结构化设计方法,改善控制结构,降低程序的复杂程度,从而提高程序的可 读性、可测试性、可维护性。其基本内容归纳为如下几点:

​ ① 程序语言中应尽量少用GOTO语句,以确保程序结构的独立性

​ ② 使用单入口单出口的控制结构,确保程序的静态结构与动态执行情况相一致,保证 程序易理解

​ ③ 程序的控制结构一般采用顺序、选择、循环三种结构,确保结构简单

​ ④ 用自顶向下逐步求精方法完成程序设计。

​ ⑤ 经典的控制结构有顺序、IF THEN ELSE分支、DO-WHILE循环。扩展的还有多分支CASE、DO-UNTIL循环结构、固定次数循环DO-WHILE。

面向对象思想

面向对象数据模型(OOM)核心概念

(1)对象和对象标识(OID)。对象是现实世界中实体的模型化,与记录、元组的概念相似,但远比它们复杂。每一个对象都有一个唯一的标识,称为对象标 识。对象标识不等于关系模式中的记录标识,OID是独立于值的,全系统唯一的。

(2)封装(encapsulate)。每一个对象是状态(state)和行为(behavior)的封装。对象的状态是该对象属性的集合,对象的行为是在该对象状态上操作的方 法(程序代码)的集合。被封装的状态和行为在对象外部是看不见的,只能通过显式定义的消息传递来访问。

(3)对象的属性(object attribute)。对象的属性描述对象的状态、组成和特性,对象的某个属性可以是单值或值的集合。对象的一个属性值本身在该属性看 来也是一个对象。

(4)类和类层次(class and class hierarchy)。

​ ① 类。所有具有相同属性和方法集的对象构成了一个对象类。任何一个对象都是某个对象类的一个实例(instance)。对象类中属性的定义域可以是任何 类,包括基本类,如整型、实型和字串等;一般类,包含自身属性和方法类本身。

​ ② 类层次。所有的类组成了一个有根有向无环图,称为类层次(结构)。一个类可以从直接/间接祖先(超类)中继承(inherit)所有的属性和方法,该类 称为子类。

(5)继承(inherit)。子类可以从其超类中继承所有属性和方法。类继承可分为单继承(即一个类只能有一个超类)和多重继承(即一个类可以有多个超类)。

软件测试

1.系统测试的意义、目的及原则

系统测试是为了发现错误而执行程序的过程,成功的测试是发现了至今尚未发现的错误的测试。

测试的目的就是希望能以最少的人力和时间发现潜在的各种错误和缺陷

系统测试是保证系统质量和可靠性的关键步骤,是对系统开发过程中的系统分析、系统设计和实施的最后复查。

根据测试的概念和目的,在进行信息系统测试时应遵循以下基本原则。

(1)应尽早并不断地进行测试。测试不是在应用系统开发完之后才进行的。由于原始问题的复杂性、开发各阶段的多样性以及参加人员之间的协调等因素,使得在开发各个阶段都有可能出现错误。因此,测试应贯穿在开发的各个阶段,尽早纠正错误,消除隐患。

(2)测试工作应该避免由原开发软件的人或小组承担,一方面,开发人员往往不愿否认自己的工作,总认为自己开发的软件没有错误;另一方面,开发人员的错误很难由本人测试出来,很容易根据自己编程的思路来制定测试思路,具有局限性。测试工作应由专门人员来进行,会更客观,更有效。

(3)设计测试方案时,不仅要确定输入数据,而且要根据系统功能确定预期输出结果。将实际输出结果与预期结果相比较就能发现测试对象是否正确。

(4)在设计测试用例时,不仅要设计有效合理的输入条件,也要包含不合理、失效的输入条件。测试的时候,人们往往习惯按照合理的、正常的情况进行测试,而忽略了对异常、不合理、意想不到的情况进行测试,而这些可能就是隐患。

(5)在测试程序时,不仅要检验程序是否做了该做的事,还要检验程序是否做了不该做的事。多余的工作会带来副作用,影响程序的效率,有时会带来潜在的危害或错误。

(6)严格按照测试计划来进行,避免测试的随意性。测试计划应包括测试内容、进度安排、人员安排、测试环境、测试工具和测试资料等。严格地按照测试计划可以保证进度,使各方面都得以协调进行。

(7)妥善保存测试计划、测试用例,作为软件文档的组成部分,为维护提供方便。

(8)测试例子都是精心设计出来的,可以为重新测试或追加测试提供方便。当纠正错误、系统功能扩充后,都需要重新开始测试,而这些工作重复性很高,可以利用以前的测试用例,或在其基础上修改,然后进行测试。

测试方法(黑白盒!)

测试是可以事先计划并可以系统地进行的一系列活动。因此,应该为软件过程定义软件测试模板,即将特定的测试方法和测试用例设计放在一系列的测试步骤中去。

软件测试方法分为静态测试动态测试

(1)静态测试。静态测试是指被测试程序在机器上运行,而是采用人工检测计算机辅助静态分析的手段对程序进行检测。

​ **① 人工检测。**人工检测是不依靠计算机而是靠人工审查程序或评审软件,包括代码检查、静态结构分析和代码质量度量等。

​ **② 计算机辅助静态分析。**利用静态分析工具对被测试程序进行特性分析,从程序中提取一些信息,以便检查程序逻辑的各种缺陷和可疑的程序构造。

(2)动态测试。动态测试是指通过运行程序发现错误。对软件产品进行动态测试时可以采用黑盒测试法和白盒测试法

测试用例的设计如下。

测试用例由测试输入数据和与之对应的预期输出结构组成。在设计测试用例时,应当包括合理的输入条件和不合理的输入条件。

1)用黑盒法设计测试用例

黑盒测试也称为功能测试,在完全不考虑软件的内部结构和特性的情况下,测试软件的外部特性。进行黑盒测试主要是为了发现以下几类错误。

(1)是否有错误的功能或遗漏的功能?

(2)界面是否有误?输入是否正确接收?输出是否正确?

(3)是否有数据结构或外部数据库访问错误?

(4)性能是否能够接受?

(5)是否有初始化或终止性错误?

常用的黑盒测试技术有等价类划分、边值分析、错误猜测和因果图等。

(1)等价类划分。

等价类划分法将程序的输入域划分为若干等价类,然后从每个等价类中选取一个代表性数据作为测试用例。

每一类的代表性数据在测试中的作用等价于这一类中的其他值。

这样就可以用少量代表性的测试用例取得较好的测试效果。

等价类划分有两种不同的情况:有效等价类无效等价类

在设计测试用例时,要同时考虑这两种等价类。

定义等价类的原则如下。

① 在输入条件规定了取值范围或值的个数的情况下,可以定义一个有效等价类和两个无效等价类。

② 在输入条件规定了输入值的集合或规定了“必须如何”的条件的情况下,可以定义一个有效等价类和一个无效等价类。

③ 在输入条件是一个布尔量的情况下,可以定义一个有效等价类和一个无效等价类。

④ 在规定了输入数据的一组值(假定n个),并且程序要对每一个输入值分别处理的情况下,可以定义n个有效等价类和一个无效等价类。

⑤ 在规定了输入数据必须遵守的规则的情况下,可定义一个有效等价类(符合规则)和若干个无效等价类(从不同角度违反规则)。

⑥ 在确知已划分的等价类中,各元素在程序处理中的方式不同的情况下,则应再将该等价类进一步地划分为更小的等价类。

定义好等价类之后,建立等价类表,并为每个等价类编号。在设计一个新的测试用例时,使其尽可能多地覆盖尚未覆盖的有效等价类,不断重复,最后使得所有有效等价类均被测试用例所覆盖。然后设计一个新的测试用例,使其只覆盖一个无效等价类。

(2)边界值划分。

输入的边界比中间更加容易发生错误,因此用边界值分析来补充等价类划分的测试用例设计技术。

边界值划分选择等价类边界的测试用例,注重于输入条件边界适用于输出域测试用例。

对边界值设计测试用例应遵循的原则如下。

① 如果输入条件规定了值的范围,则应取刚达到这个范围的边界的值,以及刚刚超越这个范围边界的值作为测试输入数据。

② 如果输入条件规定了值的个数,则用最大个数、最小个数、比最小个数少1、比最大个数多1的数据作为测试数据。

③ 根据规格说明的每个输出条件,使用上述两条原则。

④ 如果程序的规格说明给出的输入域或输出域是有序集合,则应选取集合的第一个元素和最后一个元素作为测试用例。

⑤ 如果程序中使用了一个内部数据结构,则应当选择这个内部数据结构边界上的值作为测试用例。

⑥ 分析规格说明,找出其他可能的边界条件。

(3)错误推测。

错误推测是基于经验和直觉推测程序中所有可能存在的各种错误,从而有针对性地设计测试用例的方法。

其基本思想是列举出程序中所有可能有的错误和容易发生错误的特殊情况,根据它们选择测试用例。

(4)因果图。

因果图法是从自然语言描述的程序规格说明中找出因(输入条件)和果(输出或程序状态的改变),通过因果图转换为判定表

利用因果图导出测试用例需要经过以下几个步骤。

① 分析程序规格说明的描述中,哪些是原因,哪些是结果。原因常常是输入条件或是输入条件的等价类,而结果是输出条件。

② 分析程序规格说明的描述中语义的内容,并将其表示成连接各个原因与各个结果的“因果图”。

③ 标明约束条件。由于语法或环境的限制,有些原因和结果的组合情况是不可能出现的。为表明这些特定的情况,在因果图上使用若干个标准的符号标明约束条件。

④ 把因果图转换成判定表。

⑤ 为判定表中每一列表示的情况设计测试用例。

这样生成的测试用例(局部,组合关系下的)包括了所有输入数据的取“真”和取“假”的情况,构成的测试用例数据达到最少,且测试用例数据随输入数据数目的增加而增加。

2)用白盒法设计测试用例

白盒测试也称为结构测试,根据程序的内部结构和逻辑来设计测试用例,对程序的路径和过程进行测试,检查是否满足设计的需要。

白盒测试常用的技术是逻辑覆盖、循环覆盖和基本路径测试

(1)逻辑覆盖。

逻辑覆盖考察用测试数据运行被测程序时对程序逻辑的覆盖程度

主要的逻辑覆盖标准有语句覆盖判定覆盖条件覆盖判定/条件覆盖条件组合覆盖路径覆盖6种。

① 语句覆盖。语句覆盖是指选择足够的测试数据,使被测试程序中每条语句至少执行一次。语句覆盖对程序执行逻辑的覆盖很低,因此一般认为它是很弱的逻辑覆盖。

② 判定覆盖。判定覆盖是指设计足够的测试用例,使得被测程序中每个判定表达式至少获得一次“真”值和“假”值,或者说是程序中的每一个取“真”分支和取“假”分支至少都通过一次,因此判定覆盖也称为分支覆盖。判定覆盖要比语句覆盖更强一些。

③ 条件覆盖。条件覆盖是指构造一组测试用例,使得每一判定语句中每个逻辑条件的各种可能的值至少满足一次

④ 判定/条件覆盖。判定/条件覆盖是指设计足够的测试用例,使得判定中每个条件的所有可能取值(真/假)至少出现一次,并使每个判定本身的判定结果(真/假)也至少出现一次

⑤ 条件组合覆盖。条件组合覆盖是指设计足够的测试用例,使得每个判定中条件的各种可能值的组合都至少出现一次。满足条件组合覆盖的测试用例是一定满足判定覆盖、条件覆盖和判定/条件覆盖的。

⑥ 路径覆盖。路径覆盖是指覆盖被测试程序中所有可能的路径

(2)循环覆盖。

执行足够的测试用例,使得循环中的每个条件都得到验证。

(3)基本路径测试。

基本路径测试法是在程序控制流图的基础上,通过分析控制流图的环路复杂性,导出基本可执行路径集合,从而设计测试用例。

设计出的测试用例要保证在测试中程序的每一条独立路径都执行过,即程序中的每条可执行语句至少执行一次

此外,所有条件语句的真值状态和假值状态都测试过。

路径测试的起点程序控制流图。程序控制流图中的节点代表包含一个或多个无分支的语句序列边代表控制流

白盒测试的原则如下。

(1)程序模块中的所有独立路径至少执行一次

(2)在所有的逻辑判断中,取“真”和取“假”的两种情况至少都能执行一次

(3)每个循环都应在边界条件一般条件各执行一次

(4)测试程序内部数据结构的有效性等。

调试

调试发生在测试之后,其任务是根据测试时所发现的错误,找出原因和具体的位置,进行改正。调试工作主要由程序开发人员来进行,谁开发的程序就由谁来进行调试。

目前常用的调试方法有如下几种。

(1)试探法。调试人员分析错误的症状,猜测问题的所在位置,利用在程序中设置输出语句,分析寄存器、存储器的内容等手段来获得错误的线索,一步步地试探和分析出错误所在。这种方法效率很低,适合于结构比较简单的程序。

(2)回溯法。调试人员从发现错误症状的位置开始,人工沿着程序的控制流程往回跟踪代码,直到找出错误根源为止。这种方法适合于小型程序,对于大规模程序,由于其需要回溯的路径太多而变得不可操作。

(3)对分查找法。这种方法主要用来缩小错误的范围,如果已经知道程序中的变量在若干位置的正确取值,可以在这些位置上给这些变量以正确值,观察程序运行输出结果,如果没有发现问题,则说明从赋予变量一个正确值开始到输出结果之间的程序没有错误,问题可能在除此之外的程序中。否则错误就在所考察的这部分程序中,对含有错误的程序段再使用这种方法,直到把故障范围缩小到比较容易诊断为止。

(4)归纳法。归纳法就是从测试所暴露的问题出发,收集所有正确或不正确的数据,分析它们之间的关系,提出假想的错误原因,用这些数据来证明或反驳,从而查出错误所在。

(5)演绎法。根据测试结果,列出所有可能的错误原因。分析已有的数据,排除不可能和彼此矛盾的原因。对其余的原因,选择可能性最大的,利用已有的数据完善该假设,使假设更具体。用假设来解释所有的原始测试结果,如果能解释这一切,则假设得以证实,也就找出错误;否则,要么是假设不完备或不成立,要么有多个错误同时存在,需要重新分析,提出新的假设,直到发现错误为止。

posted on 2022-03-10 07:45  JavaCoderPan  阅读(49)  评论(0编辑  收藏  举报  来源

导航