《软件工程》期末复习笔记(华科软院)

1.TDD测试驱动开发实验

《软件工程》期末复习笔记(华科软院)_第1张图片

测试驱动开发,英文全称Test-Driven Development,简称TDD,是一种不同于传统软件开发流程的新型的开发方法。它要求在编写某个功能的代码之前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进行。这有助于编写简洁可用和高质量的代码,并加速开发过程。

《软件工程》期末复习笔记(华科软院)_第2张图片

《软件工程》期末复习笔记(华科软院)_第3张图片

 

  1. 测试驱动开发(Test-Driven Development):是敏捷开发中的一项核心实践和技术,也是一种设计方法论。是极限编程的一个重要组成部分,它的基本思想就是在开发功能代码之前,先编写测试代码。

  2. TDD的原理:在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代理。

  3. TDD基本思路:通过测试来推动整个开发的进行,但测试驱动开发并不只是单纯的测试工作,而是把 需求分析,设计,质量控制量化的过程。

  4. TDD的重要目的:不仅仅是测试软件,测试工作保证代码质量仅仅是其中一部分,而且是在开发过程中帮助客户和程序员出去模棱两可的需求。TDD首先考虑使用需求(对象、功能、过程、接口等),主要是变成测试用例框架对功能的过程和接口进行设计,而测试框架可以持续验证。

  5. TDD优缺点:

  a.优点:(在任意一个开发点都可以拿出一个可以使用,含少量bug并具有一定功能的产品。)

  『充满吸引力的优点』

  ① 完工时完工。表明我可以很清楚的看到自己的这段工作已经结束了,而传统的方式很难知道什么时候编码工作结束了。

  ② 全面正确的认识代码和利用代码,而传统的方式没有这个机会。

  ③ 为利用你成果的人提供Sample,无论它是要利用你的源代码,还是直接重用你提供的组件。

  ④ 开发小组间降低了交流成本,提高了相互信赖程度。

  ⑤ 避免了过渡设计。

  ⑥ 系统可以与详尽的测试集一起发布,从而对程序的将来版本的修改和扩展提供方便。

  ⑦ TDD给了我们自信,让我们今天的问题今天解决,明天的问题明天解决,今天不能解决明天的问题,因为明天的问题还没有出现(没有TestCase),除非有TestCase否则我决不写任何代码;明天也不必担心今天的问题,只要我亮了绿灯。

  『不显而易见的优点』

  ① 逃避了设计角色。对于一个敏捷的开发小组,每个人都在做设计。

  ② 大部分时间代码处在高质量状态,100%的时间里成果是可见的。

  是由于可以保证编写测试和编写代码的是相同的程序员,降低了理解代码所花费的成本。

  ③ 为减少文档和代码之间存在的细微的差别和由这种差别所引入的Bug作出杰出贡献。

  ④ 在预先设计和紧急设计之间建立一种平衡点,为你区分哪些设计该事先做、哪些设计该迭代时做提供了一个可靠的判断依据。

  『有争议的优点』

  ① 事实上提高了开发效率。每一个正在使用TDD并相信TDD的人都会相信这一点,但观望者则不同,不相信TDD的人甚至坚决反对这一点,这很正常,世界总是这样。

  ② 发现比传统测试方式更多的Bug。

  ③ 使IDE的调试功能失去意义,或者应该说,避免了令人头痛的调试和节约了调试的时间。

  ④ 总是处在要么编程要么重构的状态下,不会使人抓狂。(两顶帽子)

  ⑤ 单元测试非常有趣。

  b.缺点:增加代码量。测试代码是系统代码的两倍或更多。

  6. 测试驱动开发的效果:以可执行的形式文档化你的需求,迫使你分清职责隔离依赖以驱动你的设计,编织安全网以便将Bug扼杀在摇篮的状态,防止其逃逸。

  7. 测试人员在新的特性还没开发完成之前做什么?

  除了提前编写测试用例,而需要测试人员一起参加一项重要的活动,就是参与特性验收条件的制定。

  8. 测试驱动开发的基本过程

  a. 快速新增一个测试用例新的TestCase

  b. 编译所有代码,刚刚写的那个测试很可能编译不通过原始的TODO List

  c. 做尽可能少的改动,让编译通过Interface

  d. 运行所有的测试,发现最新的测试不能编译通过-(Red Bar)

  e. 做尽可能少的改动,让测试通过Implementation

  f. 运行所有的测试,保证每个都能通过 -(Green Bar)

  g. 重构代码,以消除重复设计Clean Code That Works

  9. TDD与AMDD(敏捷模型驱动开发)相比

  ·TDD缩短了编程反馈周期,而AMDD缩短了建模反馈周期

  ·TDD提供详细规范(测试),而AMDD提供一般规范(数据模型)

  ·TDD有助易于开发中编写搞质量代码,而AMDD有助于在项目中桶项目负责人和开发人员进行有效地沟通

  ·TDD能对你开发的软件有一个具体形态描述,AMDD能让你的团队,包括项目负责人,向着一个共有的目标前进;

  ·TDD提供了具体的文档的具体反馈,而AMDD对具体文档的允许口头反馈(具体反馈需要程序员在代码中证明,而那样就是非敏捷模型的技术了)

  ·TDD可同构关注代码的调用和可测试来看你的设计是否整洁,而AMDD提供一个机会让你在写代码之前思考

  ·TDD是非可视化的,而AMDD是可视化的

  ·两种技术对传统开发人员来说都是新的,搞不好会让他们不爽

  ·两种结束都支持螺旋式开发

