一个复杂的软件不但要有合理的软件架构(Software Architecture)、软件设计与实现(Software Design, Implementation and Debug),还要有各种文件和数据来描述各个程序文件之间的依赖关系、编译参数、链接参数,等等。
源代码管理——Source Code Control
配置管理——Software Configuration Management
我们还有一系列的工具和程序来保证程序的正确性,这些工具流程和程序本身应该更正确,才能保证别的软件的质量。这就是质量保障(Quality Assurance),具体的验证过程叫做软件测试(Testing)。软件团队要从需求分析(Requirement Analysis)开始,把合适的需求梳理出来,然后逐步展开后续工作,如设计(软件架构)、实现(写数据结构和算法)、测试,到最后发布软件。软件团队的人员也会流动,新的成员要尽快读懂已有的程序,了解程序的设计,这叫程序理解(Pro-gram Comprehension)。软件在运行过程中还会出这样那样的问题,也许我们要时不时给软件打一个补丁,或者维护众多的服务器,团队的新老成员要一起工作,修复各种各样的问题,这叫软件维护(Software Maintenance),或者服务运营(Service Operation)。这一系列过程就是软件的生命周期(Software Life Cycle,SLC),有人得负责软件项目的管理(Project Management)。一个好的软件,即使功能和同类软件区别不大,但是会让人感觉到非常好用。这就是软件的用户体验(User Experience)。
软件工程是把系统的、有序的、可量化的方法应用到软件的开发、运营和维护上的过程。
软件是可以运行在计算机及电子设备中的指令和数据的有序集合。
软件开发过程有什么特别的难题?
- 复杂性(Complexity)
- 不可见性(Invisibility)
- 易变性(Changeability)
- 服从性(Conformity)
- 非连续性(Discontinuity)
通过理论学习和具体项目的练习,做到下面三点:
- 研发出符合用户需求的软件说明:要通过实际的工作收集、推导、提炼需求,并在软件发布后通过实际数据验证需求的确被满足了。需求来自于实际,而不是自己想象出来的"需求"或者人云亦云的需求
- 通过一定的软件流程,在预计的时间内发布"足够好"的软件说明
-
并通过数据和其他方式展现所开发的软件是可以维护和继续发展的说明
例如,对用户需求有详细的分析,包括对将来这类软件发展的趋势的分析。主要功能都有设计文档,源代码完整,有修改记录,并有最后版本。关键模块有可以执行的单元测试、压力测试脚本,等等。对于已知的bug和将来的工作都有详细的记录。
PSP——Personal Software Process,个人软件开发流程
创建单元测试函数的主要步骤是:
- 设置数据
- 使用被测试类型的功能
- 比较实际结果和预期的结果
代码覆盖报告——Code Coverage Report
在写技术模块的规格说明书(Specification)的时候,要越详细越好,最好各项要求都可以表示为一个单元测试用例。
如果你写的模块会有不同的人,在不同的时间使用,那你最好把你这一"单元"要做的事,以及它不能做的事,用单元测试清晰地表达出来。同时,单元测试也能帮助程序员记录这个模块的历史和设计变更的理由。
验证单元测试好坏的一系列标准:单元测试应该在最基本的功能/参数上验证程序的正确性。单元测试应该测试程序中最基本的单元—如在C++/C#/Java中的类,在此基础上,可以测试一些系统中最基本的功能点(这些功能点由几个基本类组成)。从面向对象的设计原理出发,系统中最基本的功能点也应该由一个类及其方法来表现。单元测试要测试API中的每一个方法及每一个参数
在设计的时候就写好单元测试,这样单元测试就能体现API的语义,如果没有单元测试,语义的准确性就不能得到保障,以后会产生歧义。
单元测试过后,机器状态保持不变。这样就可以不断地运行单元测试,如果单元测试创建了临时的文件或目录,应该在Teardown阶段删掉。
如果单元测试在数据库中创建或修改了记录,那么也许要删除或恢复这些记录,或者每一个单元测试使用一个新的数据库,这样可以保证单元测试不受以前单元测试实例的干扰。
元测试要快——一个测试的运行时间是几秒钟,而不是几分钟。
问:如果用随机数以增加测试的真实性,好么?
答:一般情况下不好,如果某个随机数导致程序出错,但是下一次运行又不能重复这一错误,则于事无补。
独立性—单元测试的运行/通过/失败不依赖于别的测试,可以人为构造数据,以保持单元测试的独立性。
一般情况下,单元测试中的模块可以直接引用其他的模块,并期待其他的模块能返回正确的结果。如果其他的模块很不稳定,或者其他模块运行比较费时(如进行网络操作),而且对于本模块的正确性并不起关键的作用,这时可以人为地构造数据,以保证单元测试的独立性。单元测试应该覆盖所有代码路径。单元测试应覆盖所测单元的所有代码路径,包括错误处理路径。为了保证代码覆盖率,单元测试必须测试公开的和私有的函数/方法。
100%的代码覆盖率并不等同于100%的正确性!分析如下:
- 代码覆盖率对于"应该写但是没有写的代码"无能为力。例如代码申请了内存或其他资源,但并没有释放。又如,代码中并没有处理错误情况。就像没有处理和文件、网络相关的一些异常情况,例如文件不存在、权限有问题,等等。
- 代码中有效能问题,虽然代码执行了,并且也正确地返回了,但是代码效率非常低。有些情况下,可以针对代码效率写一个单元测试。
- 多线程环境中的同步问题,这个问题和代码执行的时序、共享资源的锁定有关。
- 其他与外部条件相关的问题(例如与设备、网络相关的问题)。
单元测试应该集成到自动测试的框架中。另一个重要的措施是要把单元测试自动化,这样每个人都能随时、随地运行单元测试。
单元测试必须和产品代码一起保存和进行版本维护。
回归测试(Regression Test)。Regress 的英语定义是:return to a worse or less developed state,是倒退、退化、退步的意思。在软件项目中,如果一个模块或功能以前是正常工作的,但是在一个新的构建中出了问题,那么这个模块就出现了一个"退步"(Regression),从正常工作的稳定状态退化到不正常工作的不稳定状态。
在一个模块的功能逐步完成的同时,与此功能有关的测试用例也同样在完善中。一旦有关的测试用例通过,我们就得到了此模块的功能基准线(Baseline),一个模块的所有单元测试就是这个模块最初的Baseline。