测试驱动开发(Test Driven Development,简称TDD)是一种软件开发方法,其优先考虑在创建实际代码时编写自动化测试。它遵循一种循环模式,首先编写一个失败的测试,然后编写代码使测试通过,最后对代码进行重构。TDD最初的开发目的是确保创建的软件在长期内具有质量、可维护性和可扩展性。测试应明确反映源代码中的功能实现情况,以明确不同开发者的职责。与任何文档相比,测试在源代码中实现的功能方面始终保持最新状态。
TDD对程序的安全性有积极影响,本文将介绍TDD的多种理论方法。
经典TDD
单元测试和用它们测试的单元是同时进行连续开发的。实际编程是通过小规模、重复的微迭代完成的。这样的迭代只需要几分钟,包括三个主要部分,即红、绿、重构。
红:编写一个测试来测试要编程的新行为(功能)。从最简单的示例开始,如果该功能较旧,则可能是已知的错误或要实现的新功能。这个测试最初不会被现有的程序代码满足,所以它必须失败。
绿:尽可能少地更改程序代码,并添加到测试运行之后,直到它通过所有测试。
重构:去除重复代码,必要时进行抽象、与绑定代码约定对齐等。在这个阶段不应引入测试尚未覆盖的新行为。每次更改后,都要进行测试;如果测试失败,就无法复制在使用的代码中出现的错误。重构的目标是使代码简单易懂。
重复以上三个步骤,直到修复已知的错误,代码提供所需的功能,并且开发者无法想出任何可能失败的有用测试为止。以这种方式处理的程序技术单元目前被认为是完整的。但是,与它一起创建的测试会保留下来,以便在未来更改后再次进行测试,以验证已经实现的行为方面是否仍然满足要求。
为了使第2步中的代码更改达到目标,每个更改必须更合理,例如,不能只为了编辑当前测试用例而牺牲其他测试用例。越来越详细的测试使代码更加简洁规范,定期关注更改优先级可以实现更高效的算法。
坚持这种方法就是一种演化设计,每次迭代都会使系统变得更好。
自外向内测试驱动开发
自外向内测试驱动开发(Outside In Test Driven Development,简称OITDD)是一种软件开发方法,强调先通过创建高级验收测试或端到端测试来定义用户或外部接口的期望行为,然后开始开发过程。它通常也被称为行为驱动开发(Behavior Driven Development,简称BDD)。
在自外向内TDD中,首先编写一个描叙系统预期行为但尚未通过的失败验收测试,作为开发的起点。由于系统没有所需的功能,该测试最初预计会失败。
完成第一个验收测试之后,下一步是为最小可能通过验收测试的代码单元编写一个失败的单元测试。这个单元测试定义了系统内特定模块或组件的预期行为。由于相应的代码尚未实现,单元测试会失败。
接下来的步骤是实现代码,使失败的单元测试通过。代码是逐步编写的,重点关注失败测试的即时需求。开发者逐步编写进一步的测试和代码,直到验收测试通过并实现了系统的预期行为。
自外向内TDD的理念是从外部驱动开发过程,从高级行为开始,逐渐向内部实现细节移动。这种方法有助于确保系统的开发满足用户的需求和期望。它还通过鼓励创建小而专注的测试和模块化代码,促进了组件的可测试性和解耦。
开发者通过实践自外向内TDD并确保系统按预期行为运行,来获得对代码的信心。它还有助于在开发早期发现设计缺陷,并鼓励创建松耦合且易于维护的代码。
总而言之,自外向内TDD是一种结合了测试和开发的方法论,侧重于为用户提供价值,并从外部的视角驱动开发过程。
验收测试驱动开发
验收测试驱动开发(Acceptance Test Driven Development,简称ATDD)是一种与测试驱动开发相关但在方法上有所不同的方法。验收测试驱动开发是客户或用户、开发者和测试人员之间的沟通工具,旨在确保需求被清楚描述。验收测试驱动开发不需要自动化测试用例,尽管对于回归测试来说是有用的。用于测试驱动开发的验收测试也必须对非开发人员可读。在许多情况下,测试驱动开发的测试可以从验收测试中推导出来。对于将此方法用于系统安全,边界条件必须超出正常的技术方面,这只在某些情况下才会被考虑到。
应该使用什么样的测试覆盖率?
对于安全性的影响而言,自动运行测试非常重要。这些测试可以产生可度量和与之前运行进行比较的测试覆盖率。问题是,哪种测试覆盖率最合适。已经证明各种方法的强度存在显著差异。笔者强烈支持变异测试覆盖率,因为它是最有效的测试覆盖率之一。测试覆盖率越好,可以达到的效果越好。
如何支持应用程序的安全性?
1.及早识别漏洞:在实施功能之前编写测试有助于早期发现潜在的安全漏洞。通过在测试设计阶段考虑安全问题,开发人员可以预测可能的攻击向量,并在编写代码时遵循安全准则。这种主动的方法能够在安全问题深嵌到代码库之前检测到它们。
2.鼓励安全编码实践:TDD鼓励编写模块化、结构良好且可测试的代码。这使得开发人员能够采用安全编码实践,如输入验证、适当的错误处理和安全数据存储。通过编写模拟不同安全场景的测试,开发人员更有可能考虑边缘情况并验证其代码的安全行为。
3.安全的回归测试:TDD依赖于定期运行自动化测试,以保持新增代码时现有功能的稳定性。这种方法有助于防止安全相关问题的下降。如果存在与安全相关的测试用例,并且最初通过了测试,可以测试对其进行的后续更改,以确保没有意外引入漏洞。
4.建立安全意识文化:TDD将安全测试作为开发过程的一个重要部分,有助于在开发团队中培养安全意识文化。通过在功能测试之外进行安全测试,开发人员更有可能将安全性作为工作的基本方面并予以优先考虑。这种思维方式鼓励对安全问题采取积极主动的态度,而不仅仅将其视为次要问题。
5.促进协作与代码审查:TDD鼓励团队成员之间的协作和代码审查。当开发人员首次编写测试时,他们可以讨论安全问题,并从同事那里获得反馈。这种协作方法增加了团队整体对安全性的认识,并能够早期发现和解决安全问题。
虽然TDD并非解决所有安全问题的银弹,但它提供了一种系统化且规范的开发方法,间接加强了软件的安全性。通过推动以安全为中心的思考、促进安全编码实践以及促进漏洞的及早发现,TDD可以帮助构建更安全、更具弹性的应用程序。需要注意的是,为了满足安全要求,还应包括其他安全措施,如威胁建模、代码分析和渗透测试等。
一些更实用的方法
如何解决合规问题?
合规问题是指在项目中以某种许可证运行的元素,但可能不允许以这种方式使用,这种情况可能发生在项目的所有技术领域中。多年来,使用的库或框架通常不会更改其许可证。另外还必须检查所使用元素的所有依赖项。为了解决此类违规问题,必须用适当许可证下的语义等效替换受影响的元素。
如何消除漏洞?
漏洞是源代码中可能被利用的错误或缺陷,可能存在于技术栈的不同层面。重要的是要识别所使用技术中的漏洞,以了解哪些漏洞可以在哪种组合中被利用来攻击该系统。在这一点上,需要澄清个别考虑因素。这种全面的视角使我们能够识别不同的攻击向量,并确定在系统中没有可被利用的弱点,可以针对性地保护系统。
为什么需要高效的依赖管理?
开发者可以使用哪些工具来解决合规问题和漏洞?答案很明显,这得益于非常有效的依赖管理结合强大的测试覆盖率。因为在所有情况下,都必须交换依赖项。即使它是合规问题的替代品,修复漏洞也是在不同版本中替换相同的依赖项,可以是更高或更低的版本。总体而言,关键是巧妙地组合各种因素。强大的测试覆盖率有助于随后的测试,以确定功能是否仍然可用。
结论
强大的测试覆盖率在修改和交换外部元素以及源代码时是有益的。在这里,可以依赖CI环境的结果,而无需添加手动验证,并因此实施快速且高效的发布流程,以尽量减少时间损耗。
参考链接:
https://dzone.com/articles/tdd-and-the-impact-on-security