10. TDD = TFD (Test First Development ) + 重构

 

原理:

测试驱动开发的基本思想就是在开发功能代码之前,先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。然后循环进行添加其他功能,直到完全部功能的开发。

我们这里把这个技术的应用领域从代码编写扩展到整个开发过程。应该对整个开发过程的各个阶段进行测试驱动,首先思考如何对这个阶段进行测试、验证、考核,并编写相关的测试文档,然后开始下一步工作,最后再验证相关的工作。下图是一个比较流行的测试模型:V测试模型。

《软件工程》期末复习笔记(华科软院)_第4张图片

在开发的各个阶段,包括需求分析、概要设计、详细设计、编码过程中都应该考虑相对应的测试工作,完成相关的测试用例的设计、测试方案、测试计划的编写。这里提到的开发阶段只是举例,根据实际的开发活动进行调整。相关的测试文档也不一定是非常详细复杂的文档,或者什么形式,但应该养成测试驱动的习惯。

关于测试模型,还有X测试模型。这个测试模型,我认为,是对详细阶段和编码阶段进行建模,应该说更详细的描述了详细设计和编码阶段的开发行为。及针对某个功能进行对应的测试驱动开发。

《软件工程》期末复习笔记(华科软院)_第5张图片

 

原则:

测试隔离。不同代码的测试应该相互隔离。对一块代码的测试只考虑此代码的测试,不要考虑其实现细节(比如它使用了其他类的边界条件)。

一顶帽子。开发人员开发过程中要做不同的工作,比如:编写测试代码、开发功能代码、对代码重构等。做不同的事,承担不同的角色。开发人员完成对应的工作时应该保持注意力集中在当前工作上,而不要过多的考虑其他方面的细节,保证头上只有一顶帽子。避免考虑无关细节过多,无谓地增加复杂度。

测试列表。需要测试的功能点很多。应该在任何阶段想添加功能需求问题时,把相关功能点加到测试列表中,然后继续手头工作。然后不断的完成对应的测试用例、功能代码、重构。一是避免疏漏,也避免干扰当前进行的工作。

测试驱动。这个比较核心。完成某个功能,某个类,首先编写测试代码,考虑其如何使用、如何测试。然后在对其进行设计、编码。

先写断言。测试代码编写时,应该首先编写对功能代码的判断用的断言语句,然后编写相应的辅助语句。

可测试性。功能代码设计、开发时应该具有较强的可测试性。其实遵循比较好的设计原则的代码都具备较好的测试性。比如比较高的内聚性,尽量依赖于接口等。

及时重构。无论是功能代码还是测试代码,对结构不合理,重复的代码等情况,在测试通过后,及时进行重构。关于重构,我会另撰文详细分析。

小步前进。软件开发是个复杂性非常高的工作,开发过程中要考虑很多东西,包括代码的正确性、可扩展性、性能等等,很多问题都是因为复杂性太大导致的。极限编程提出了一个非常好的思路就是小步前进。把所有的规模大、复杂性高的工作,分解成小的任务来完成。对于一个类来说,一个功能一个功能的完成,如果太困难就再分解。每个功能的完成就走测试代码-功能代码-测试-重构的循环。通过分解降低整个系统开发的复杂性。这样的效果非常明显。几个小的功能代码完成后,大的功能代码几乎是不用调试就可以通过。一个个类方法的实现,很快就看到整个类很快就完成啦。本来感觉很多特性需要增加,很快就会看到没有几个啦。你甚至会为这个速度感到震惊。(我理解,是大幅度减少调试、出错的时间产生的这种速度感)

 

重构的目的是什么?

为了换新框架增长技能还是对项目真的有益

