了解软件工程
软件工程
软件开发生命周期
要求和规范🔽
设计:建筑🔽
🟢 架构模式
🟢 架构结论
设计:模块化
🟢 模块化的目标
🟢 耦合介绍
🟢 凝聚力介绍
🟢 凝聚力结论
实施与部署
测试概述
🟢 错误
软件维护
🟢 维护类型
🟢 版本控制系统
🟢 结论
软件工程
- 它是一种系统的、规范的、经济高效的软件开发技术。
- 它是一种开发软件的工程方法。
🟢软件工程流程
1) 软件开发生命周期
2) 需求与规范 (SRS)
3) 架构
4) 软件设计流程
5) 实施
6) 开发
7) 测试
软件开发生命周期
它是软件工程行业用于设计、开发和测试高质量软件的过程,SDLC 倾向于生产满足客户期望的高质量软件。
🟢SDLC 模型
有多种流程、方法和框架,涵盖组织可以使用的特定规定步骤,已开发了许多 SDLC 模型来实现不同的所需目标,这些模型指定了流程的各个阶段及其执行的顺序。
- 瀑布模型
- V 模型
- 增量模型
- 迭代模型
- 螺旋模型
- RAD 模型
- 原型模型
- 敏捷模型
🟢瀑布模型
它将项目活动分解为线性连续阶段,其中每个阶段都依赖于前一个阶段的可交付成果。
🟢增量模型
增量构建模型是一种软件开发方法,其中模型以增量方式设计、实现和测试(每次添加一点),直到产品完成(当产品满足所有要求时,产品定义为完成)
每次迭代都要经过需求、设计、编码和测试阶段,并且系统的每个后续版本都会在前一个版本上添加功能,直到所有设计的功能都得到实现。
🟢迭代模型
迭代模型是软件开发生命周期 (SDLC) 的一种特定实现,它侧重于初始的简化实现,然后逐步增加复杂性和更广泛的功能集,直到最终系统完成。在讨论迭代方法时,“增量开发”的概念也经常被广泛地互换使用,它描述了在每次新迭代的设计和实现过程中所做的增量修改。
与传统的瀑布模型不同,瀑布模型注重严格的逐步开发过程,而迭代模型则更倾向于被视为一个循环过程。在初始规划阶段之后,少数几个阶段会不断重复,每次完成周期都会逐步改进和迭代软件。在每次迭代中,改进之处都能被快速识别和实施,从而使下一次迭代至少比上一次略有改进。
🟢螺旋模型
螺旋模型将迭代开发的理念与瀑布模型的系统性和可控性相结合。螺旋模型融合了迭代开发过程模型和顺序线性开发模型(即瀑布模型),并高度重视风险分析。它允许产品在螺旋的每次迭代中逐步发布或逐步改进。
🟢敏捷模型
敏捷的含义是快速或灵活。“敏捷过程模型”是指一种基于迭代开发的软件开发方法。敏捷方法将任务分解为较小的迭代或部分,这些迭代或部分无需直接涉及长期规划。项目范围和需求在开发过程开始时就已确定。关于迭代次数、持续时间以及每次迭代范围的计划均已提前明确定义。
在敏捷开发模型中,每次迭代都被视为一个短时间“框架”,通常持续一到四周。将整个项目划分为更小的部分有助于最大限度地降低项目风险,并缩短项目交付时间。每次迭代都涉及一个团队完成完整的软件开发生命周期,包括规划、需求分析、设计、编码和测试,最终向客户展示出可用的产品。
敏捷是一种方法论,一种思维方式,它本身并不是一种模型。
敏捷开发模式的诞生是因为软件开发的范围正在发生变化。瀑布式开发方法本身并不坏,只是速度慢,而且不适用于解决许多现代问题。
这主要是因为现代系统的复杂性,很难做到100%的规划。这意味着在某个时候,可能需要进行变更,生产将暂停,直到所有文档都更新完毕。
然而,有了敏捷,我们可以行动得更快一些。我们一直在寻找变化并进行调整。
敏捷宣言
敏捷宣言是一套敏捷开发的指导原则。它们是:
🔺个人和互动高于流程和工具。
🔺可运行的软件胜过详尽的文档。
🔺客户合作重于合同谈判。
🔺响应变化而不是遵循计划。
所有敏捷模型都基于这套规则而创建。需要注意的是,我们并非抛弃流程和工具,也并非没有计划。我们只是在开发过程中设定优先级。我们希望开发出所需的软件。为此,我们需要保持沟通渠道畅通,并与所有相关人员合作。
通过这份宣言,创建了符合这些规则的模型。
敏捷模型
🔺Scrum - Scrum 专注于冲刺。冲刺指的是 1-4 周的生产周期。我们拿到软件,设定一个目标,然后按照这个目标进行构建。
一旦我们完成一个冲刺,我们就会回到利益相关者那里,向他们展示软件,接受建议,然后进入下一个冲刺。
所有这些让我们保持灵活性。我们几乎每两周都会通过这种模式与利益相关者沟通一次。这意味着我们能够不断采纳这些建议并调整开发方向。
为了更深入地了解
🔺看板 - 看板系统是一种优化系统。通过看板,我们可以分析生产流程,找出生产放缓的原因。
为此,我们通常会使用某种可视化流程图。我们将项目分解成多个任务,并绘制成图表。这样,我们就能发现生产过程中是否有任何环节出现进度放缓。例如,可能是我们的审核流程拖慢了进度,也可能是计划环节导致进度放缓。
借助看板,我们尝试对流程进行细微调整,使其朝着正确的方向发展。我们希望与现有流程协同工作,而不是取代它。
🔺精益创业 - 精益创业是在投入开发资金之前先测试市场的一种方式。我们会创建一个最小可行产品 (MVP),看看是否有人对我们正在开发的产品感兴趣。
生产成本很高。如果我们在一个项目上花了50万美元,结果却发现没人对这个产品感兴趣,那就太糟糕了。
举个例子,我们可以创建一个销售特定产品的网站。让它正常运行,让人们可以将该商品放入购物车。然后,当他们购买时,让它友好地提示用户此功能即将推出。之后,我们会追踪有多少用户真正有兴趣在我们的网站上购买产品。
如果我们很感兴趣,那么我们就可以继续生产。如果我们没有那么多兴趣,那么也许我们需要重新考虑我们的设计。
🟢瀑布模型示例
1)要求
- 收集电子邮件和消息
- 存储在数据库中
- 防止错误输入
2)设计
- 使用 HTML 和 CSS 进行构建
- JS进行输入验证
- MySQL 作为后端
3)开发
- 实际编码
- 记录
4)测试
- 表单是否收集输入
- 它会发送信息到数据库吗
- 它能防止错误输入吗
5)部署
- 在 AWS 上部署
6)维护
- 修复错误
- 添加功能
要求和规范🔽
🟢要求
一种确定软件应该做什么的确切规范的方法,定义系统的目标。
-
用户对系统要求的非技术性定义。
-
任何人都能理解。
-
例如 - 能够提交医疗治疗申请表。
🟢规格
我们可以用一种更技术性的方式来明确了解软件应该做什么,我们可以说这是需求的技术方面。
-
用户对系统要求的技术定义。
-
保持简单,我们并不想设计。
-
例如 - 将 AES 256 表单数据从前端发送到服务器。
🟢示例
要求
- 该轮胎必须适用于SUV汽车。
规格
- 必须支撑 75,00 磅的压力。
- 轮胎必须符合美国DOT标准。
- T 或更高的速度质量。
🟢需求类型
根据功能,有两种类型的需求
1.功能性
-
该程序的功能是什么
-
系统应该做什么
2.非功能性
-
应该实现什么目标
-
系统应该如何运作
除了这两种要求外,还有三种类型的要求
1.产品
-
产品本身必须具备
-
例如 - 应用程序必须使用 kotlin 编写
2.组织
-
公司政策、标准、风格等
-
例如 - 数据应采用 AES 256 加密
-
ex - 项目应该由 SCRUM 开发
3.外部
-
外部法律、法规、趋势等
-
例如 - 根据美国法律 Xyz,必须使用 SSL
设计:建筑🔽
🟢架构介绍
架构是系统中最高级别的设计。它是理念与现实之间的桥梁。它基于我们对系统的构想,并为其制定计划。我们只关注系统中最重要的几个部分。我们希望将其从理念分解成具体的可构建部分。
在软件开发中,糟糕的架构是无法通过良好的编程来解决的。它是开发过程中至关重要的一步。一旦我们确定了架构,就必须明白它是无法更改的。
🟢架构概述
糟糕的架构
📌优秀的建筑
软件架构的核心在于将大型系统和理念分解成更小、更集中的系统。我们的第一步是收集需求,构建初始架构。我们根据这些广泛的理念和指导方针,将其组织成各个功能区域。
然后,每个区域都会经过相同的流程,分解成越来越小的部分。最终,我们将得到整个系统的设计蓝图。
良好的架构很难实现。正确的开发需要耗费大量的资源。然而,这部分前期成本几乎总能通过软件的可维护性得到弥补。这将减少错误的数量,并缩短修复这些错误的时间。
良好的架构也有助于加快开发速度并提高资源利用率。如果我们将项目分解成小块,就能理解如何让多个开发人员同时工作。
🟢软件架构示例
1.我们要设计一个游戏的架构。
2.现在我们应该根据功能来划分这些层。
3.现在我们将添加另一层来控制前端和游戏逻辑之间的交互。
🟢架构模式
📌管道和过滤器
管道和过滤器模式非常适合用于跨多个不同层处理数据。该模式的关键在于每个步骤能够输入和输出相同类型的数据。因此,如果你在一端发送一组数字,那么在另一端也会得到一组数字。
🟢 架构模式
📌 管道和过滤器
管道和过滤器模式非常适合用于跨多个不同层处理数据。该模式的关键在于每个步骤能够输入和输出相同类型的数据。因此,如果你在一端发送一组数字,那么在另一端也会得到一组数字。
此关键约束使得您可以按任意顺序混合搭配逻辑,且程序仍能正常运行。这些不同的过滤器也可以跨多个服务器设置。
这种模式无疑增加了复杂性。设置起来可能很棘手,很难做到正确。此外,如果任何一步数据丢失,整个流程都会中断。
📌 客户端-服务器
客户端-服务器模式如今非常常见。每个网站和大多数手机应用都采用这种架构。在这种模式下,软件分为两部分:客户端和服务器。
软件工程
以 iPhone 应用为例。你在应用商店下载的就是所谓的“客户端软件”。这是应用与服务器通信的版本。它不会在本地存储任何服务器数据。它只是在必要时进行适当的服务器调用。
当然,另一部分是“服务器软件”。服务器软件安装在服务器上,用于接收来自客户端的请求。服务器保存并更新数据,处理请求并将数据发送给客户端。由于请求信息的客户端数量几乎是无限的,因此服务器必须进行正确的调优。
这是一个访问和更新单一信息存储库的绝佳模式。它非常适合追踪账户信息并规范哪些数据需要自动提交。
📌 主从
主从模式由两个元素组成:主服务器和从服务器。主服务器完全控制与其关联的所有从服务器。这适用于多种不同的应用。
软件工程
其中一个应用是使用重复的备份服务器。您不希望这些备份服务器彼此独立运行。这会创建一堆不同的内存状态。每台服务器都会拥有不同的数据集。因此,您需要一台主服务器,即主要运行的服务器。
主服务器负责处理所有日常操作。然后在一天中的某个时间点,它会向所有从属服务器发送信号,通知它们开始备份操作。所有从属服务器都会启动,从主服务器复制数据,然后返回休眠状态。
这种模式也适用于“多线程”。我们将一个操作分解成多个小部分。每个部分分配一个线程,并通过 CPU 进行处理。如果 CPU 有多个核心,它就可以同时处理多个线程。
我们通常有一个主线程来控制所有从属线程的创建和跟踪。从属线程会严格按照主线程的指令执行。主线程会不断重新评估情况,创建和删除从属线程。操作完成后,主线程也会停止。
📌 分层图案
分层模式将程序划分为多个技术层。这些层仅与相邻层通信。假设我们有一个 9 层的架构。在这个模型中,第 8 层只能与第 9 层和第 7 层通信,第 4 层只能与第 3 层和第 5 层通信,等等。
软件工程
这简化了通信渠道,并有助于更好地区分程序的各个区域。总的来说,这有助于提高程序的可维护性。缺点是,这可能会增加某些方面的复杂性。例如,如果您需要将消息从第 1 层发送到第 9 层,则每层都必须有一个函数来传递该消息。
🟢 架构结论
软件开发没有放之四海而皆准的方案。整个过程必须根据具体情况而定。我们作为工程师,会努力寻找能够解决问题的最佳模式或模式组合。
这个过程是一个迭代的过程。我们提出一个想法,获得反馈,然后反复修改,这个过程反复多次。经过一系列迭代,我们最终找到了最适合问题的架构。
设计:模块化
设计是我们真正规划系统的地方。在这一步,我们可以尽可能详细地阐述。
这里的主要重点是将项目分解为子系统和模块。
- 子系统:具有独立价值的独立系统。
- 模块:子系统的组件,不能独立运行。
我们应该慢慢地完善和重新组织系统和子模块,直到它们最合理。
软件工程
设计包含两件事:
- 活动:致力于设计软件。
- 产品:详细说明软件设计的一份或一组文档。
信息隐藏——将程序的复杂性隐藏在“黑匣子”内。
数据封装——向用户隐藏实现细节,仅提供接口。
这两种方法都致力于隐藏实现细节并保护数据的完整性。我们希望控制数据流,并为用户提供易于使用的体验。
有了这些理念,我们可以将最复杂的代码变得任何人都可以理解。在设计过程的每个步骤中都这样做也有助于提高代码的可维护性。
如果实现得当,我们无需了解整个代码库即可进行更改。我们只需要了解正在处理的程序部分即可。程序所有其他级别的封装使测试和更改变得简单。
🟢 模块化的目标
1)抽象性
2)可组合性
3)可分解性
4)可保护性
5)连续性
6)模块可理解性
🟢 耦合介绍
耦合度是系统模块化设计时需要考虑的重点之一。它详细说明了每个模块与其他模块的依赖程度。一组紧密耦合的模块是糟糕的设计,会导致代码难以维护。
我们不希望模块之间相互依赖。我们希望能够将一个模块替换成另一个模块,并且只需更新替换模块中的代码。程序的依赖性越强,每次修改就需要重写越多的模块。
📌 紧耦合
这是最糟糕的耦合形式。紧耦合意味着模块之间依赖性很强。修改起来非常困难,错误也很难追踪。
- 内容耦合:当一个模块修改或依赖于另一个模块的内部工作时。
- 公共耦合:当多个模块可以访问相同的全局数据时。
- 外部耦合:当多个模块可以直接访问相同的外部输入/输出时。
📌 中等耦合
这里的耦合正在变得越来越好,但我们仍然有改进的空间。
- 控制耦合:当数据传递时会影响另一个模块的内部逻辑。
- 数据结构耦合:这是指多个模块共享相同的数据结构。
📌 松耦合
- 数据耦合:指两个模块共享相同的数据。这是一种良好的耦合形式。
- 消息耦合:这种耦合是指在模块之间传递消息或命令。
- 无耦合:模块之间没有任何通信。
🟢 凝聚力介绍
当我们讨论模块化时,内聚力是另一个需要关注的方面。内聚力衡量的是模块对单一任务的专注程度。模块越专注,内聚力就越高。
内聚力越高越好。我们希望模块只做一件事,而且只做一件事。这样做是为了可维护性。
我们希望找到一个能够最大化两者效果的点。我们希望创建松耦合和高内聚。
📌 内聚力弱
- 巧合内聚:模块内的任务仅仅因为位于同一模块中才相互关联。这是最弱的内聚形式。在这种情况下,模块完全随机。模块内的任务之间没有任何关联,除了它们被简单地放入同一个文件中之外。
- 时间凝聚力:模块内的任务之所以相互关联,是因为事件发生的时间大致相同。
- 逻辑凝聚力:模块内的任务由于属于同一类别而相互关联。
📌 中等凝聚力
- 程序内聚性:执行顺序从一个命令传递到另一个命令。这里存在时间关系。这与时间性不同,因为任务之间既相互关联又彼此重要。这些任务必须按照一定的顺序执行才能正常工作。
- 沟通凝聚力:当所有任务都支持相同的输入和输出数据时。
- 顺序内聚:前两者的结合。当所有任务都工作时,其中一个任务的输出数据是下一个任务的输入数据。这样,我们就有了一个任务流程,所有任务共享相同的数据。
📌 强大的凝聚力
- 功能内聚:指模块内的所有任务都支持一个且仅一个与问题相关的任务所需的活动。本质上,该模块只执行单一操作。
- 对象内聚性:这可以与功能内聚性合并,也可以单独存在。对象内聚性是指所有活动都修改单个对象。
这仅适用于面向对象语言。例如,一个模块可能只修改用户对象。该模块中的所有任务都会以某种方式更新用户模块。
🟢 凝聚力结论
内聚力对于代码的理解和维护至关重要。模块越集中,代码就越容易调试。
但是,请记住,这必须与耦合保持平衡。一组内聚性极强的模块也可能紧密耦合在一起。我们正在寻找两者之间的平衡。如果您还记得这些章节中的一点,那就是我们想要“松耦合,强内聚”。
实施与部署
好的设计能让我们在项目正式动工前就看到它的全貌。这样,我们就能决定哪些区域需要建造,哪些区域需要购买。购买代码的好处在于,它几乎总是更便宜。
购买而不是自行开发几乎总是双赢的。然而,编码通常非常具体。这使得很难找到完全符合问题的软件。不过,在开始构建之前,先做一些研究;这样可以省下很多钱。
🟢 照顾程序员
实施阶段耗费的时间最多,也最容易浪费时间。
编程时务必保持警觉和专注。35
小时的编程效率可能和 70 小时一样高。
编程需要专注;持续的干扰会降低整体的专注度。
🟢 编码原则
- 使用样式指南,以便所有代码看起来相对相同。
- 代码是为人编写的,而不是为计算机编写的。
- 使模块易于理解。
- 做任何事都要有计划。
🟢 部署介绍
部署是测试和实施的混合;它主要发生在测试之后,但也需要实施。
该领域的规划水平直接关系到部署如何影响整个项目。
部署应该以撤退的理念来构建。如果出了问题,我们该如何恢复?
🟢 部署规划
- 规划的数量取决于变化的大小。
- 我们看看哪些地区最有可能出现最大的问题。
- 值得关注的领域:
- 数据库
- 软件集成
- 运行时更改
- 训练
- 停机时间
- 备份
- 网络
- 记忆
- 最后,我们需要考虑规划回头的步骤。
🟢 部署回滚
回滚是将系统恢复到之前工作状态的行为。
- 寻找一个不归路。
- 这是返回所花的时间比继续通过所花的时间更长的点。
- 了解这一点将有助于在部署期间做出决策。
- 部署过程的每一步都会决定回滚是否是更好的选择。
测试概述
测试是发现错误的过程。这些错误可能是代码本身的问题,也可能是未能满足要求。如果应用没有按照预期完成任务,那就是一个问题。在测试中,我们致力于确保程序满足所有要求。
- 测试数据:用于测试系统的输入。
- 测试用例:我们使用给定数据操作系统的方式。
- 预言:一系列“好”的结果。
🟢 错误
漏洞本质上是对预期行为的偏离。例如,如果您有一个网站,一个潜在的漏洞可能是网站无法加载。代码在加载过程中发生致命故障,导致服务中断。这当然是与能够访问网站的预期行为的偏差。
另一个错误可能是网站错误地将您登录到其他人的帐户。在这种情况下,网站仍然正常运行。但是,发生了与预期行为不同的情况,您只能访问自己的帐户。
- 失败:代码偏离预期行为的事件。
- 错误:导致失败的代码部分。
- 错误:实际结果如何。
测试可以用来发现bug,但无法确保bug的彻底消除。这是因为确保这一点的唯一方法是测试每一个可能的测试用例。对于一个典型的系统来说,测试用例的数量可能高达数十亿甚至数万亿。这几乎是不可能做到的。仅测试一项的成本就可能远远超过整个开发过程的10倍。
因此,我们的做法是尝试用最少的测试用例覆盖最大范围。我们混合使用不同的测试实践来完成这项任务。bug 总是会存在,但如果我们足够聪明,就能主动清除大部分 bug。
🟢 验证和确认
- 验证:我们构建的东西正确吗?软件是否按照给定的规格运行。
- 验证:我们构建的东西正确吗?软件是否符合用户/客户的需求?
违反验证的一种情况是程序访问了错误的数据库。在这种情况下,我们构建的系统是错误的,它偏离了预期的行为。
违反验证的一种情况是,程序计算的是汽车付款,而不是房屋付款。我们的汽车付款计算器可能是世界上最稳定的计算器。然而,我们本应构建一个房屋付款计算器。我们没有构建正确的系统。我们构建的系统解决了它原本设计时并未考虑的问题。(反过来说,它也没有解决它原本设计时考虑的问题。)
一个更现实的例子是,如果一家公司有一套非常独特的信息收集方式,而我们设计了一个以不同方式收集这些信息的系统。所有相同的信息都被收集了,但方式却不对。我们没有构建正确的系统。
了解和测试这两者对于交付高质量的软件非常重要!
🟢 测试
📌 单元测试
单元测试专注于软件的最小单元。我们尝试隔离不同的区域,并重复测试,直到测试程序的每个模块。我们选取一个模块,并为其提供测试用例。然后,我们根据预测标准 (Oracle) 检查这些测试用例。
总的来说,我们努力确保模块能够按照设计运行。我们不希望其他模块的 bug 破坏我们正在测试的模块。因此,在单元测试中,我们通常会提供虚拟值来隔离模块。这样,我们就能知道,如果发生了 bug,它就发生在我们正在测试的区域。
📌集成测试
下一步是集成测试。一旦我们确信所有模块都按预期运行,就需要开始对它们进行集成测试。通过集成测试,我们可以开始测试整个架构和通信。
在单元测试中,我们为单个模块编写测试用例;而在集成测试中,我们会为模块组编写测试用例。例如,我们可能想测试整个表单,而不是仅仅测试表单中的每个框。
📌增量测试
集成测试的一个问题是,如果出现错误,很难准确判断是哪个模块引入了该错误。增量测试可以简化此过程。
通过增量测试,我们会慢慢地将一个模块一个模块地添加到测试环境中。这样,如果出现错误,我们就知道是哪个模块导致了错误。
📌 自上而下的测试
自上而下测试,我们从尽可能高的层级开始测试,然后逐步向下进行。为了实现这一点,我们需要准备一组虚拟模块,并逐步用常规模块替换它们。
- 存根 (Stub):待实现模型的模板。通常返回硬编码值。
通常使用存根 (stub) 来实现这一点。它是一个没有任何逻辑的模块,里面只有返回硬编码值的函数。这里的硬编码指的是没有经过计算的内容,只是我们放入的值。例如,一个存根可能包含一个函数“getUser(int 45)”。它不会遍历并查找用户信息,而是直接返回一个我们硬编码进去的用户对象。
这样,我们就可以测试模块使用用户信息的能力,而无需引入其他模块。一旦我们测试完所有内容并确保其正常工作,我们就可以添加实际模块并进行下一轮测试。
📌 自下而上的测试
自下而上测试与自上而下测试相反。自下而上测试中,我们从底层开始,并使用所谓的驱动因素来向上推进。
- 驱动程序:执行命令并初始化所需变量的模板。
当我们自下而上地工作时,我们需要调用底层模块的逻辑。例如,如果我们的模块包含 getUser() 函数,那么我们需要找到一种正确测试它的方法。我们需要一个驱动程序,它负责初始化变量,然后多次调用我们需要的函数。
因此,在这种情况下,驱动程序可能会使用随机值调用 getUser() 1,000,000 次。然后,我们会查看结果,看看该函数是否按预期运行。一旦我们满意,我们就会添加另一层并继续。
📌 背靠背测试
当你已经有了一个可以运行的程序时,背靠背测试是一个很好的选择。在进行更改之前,我们会在系统上运行一组测试。之后,我们会通过更新、添加或删除模块来进行更改。之后,我们再运行同一组测试。
我们现在有两组数据:修改前和修改后。我们会比较这两组数据,确保没有差异。如果存在差异,我们就知道这次修改的效果超出了我们的预期。然后我们就可以回滚或修复错误了。
📌 手动测试 vs 自动测试
我们可以通过两种不同的方式设置测试。我们可以手动设置,即自己输入值;也可以自动设置,即设置另一个系统来执行测试。
- 自动的
这两种方法各有优缺点。自动测试的优势在于,它能够覆盖比手动测试更大的范围。我们可以搭建一个系统,让它测试数百万个测试用例。一旦搭建完成,每次修改代码时都可以运行它,确保更新不会破坏任何功能。
然而,这一切都会带来更多的开销。测试系统本身就是一个系统。这意味着需要更多的规划和开发时间。除此之外,我们还必须设计可以在计算机上完成的测试,并提供预言机来检查其数据。如果我们要测试数百万个测试用例,那么预言机很可能必须是一个复杂的算法。
还有测试人员测试的问题。如果我们的测试人员设计不正确,导致误报或漏报怎么办?然后,我们就会浪费不必要的时间去寻找虚假的错误,或者部署糟糕的软件。
- 手动的
手动测试需要人工测试代码。用户进入测试环境后,会期望执行某个操作。然后,人工测试看看是否存在预期的行为。错误会被记录下来,并提交给开发人员。
这些测试人员可以是开发人员、利益相关者或经过专门培训的测试人员。他们可以更轻松地提出预言并执行各种测试。
然而,与计算机相比,人类的速度非常慢。他们只能测试系统有限的部分。很多时候,这意味着系统的许多部分都没有得到适当的测试。
全面的
最好的测试方法是将两者结合起来。拥有一套优秀的自动化测试和一套优秀的测试人员。这样既能兼顾自动化测试的速度,又能兼顾手动测试的便捷性和灵活性。
📌黑盒测试与白盒测试
测试人员的不同,测试结果也会有很大差异。在测试领域,有“白盒测试”和“黑盒测试”的概念。
黑盒测试是指根据设备的预期功能进行测试。大多数用户拿到软件后都会这样做。他们会尝试执行所有他们想要的操作。测试所有预期行为,确保程序正常运行。如果出现错误,通常是因为未能满足程序要求。
白盒测试是在系统内部进行测试。这正是自动化测试的用武之地。我们会从各个角度和方面测试程序,以确保其正常运行。我们的目标是测试不同情况的不同组合。例如,对于一个计算房屋还款额的程序,我们需要测试多个利率、房价等。
这是技术层面的问题。这使我们能够测试系统的各个方面。
软件维护
软件维护是指软件交付后对其进行修改的过程。本质上,它是软件蜜月期之后的阶段。这是软件生命周期中耗时最长、成本最高的阶段。
大部分维护工作通常都花在修复错误上。
🟢 维护类型
维护有三种类型:
📌 纠正性维护
纠正性维护是指我们介入并修复错误。我们修复代码,使其能够按照最初的预期运行。这种维护是对使用过程中出现的意外问题的反应。它通常会消耗维护预算的很大一部分。
📌 自适应维护
适应性维护是指我们根据环境变化来修改软件。当环境发生变化时,我们需要修改软件来适应变化。
例如,假设我们正在开发一个使用 Facebook API 的应用程序。Facebook 更改了他们的 API。现在,由于 Facebook 的变更,我们的软件无法正常运行。自适应维护就是检查我们的软件,确保其恢复正常运行的过程。
📌 完善维护
完善性维护是指我们介入并向系统添加功能。我们不会修复任何问题,也不会为了适应新环境而改变系统。相反,我们会介入并改进系统。
例如,我们有一个计算房屋贷款的程序。我们可能会添加一个计算汽车贷款的功能。这现在是一个额外的功能,也是我们大部分时间都在做的事情。一个精心设计的程序应该易于维护,并且在未来能够扩展。
🟢 版本控制系统
版本控制是我们追踪代码库随时间变化的方式。它记录了我们所做的所有更改,并以一种让我们能够准确了解代码如何发展到当前状态的方式存储。
📌 我们为什么要使用它?
版本控制很重要,因为它允许我们做一些事情:
- 恢复到旧版本:如果我们犯了错误,我们可以恢复到旧版本的代码。
- 分支:我们可以从主版本的代码中分离出来,以添加功能或尝试新的东西。
- 协作:多个人可以同时处理相同的代码。
- 历史:记录每个更改,以及进行更改的人和原因。
📌 基本概念
- 存储库:这是所有不同版本代码的记录。
- 提交:当用户决定要记录代码所做的更改时,就像游戏中的保存点一样。
- 分支:非主版本的代码的单独路径。
- 合并:将您在单独分支中所做的更改添加到主分支。
- 拉取请求:这是当您创建了一个分支,进行了更改,然后要求将这些更改合并到代码的主版本中时。
🟢 结论
本指南涵盖了丰富的内容,从软件工程的基础知识到各种架构模式、设计原则,以及模块化、耦合、内聚、实现、部署、测试和软件维护的重要性。此外,我们还探讨了版本控制系统作为管理代码变更和协作的重要工具。
请记住,软件工程是一个不断发展的领域,保持好奇心、乐于学习和适应能力将有助于您在不断变化的技术环境中游刃有余。无论您是经验丰富的开发人员,还是刚刚踏上软件工程之旅,都要不断探索、实验和构建,为激动人心的软件开发世界做出贡献。
我的其他博客
文章来源:https://dev.to/ankushsinghgandhi/software-engineering-3gbm