现有的架构是否严重的制约了后续功能的开发和性能支撑?

如果是,那重构就非常有必要。在项目进行到某一阶段的时候,由于产品的策略调整,会带动功能在原有的基础上有较大改动。如果现有架构无法满足新策略,那么就到了必须要重构的时候。但前提是重构后的代码结构确实要优于现有的,所以重构前要做充分的评审。

是否有时间,有人力去重构?

即使满足上面的第一条件,但是项目上根本就不给足够的时间和人员去做这个事情,那也是无法完成的。从新架构的设计到评审,需要花费很多的时间。在重构的过程中,还得将原有的功能保质的迁移过来,又得花费很多的时间。后面还得加上回归测试的时间等等。这些成本的消耗,老板是否买单呢?

重构是一个比较重大的决定,牵扯到项目的方方面面。不是可以由技术人员单方面去决定是否能重构的。当然,如果真的很闲,重构不失为一个充实工作,提升技术的好方式。

 

什么是重构

重构,用最简单的一句话说:就是要在不改变系统功能的情况下,对系统的内部结构进行重新调整。重构的最直接目的在于改进软件系统的内部架构。一个好的结构可以更加适应于需求的变化,更好的满足客户的需求,最大限度的延长软件系统的生命周期。

 

为什么要重构

在不改变系统功能的情况下,改变系统的实现方式。为什么要这么做?投入精力不用来满足客户关心的需求,而是仅仅改变了软件的实现方式,这是否是在浪费客户的投资呢?

重构的重要性要从软件的生命周期说起。软件不同与普通的产品,他是一种智力产品,没有具体的物理形态。一个软件不可能发生物理损耗,界面上的按钮永远不会因为按动次数太多而发生接触不良。那么为什么一个软件制造出来以后,却不能永远使用下去呢?

对软件的生命造成威胁的因素只有一个:需求的变更。一个软件总是为解决某种特定的需求而产生,时代在发展,客户的业务也在发生变化。有的需求相对稳定一些,有的需求变化的比较剧烈,还有的需求已经消失了,或者转化成了别的需求。在这种情况下,软件必须相应的改变。

考虑到成本和时间等因素,当然不是所有的需求变化都要在软件系统中实现。但是总的说来,软件要适应需求的变化,以保持自己的生命力。

这就产生了一种糟糕的现象:软件产品最初制造出来,是经过精心的设计,具有良好架构的。但是随着时间的发展、需求的变化,必须不断的修改原有的功能、追加新的功能,还免不了有一些缺陷需要修改。为了实现变更,不可避免的要违反最初的设计构架。经过一段时间以后,软件的架构就千疮百孔了。bug越来越多,越来越难维护,新的需求越来越难实现,软件的构架对新的需求渐渐的失去支持能力,而是成为一种制约。最后新需求的开发成本会超过开发一个新的软件的成本,这就是这个软件系统的生命走到尽头的时候。

重构就能够最大限度的避免这样一种现象。系统发展到一定阶段后,使用重构的方式,不改变系统的外部功能,只对内部的结构进行重新的整理。通过重构,不断的调整系统的结构,使系统对于需求的变更始终具有较强的适应能力。

 

拒绝变化 VS 拥抱变化

按照传统的软件设计方式,软件的生产分为需求调查、概要设计、详细设计、编码、单体测试、联合测试、现场部署几个阶段。虽说这几个阶段是可以互相渗透,但是总的来说是有一定次序的,前一个阶段的工作是后一个阶段工作的基础。这就向下面这样一种V形的模式:

《软件工程》期末复习笔记(华科软院)_第6张图片

往下的方向将系统进行分解,往上的方向将系统进行整合。这样的开发形式将软件开发分为设计前和设计后两个阶段,开发过程中存在一个重要的“里程碑”——设计说明书的。在设计说明书完成前,工程处于“设计”阶段,而在设计说明书完成之后,工程则进入“实施”阶段。一旦到了实施阶段,任何需求或者设计上的变更都是非常困难的,需要花费大量的成本。通常为了保证工程的顺利实施,开发人员常有这样一种冲动:按住客户的手,在需求说明书上签字。并且告诉客户:“从今天开始,任何需求变更都要停止,直到我们把现在这个东西做完。”这是一种拒绝变化的开发方式。

软件系统要保持与企业的目标一致。时代在发展,人们的要求在不断的提高,客户的业务在不断的发展。在这种情况下,传统的先设计、再施工的V形式已经不能适应日益复杂的业务需要。软件工程逐渐演化成下面这样的过程:

《软件工程》期末复习笔记(华科软院)_第7张图片

说明一下:

1、软件开发的目标要与企业目标保持一致,一个开发周期不宜时间过长,一般控制在半年到一年。系统部署后,并不意味着开发工作结束了,而是进入了下一个周期。

2、工程以循环迭代的方式前进,这并不意味轻视了设计,不是要搞边调研、边设计、边施工的“三边”工程,相反,是更加重视设计的地位。软件开发的全过程都需要设计,软件开发是“持续设计”的过程。同时,设计工作也不只是简单过程分解、任务分配,而是概念设计、逻辑设计、物理设计等各个方面互相交织、齐头并进。

传统的软件开发方式使用一种非常理想化的流程——先与客户讨论项目的范围,确定哪些需要做,哪些不需要做,然后规划一个完美的设计,不仅可以满足现在的需求,还能很好的适应未来的需求,设计完成后开始编码,然后测试组装,送到现场安装调试运行。这一系列过程就类似与发射一颗炮弹,首先要找到目标,然后根据地形、风力、目标的位置、移动速度等各种因素,计算提前量、炮弹发射的角度,计算出一个抛物线轨道,最后在合适的时间把炮弹发射出去。这一切都符合最正确的物理定律,一切都听起来很理想。如果没有意外条件,当然是可以击中目标的。但是炮弹一旦发射出去,一切就失去了控制,任何环境的变化都会造成偏离目标。尤其是对于一个运动的目标来说,计算过程十分复杂,很多情况下只能靠人估计。对于不规则的运动目标只能碰碰运气。这样的方式,命中率是很低的。

新的软件开发过程不追求完美的、长期的、理想的计划,更加重视实际情况,重视需求的变化,提倡采用短期的计划。这是一种拥抱变化的过程。就象是在炮弹上安装了一个反馈装置,锁定目标后,确保大方向的正确,然后就将炮弹发射出去。炮弹在运行过程中不断的将目标位置偏移量输入反馈电路,根据反馈输出调整自己的运行路线,无限的逼近目标。这样,炮弹就拥有了制导能力,命中率大大增加。

重构就可以增加工程的调整能力,他可以把产品回复到一个稳定的状态,可以基于这个状态达到下一个目标。如此反复前进,更好的满足客户的需求。

保持兼容性

重构的目的在于改变系统的实现方式,而不改变原有的功能。这个过程中,判断兼容性就十分的重要。一个子系统、模块、类、函数是否与升级前保持兼容,如何判断这个兼容性,如何保持这个兼容性,这关系到重构的成本和重构的可能性。

程序员学习写程序代码时,会发现随着程序代码愈来愈多,许多的程序代码不断重复出现和被使用,因此很自然的开始使用例程、子程序或是过程、函数等机制帮助我们进行程序代码整理的工作。于是很自然的,字体的分析方式演化成这个样子:将客户的需求过程进行分解,一步一步的分解,直到可以直接的实现他。这就是面向过程的分析方式。

面向过程的分析方式对变化的能力是很弱的。为什么呢?因为面向过程的分析方式很容易造成一种倾向——不区分行动的主体。一个过程是没有主体的,他不是在为自己工作,而是在为“别人”工作。当我们修改了一个过程之后,我们很难判断这个过程是否保持向后兼容,其他过程会不会受到影响。因为这个过程对外界有意义的不仅是他的输入和输出,还包括每一步过程,每一步过程都可能含有一个非常隐讳的业务意义,对外界产生影响。

因此,修改一个过程是非常困难的,通常升级一个面向过程的系统,可以采用两种方式:

1、写新的过程;

2、在原有的过程上加开关参数。

除此以外的升级办法都很难保证原过程和新过程的兼容性,容易造成错误。

为了更好的保证升级后模块的兼容性,应该采用面向对象的分析方式。按照这样的分析方式,一个对象为“自己”工作,他有完整的、独立的业务含义。对象之间通过接口发生联系,一个对象对外界有影响的部分只有接口,至于他做什么、如何做、做的对不对,则不是外界需要关心的事情。

判断一个接口升级后是否保持兼容性就是一件比较容易的事情了。我们可以判断接口的输入输出是否符合下面两条规则:

1、升级后的输入是升级前的输入的超级;

2、升级后的输出是升级前的输出的子集。

只要符合这两点,他就仍然可以在系统中运行,不会对其他对象造成危害。在实际的工程中,判断这个兼容性有一个更好的办法:自动化的单元测试。

在重构的过程中,自动化的单元测试是非常好的保障。采用自动化的单元测试,不断运行测试,可以保证系统的结构改变的过程中,业务行为不发生改变。

你可能感兴趣的:(实验,考试与课设)