Python 测试秘籍第二版(五)
原文:
zh.annas-archive.org/md5/98CC341CCD461D299EE4103040C60B7B译者:飞龙
第九章:新系统和旧系统的良好测试习惯
在本章中,我们将涵盖以下主题:
-
有总比没有好
-
覆盖率并不是一切
-
愿意投资于文本夹具
-
如果你对测试的价值不太确信,你的团队也不会确信
-
收集指标
-
在自动化测试中捕获错误
-
将算法与并发分开
-
当测试套件运行时间过长时,暂停重构。
-
充分利用你的信心
-
愿意放弃一整天的更改
-
与其追求 100%的覆盖率,不如试图实现稳定的增长
-
随机地打破你的应用程序可能会导致更好的代码
介绍
希望你喜欢本书的前几章。到目前为止,我们已经探讨了许多自动化测试领域,比如:
-
单元测试
-
Nose 测试
-
Doctest 测试
-
行为驱动开发
-
验收测试
-
持续集成
-
冒烟和负载测试
在本章中,我们将做一些不同的事情。与其为各种技巧提供大量代码示例,我更想分享一些我作为软件工程师在职业生涯中学到的想法。
本书中以前的所有示例都非常详细地介绍了如何编写代码、运行代码并审查其结果。希望你已经接受了这些想法,对其进行了扩展、改进,并最终应用到解决自己的软件问题中。
在本章中,让我们探讨一些关于测试的更大的想法,以及它们如何增强我们开发质量系统的能力。
有总比没有好
不要陷入纯粹的隔离或担心晦涩的测试方法。首先要做的是开始测试。
如何做...
你刚刚接手了一个由其他人开发的应用程序,而这些人已经不在你公司了。以前有过这种经历吗?我们都有过,可能不止一次。我们能预测一些常见的症状吗?嗯,它们可能类似于这些:
-
几乎没有(如果有的话)自动化测试。
-
几乎没有文档。
-
有一些代码块已经被注释掉了。
-
代码中要么没有注释,要么有些注释是很久以前写的,现在已经不正确了。
而这就是有趣的部分——我们一开始并不知道所有这些问题。我们基本上是被告知在源代码树中检查,并开始工作。例如,只有当我们遇到问题并寻求文档时,我们才会发现存在(或不存在)什么。
也许我没有捕捉到你在列表中遇到的所有问题,但我敢打赌你经历了其中很多。我不想听起来像一个怨恨的软件开发者,因为我不是。并非每个项目都是这样。但我相信我们都曾经不得不处理这种情况。那么,我们该怎么办?我们开始测试。
但魔鬼在细节中。我们写单元测试吗?线程测试呢?还是集成测试?你知道吗?其实,我们写什么类型的测试并不重要。事实上,使用正确的名称也并不重要。
当你独自一人坐在小隔间里和代码对峙时,术语并不重要。重要的是编写测试。如果你能挑出一个小的代码单元并编写一个测试,那就去做吧!但如果你拿到了一堆混乱的代码,而这些代码并没有很好地隔离单元,怎么办呢?
考虑一个系统,最小的单元是一个模块,用于解析电子文件,然后将解析结果存储在数据库中。解析结果不会通过 API 返回。它们只是悄悄地、神秘地出现在数据库中。我们如何自动化呢?嗯,我们可以做以下几件事:
-
编写一个测试,首先清空与应用程序相关的所有表。
-
找到一个用户拥有这些文件之一,并获取一份副本。
-
向测试中添加调用顶层 API 来摄取文件的代码。
-
添加一些从数据库中提取数据并检查结果的代码。(你可能需要获取那个用户,以确保代码正常工作。)
恭喜!你刚刚编写了一个自动化测试!它可能不符合单元测试的标准。事实上,它可能对你来说看起来有点丑陋,但又怎样呢?也许它需要五分钟才能运行,但这难道不比没有测试好吗?
它是如何工作的…
由于数据库是我们可以断言结果的地方,我们需要在每次运行测试之前清理出一个经过清理的版本。如果其他开发人员正在使用相同的表,这肯定需要协调。我们可能需要分配给我们自己的模式,这样我们就可以随意清空表格。
模块可能受到缺乏凝聚力和过于紧密耦合的影响。虽然我们可以尝试确定代码为什么不好,但这并不会推动我们构建自动化测试的事业。
相反,我们必须认识到,如果我们试图立即跳入单元级别的测试,我们将不得不重构模块以支持我们。没有或很少的安全网,风险是非常高的,我们可以感受到!如果我们试图坚持教科书上的单元测试,那么我们可能会放弃,并认为自动化测试是不可能的。
因此,我们必须迈出第一步,编写一个昂贵的、端到端的自动化测试来构建链条的第一环。这个测试可能需要很长时间才能运行,而且可能在我们可以断言的内容上并不是非常全面,但这是一个开始,这才是重要的。希望在稳步取得进展编写更多这样的测试之后,我们将建立一个安全网,这将防止我们不得不回头重构这段代码。
这还不是全部!
只是编写测试听起来有点太简单了吗?嗯,概念很简单,但工作会很艰难——非常艰难。
你将被迫爬行通过大量的 API,并确切地找出它们是如何工作的。而且,猜猜看?你可能不会得到很多中间结果来断言。理解 API 只是为了让你追踪数据的流向。
当我描述我们的情况的数据神秘地最终出现在数据库中时,我指的是你可能的 API 并不是为了可测试性而设计的大量返回值。
只是不要让任何人告诉你,你在构建长时间运行的测试用例上浪费时间。一个需要一个小时才能运行一次,并且每天至少运行一次的自动化测试套件,可能比手动点击屏幕更能增强信心。有总比没有好。
另请参阅
- 充分利用你的信心的配方
覆盖率并不是一切
你已经知道如何运行覆盖率报告了。然而,不要认为覆盖率越高就越好。以覆盖率为名牺牲测试质量是失败的开始。
如何做到…
覆盖率报告提供了很好的反馈。它们告诉我们哪些被执行了,哪些没有。但是,仅仅因为一行代码被执行,并不意味着它做了它应该做的一切。
你是否曾经想要在休息室里吹嘘覆盖率得分?为良好的覆盖率感到自豪并不是没有根据的,但当它导致使用这些统计数据来比较不同的项目时,我们就会涉足风险领域。
它是如何工作的…
覆盖率报告是要在运行的代码的上下文中阅读的。报告告诉我们什么被覆盖了,什么没有被覆盖,但这并不是结束的地方。相反,这才是开始的地方。我们需要看看什么被覆盖了,并分析测试对系统的执行程度。
很明显,模块的覆盖率为 0%表示我们有工作要做。但是当我们的覆盖率达到 70%时,这意味着什么呢?我们需要编写测试来覆盖其他 30%吗?当然需要!但是对于如何解决这个问题,有两种不同的思路。一种是正确的,一种是错误的。
-
第一种方法是编写新的测试,专门针对未覆盖的部分,同时尽量避免重叠原始 70%。对已经在另一个测试中覆盖的代码进行冗余测试是资源的低效使用。
-
第二种方法是编写新的测试,以便它们针对代码预期处理的场景,但我们尚未解决。未被覆盖的部分应该给我们一个提示,表明哪些场景尚未被测试。
正确的方法是第二种。好吧,我承认我以引导的方式写了这个。但关键是很容易看到未被覆盖的部分,并编写一个旨在尽快弥补差距的测试。
还有更多...
Python 给了我们令人难以置信的能力来进行 monkey patch,注入替代方法,以及其他技巧来执行未覆盖的代码。但这听起来不是有点可疑吗?以下是我们为自己设置的一些风险:
-
当新的测试不基于合理的场景时,可能会更加脆弱。
-
对我们的算法进行重大更改可能需要我们完全重写这些测试。
-
曾经写过基于模拟的测试吗?可能会将目标系统模拟得无影无踪,最终只是测试模拟。
-
即使我们的一些(甚至大多数)测试质量很好,低质量的测试也会使整个测试套件的质量变低。
如果我们做一些干扰行计数机制的事情,覆盖工具可能不会让我们逃脱一些策略。但无论覆盖工具是否计算代码,都不应该是我们确定测试质量的标准。
相反,我们需要查看我们的测试,看看它们是否试图执行我们应该处理的真实用例。当我们仅仅寻求获得更高的覆盖率百分比时,我们停止了思考我们的代码应该如何运行,这是不好的。
我们不应该增加覆盖率吗?
我们应该通过改进我们的测试、覆盖更多的场景和删除不再支持的代码来增加覆盖率。这些都将使我们朝着整体更好的质量迈进。仅仅为了覆盖率而增加覆盖率并不能提高我们系统的质量。
但我想要夸耀一下我的系统覆盖率!
我认为庆祝良好的覆盖率是可以的。与经理分享覆盖率报告是可以的。但不要让它消耗你。
如果你开始每周发布覆盖率报告,请仔细检查你的动机。如果你的经理也要求发布报告,也是一样。
如果你发现自己在比较你的系统覆盖率和另一个系统的覆盖率,那就要小心了!除非你熟悉两个系统的代码,并且真正了解报告的底线以上的内容,否则你可能会陷入风险区域。你可能会陷入可能导致团队编写脆弱测试的错误竞争中。
愿意投资于测试装置
花时间研究一些测试装置。起初可能写不了很多测试,但这项投资将会有回报。
如何做....
当我们开始构建一个新的绿地项目时,编写面向测试的模块会更容易,但在处理遗留系统时,构建一个有效的测试装置可能需要更多的时间。这可能会很艰难,但这是一项有价值的投资。
例如,在“有总比没有好”的部分,我们谈到了一个扫描电子文件并将解析结果放入数据库表的系统。我们的测试装置需要哪些步骤?也许我们应该考虑以下问题:
-
设置步骤来清理适当的表格。
-
很可能,我们可能需要使用代码或脚本来创建一个新的数据库模式,以避免与其他开发人员发生冲突。
-
可能需要将文件放在特定位置,以便解析器能够找到它。
这些都是需要时间来构建一个有效的测试用例的步骤。更复杂的遗留系统可能需要更多的步骤来准备进行测试。
它是如何工作的...
所有这些可能会让我们感到害怕,并可能推动我们放弃自动化测试,只是继续通过屏幕点击来验证事物。但是,花时间投资编写这个测试装置将在我们编写更多使用我们的测试装置的测试用例时开始产生回报。
你有没有建立过一个测试装置,然后不得不为特定情况进行修改?在使用我们的测试装置开发足够的测试用例之后,我们可能会遇到另一个需要测试的用例,超出了我们测试装置的限制。由于我们现在对它很熟悉,创建另一个测试装置可能会更容易。
这是编写第一个测试装置的另一种回报方式。未来的测试装置很有可能更容易编写。然而,这并不是改进的一种明确保证。通常,我们的测试装置的第一个变体是一个简单的。
还有更多...
我们可能会遇到需要另一个与我们构建的完全不同的测试装置的情况。在这一点上,对第一个测试装置的投资并没有同样的回报。但是,到那时,我们将成为更有经验的测试编写者,并且对于测试系统时什么有效、什么无效有更好的把握。
到目前为止所做的所有工作都将锻炼我们的技能,并且这本身就是对测试装置投资的巨大回报。
这只是关于设置数据库吗?
这不仅仅是关于设置数据库。如果我们的系统与 LDAP 服务器有广泛的交互,我们可能需要编写一个清理目录结构并加载测试数据的测试装置。
如果传统系统足够灵活,我们可以将整个测试结构放入层次结构中的一个子节点。但同样可能的是,它期望数据存在于特定位置。在这种情况下,我们可能需要开发一个脚本,启动一个单独的空的 LDAP 服务器,然后在测试完成后关闭它。
设置和关闭 LDAP 服务器可能不是最快、也不是最有效的测试装置。但是,如果我们投入时间来构建这个测试装置,以便自己编写自动化测试,最终我们将能够重构原始系统,使其与实时 LDAP 服务器解耦。整个过程将锻炼我们的技能。这就是为什么创建原始测试装置真的是一种投资。
如果你对测试的价值不太确信,你的团队也不会。
受测试影响的开发人员表现出热情;他们很高兴运行他们的测试套件并看到结果。
完全成功。这种情感和自豪感往往会感染其他开发人员。
但反之亦然。如果你对这一切不感到兴奋,也不传播这个想法,你的队友也不会。向你的系统添加自动化测试的想法将会悲惨地消失。
这不仅仅局限于我的个人经验。在 2010 年的 DevLink 大会上,我参加了一个关于测试的开放空间讨论,并在其他十几个我不认识的开发人员中看到了这种反应(pythontestingcookbook.posterous.com/)。
问候程序)。测试人员在传达他们的经验时表现出一种特定类型的兴奋。
他们对测试的经验。那些对于接受自动化测试持保留态度的人听起来很高兴,津津有味。那些对此不感兴趣的人根本就不会参与讨论。
如果你正在阅读这本书(当然你是),你很有可能是你的团队中唯一一个真正对自动化测试感兴趣的人。你的队友可能听说过它,但对这个想法没有你那么感兴趣。将其添加到你的系统将需要你大量的投资,但不要仅仅局限于分享代码;考虑以下内容:
-
展示你在取得进展时的兴奋,并解决棘手的问题。
-
通过在墙上张贴测试结果来分享它们,让其他人看到。
-
在休息室与同事聊天时谈论您的成就。
测试不是一个冷冰冷的机械过程;它是一个令人兴奋的、火热的开发领域。沉迷于测试的开发人员迫不及待地想与他人分享。如果您寻找传播自动化测试之火的方法,最终其他人会对此感兴趣,您会发现自己与他们讨论新的测试技术。
收集指标
开始一个电子表格,显示行数、代码、测试数量、总测试执行时间和错误数量,并在每次发布时进行跟踪。这些数字将捍卫您的投资。
如何做...
这些高层次的步骤展示了如何随着时间的推移捕获指标:
-
创建一个电子表格来跟踪测试用例的数量,运行测试套件所需的时间,测试运行的日期,任何错误以及每个测试的平均时间。
-
将电子表格作为另一个受控制的工件添加到您的代码库中。
-
添加一些图表,显示测试时间与测试数量的曲线。
-
每次发布时至少添加一行新数据。如果您可以更频繁地捕获数据,比如每周一次甚至每天一次,那就更好了。
它是如何工作的...
随着您编写更多的测试,测试套件的运行时间会变长。但您还会发现错误的数量往往会减少。您进行的测试越多,频率越高,您的代码就会越好。捕获您的测试指标可以作为时间花在编写和运行测试上的硬证据,这是一个明智的投资。
还有更多...
为什么我需要这个文档?难道我不知道测试有效吗?把它看作是对质量断言的备份。几个月后,管理层可能会要求您加快速度。也许他们需要更快的东西,他们认为您只是在测试这些东西上花费了太多时间。
如果您可以拿出您的电子表格,并展示随着测试工作的减少而减少的错误,他们将无从辩驳。但如果您没有这个,只是争辩说测试会让事情变得更好,您可能会输掉争论。
度量标准不仅仅是为了向管理层辩护
我个人很享受看到测试增加而错误减少。这是一种追踪自己并掌握进展情况的个人方式。而且,说实话,我的上一个经理完全支持自动化测试。他有自己的成功指标,所以我从未拿出过我的。
在自动化测试中捕获错误
在修复您发现的一行错误之前,先编写一个自动化测试,并确保它是可重复的。这有助于建立我们的系统免受回归到过去修复的故障的影响。
如何做...
这些高层次的步骤捕获了在自动化测试中捕获错误之前的工作流程:
我们修复它们:
-
当发现新错误时,编写一个重现它的测试用例。测试用例是否运行时间长、复杂或与许多组件集成并不重要。关键的是要重现错误。
-
将错误添加到您的测试套件中。
-
修复错误。
-
在检查更改之前,确保测试套件通过。
它是如何工作的...
向从未进行过自动化测试的应用程序引入自动化测试的最简单方法是一次测试一个错误。这种方法确保新发现的错误不会在以后悄悄地重新进入系统。
测试可能会给人一种松散的感觉,而不是全面的感觉,但这并不重要。重要的是,随着时间的推移,您将慢慢建立一个可靠的测试用例安全网,验证系统的预期性能。
还有更多...
我并没有说这会很容易。为没有考虑可测试性的软件编写自动化测试是一项艰苦的工作。正如在配方有总比没有好中提到的,第一个测试用例可能是最难的。但随着时间的推移,随着你编写更多的测试,你将获得信心回头重构事物。你一定会感到有能力,因为你知道你不会不知不觉地破坏事物。
当需要添加一个全新的模块时,你将为此做好准备。
用这种方法通过测试用例捕获错误是有用的,但速度慢。但没关系,因为慢慢添加测试会让你有时间以舒适的步伐提高你的测试技能。
这会有什么好处呢?嗯,最终,你将需要向你的系统添加一个新模块。这总是会发生的吧?到那时,你在测试和测试装置上的投资应该已经在提高现有代码质量方面产生了回报,但你也将在测试新模块上有一个先发优势。考虑以下:
-
你不仅会知道,而且真正理解“面向测试的代码”的含义。
-
你将能够以非常有效的方式同时编写代码和测试。
-
新模块将有更高质量的先发优势,不需要像系统的传统部分那样需要太多的努力来赶上。
不要屈服于跳过测试的诱惑。
正如我之前所说,第一个测试用例将非常难写。接下来的几个也不会容易。这让人很容易举手投降,跳过自动化测试。但是,如果你坚持下去,写出一些有效的东西,你就可以继续在这个成功的努力上建立。
这可能听起来像陈词滥调,但如果你坚持大约一个月,你将开始从你的工作中看到一些结果。这也是开始收集指标的好时机。记录你的进展并能够反思它可以提供积极的鼓励。
将算法与并发分离
并发性很难测试,但大多数算法在解耦后并不难。
如何做到...
Herb Sutter 在 2005 年写了一篇文章,题为免费午餐结束了,他指出微处理器正在接近串行处理的物理极限,这将迫使开发人员转向并发解决方案(www.gotw.ca/publications/concurrency-ddj.htm)。
新的处理器配备了多个核心。为了构建可扩展的应用程序,我们不能再只等待更快的芯片。相反,我们必须使用替代的并发技术。这个问题正在许多语言中得到解决。 Erlang 是第一种允许用于构建可用性为九个 9 的电信系统的语言之一,这意味着每 30 年大约有一秒的停机时间。
其中一个关键特点是在 actor 之间发送不可变数据。这提供了良好的隔离,并允许多个单元在 CPU 核心上运行。Python 有提供类似解耦的异步消息传递风格的库。最常见的两个是 Twisted 和 Kamaelia。
但是,在你开始使用这些框架之前,有一件重要的事情要记住:很难在测试并发性的同时测试算法。要使用这些库,你需要注册发出消息的代码,还需要注册处理消息的处理程序。
它是如何工作的...
将算法与所选择的并发库的机制解耦是很重要的。这将使测试算法变得更容易,但这并不意味着你不应该进行负载测试或尝试用实时数据回放场景来过载你的系统。
它的意思是,从大量测试场景开始是错误的优先级。在能够处理一次事件的自动化测试用例之前,你的系统需要正确处理一个事件。
研究并发框架提供的测试选项
一个好的并发库应该提供可靠的测试选项。寻找它们并尽量使用。但不要忘记验证你的自定义算法在简单的串行方式下也能工作。测试两边将给你很大的信心,系统在轻载和重载下都能如预期地运行。
当测试套件运行时间太长时,暂停重构
当你开始构建测试套件时,你可能会注意到运行时间变得相当长。如果它太长,以至于你不愿意每天至少运行一次,你需要停止编码并专注于加快测试的速度,无论是测试本身还是被测试的代码。
如何做…
这假设你已经开始使用以下一些实践来构建测试套件:
-
有总比没有好
-
愿意投资测试装置
-
在自动化测试中捕捉错误
这些是缓慢开始的步骤,用于在最初没有任何自动化测试的系统中开始添加测试。进行自动化测试的一个权衡是编写相对昂贵的测试。例如,如果你的关键算法与数据库没有足够解耦,你将被迫编写一个涉及设置一些表、处理输入数据,然后针对数据库状态进行查询的测试用例。
随着你编写更多的测试,运行测试套件的时间肯定会增加。在某个时候,你会感到不太愿意花时间等待测试套件运行的时间。由于测试套件只有在使用时才有效,你必须暂停开发并追求重构代码或测试用例本身,以加快速度。
这是我遇到的一个问题:我的测试套件最初需要大约 15 分钟才能运行。最终它增长到需要一个半小时来运行所有的测试。我达到了一个只能每天运行一次,甚至跳过一些天的地步。有一天,我试图进行大规模的代码编辑。当大部分测试用例失败时,我意识到我没有经常运行测试套件来检测哪一步出了问题。我被迫放弃所有的代码编辑并重新开始。在继续之前,我花了几天时间重构代码和测试,将测试套件的运行时间降低到可以接受的 30 分钟。
它是如何工作的…
这是关键的衡量标准:当你对每天运行测试套件感到犹豫不决时,这可能是需要清理的迹象。测试套件应该被多次运行一天。
这是因为我们有竞争的利益:编写代码和运行测试。重要的是要认识到这些事情:
-
要运行测试,我们必须暂停编码工作
-
要编写更多的代码,我们必须暂停测试工作
当测试占据我们日常时间表的大部分时,我们必须开始选择哪个更重要。我们倾向于更多地写代码,这可能是人们放弃自动化测试并认为它不适合他们情况的关键原因。
这很困难,但如果我们能抵制走捷径的诱惑,而是对代码或测试进行一些重构,我们将更有动力更频繁地运行测试。
还有更多…
在重构方面,这更少是科学而更多是巫术。重要的是要寻找能够给我们带来良好收益的机会。重要的是要理解,这可以是我们的测试代码,我们的生产代码,或者两者的组合都需要重构。考虑以下几点:
-
性能分析可以告诉我们热点在哪里。重构或重写这些部分可以改进测试。
-
紧耦合通常会迫使我们放入比我们想要的更多的系统部分,比如数据库使用。如果我们可以寻找方法将代码与数据库解耦,并用模拟或存根替换它,那么我们就可以更新相关的测试,以获得更快的测试套件运行。
从测试中获得的覆盖率可以帮助。所有这些方法对我们代码的质量都有积极的影响。更高效的算法会带来更好的性能,更松散的耦合有助于降低我们的长期维护成本。
另请参阅
- 愿意放弃一整天的更改
利用你的信心
建立足够的测试后,你会有足够的信心重写大部分代码或进行几乎触及每个文件的大规模修改。去做吧!
如何做...
随着你构建更多的测试并每天运行多次,你会开始对系统的了解和不了解有所感觉。尤其是当你为系统的特定部分编写了足够昂贵、运行时间长的测试后,你会有强烈的愿望重写该模块。
你还在等什么?这就是构建可运行的测试安全网的关键。了解模块的各个方面可以让你攻克它。你可以重写它,更好地解耦它的部分,或者进行其他必要的改进,同时也能更好地支持测试。
它是如何工作的...
虽然你可能会有强烈的愿望攻击代码,但可能会有同样强烈的反对感。这是风险规避,我们都必须处理。我们希望避免头等跳进可能产生严重后果的情况。
假设我们已经建立了足够的安全网,现在是时候着手处理代码并开始清理它了。如果我们在进行这些更改时频繁运行测试套件,我们可以安全地进行所需的更改。这将提高代码的质量,并可能加快测试套件的运行时间。
在进行更改时,我们不必全力以赴
利用我们的信心意味着我们可以进入并对代码进行更改,但这并不意味着我们可以进入测试不充分、不足的代码区域。我们可能想要清理几个区域,但我们应该只处理我们最有信心的部分。随着我们在未来添加更多的测试,将有机会处理其他部分。
愿意放弃一整天的更改
你有没有曾经整天进行更改,只发现一半的测试失败,因为你忘记经常运行测试套件?准备好放弃这些更改。这就是自动化测试让我们能够做到的……回到一切都运行完美的时候。这会很痛苦,但下次你会记得更经常运行测试套件。
如何做...
这个方法假设你正在使用版本控制并进行定期提交。如果你两周没有提交,这个想法就不适用。
如果你每天至少运行一次测试套件,并且在通过后提交你所做的更改,那么回到某个之前的时间点,比如一天的开始,就会变得容易。
我做过很多次。第一次是最困难的。对我来说,这是一个新的想法,但我意识到软件的真正价值现在依赖于我的自动化测试套件。在下午的中途,我第一次运行了当天编辑了一半系统的测试套件。超过一半的测试失败了。
我试图深入挖掘并修复问题。问题在于,我无法弄清楚问题的根源。我花了几个小时试图追踪它。我开始意识到,如果不浪费大量时间,我是无法弄清楚的。
但我记得前一天一切都通过了。最终,我决定放弃我的更改,运行测试套件,验证一切都通过了,然后勉强回家。
第二天,我再次攻击了这个问题。只是,这一次,我更频繁地运行测试。我成功地编写了它。回顾这种情况,我意识到这个问题只花了我一天的时间。如果我试图坚持下去,我可能会花费一周的时间,最终可能还是要扔掉一切。
它是如何工作的...
根据你的组织如何管理源代码,你可能需要做以下事情:
-
通过删除分支或取消你的检出来自己做
-
联系你的 CM 团队删除当天的分支或提交
这实际上不是一个技术问题。无论谁负责分支管理,源代码控制系统都很容易做到这一点。困难的部分是做出放弃更改的决定。我们经常感到修复问题的愿望。我们的努力导致问题进一步恶化,我们就越想解决它。在某个时候,我们必须意识到前进的成本更高,而不是倒退重新开始。
敏捷性的轴从经典的瀑布式软件生产延伸到高度敏捷的流程。敏捷团队倾向于在较小的冲刺中工作,并以较小的块提交。这使得抛弃一天的工作更容易接受。任务越大,发布周期越长,你的更改自两周前开始任务以来就没有被检查的可能性就越大。
相信我,放弃两周的工作完全不同于放弃一天的工作。我绝不会主张放弃两周的工作。
核心思想是不要在测试套件未通过的情况下回家。如果这意味着你必须放弃一些东西才能实现这一点,那就是你必须做的。这真的强调了编写一点/测试一点的重点,直到新功能准备好发布。
还有更多...
我们还需要反思为什么我们没有经常运行测试套件。这可能是因为测试套件运行时间太长,你在犹豫是否要花费那么长的时间。也许是时候暂停重构当测试套件运行时间太长了。我真正学到这个教训的时候是当我的测试套件运行了一个半小时。在解决了整个问题之后,我意识到我需要加快速度,花了大概一两个星期将其缩短到可以接受的 30 分钟。
这与“有总比没有好”如何契合
在本章的前面,我们谈到了编写一个可能非常昂贵的测试用例来启动自动化测试。如果我们的测试变得如此昂贵以至于时间上不可行呢?毕竟,我们刚才说的不是会导致我们正在处理的情况吗?
编写一点代码/测试一点可能看起来是一个非常缓慢的进行方式。这可能是许多传统系统从未采用自动化测试的原因。我们必须攀登的山是陡峭的。但是,如果我们能坚持下去,开始构建测试,确保它们在一天结束时运行,然后最终暂停重构我们的代码和测试,我们最终可以达到更好的代码质量和系统信心的愉快平衡。
另见
-
有总比没有好
-
当测试套件运行时间太长时,暂停重构
而不是追求 100%的覆盖率,试着保持稳定的增长
没有覆盖分析,你不会知道自己的情况。但不要追求太高。相反,专注于逐渐增加。你会发现随着时间的推移,你的代码变得更好——甚至可能减少了量——而质量和覆盖率稳步提高。
如何做到这一点...
如果你从一个没有测试的系统开始,不要过分关注一个荒谬的高数字。我曾经在一个系统上工作,当我接手它时,覆盖率为 16%。一年后,我把它提高到了 65%。这离 100%还差得远,但由于捕获了自动化测试中的一个错误和收集了指标,系统的质量得到了飞跃式的提高。
有一次,我和我的经理讨论了我的代码质量,他给我展示了他开发的一份报告。他对他监督的每个应用程序的每个版本运行了一个代码计数工具。他说我的代码计数有一个独特的形状。所有的
其他工具的代码行数不断增加。我的代码增长,达到顶峰,然后开始减少,仍然在下降。
尽管我的软件做的比以往任何时候都多,但这种情况发生了。这是因为我开始丢弃未使用的功能、糟糕的代码,并在重构过程中清理垃圾。
它是如何工作的...
通过逐渐构建一个自动化的测试套件,你将逐渐覆盖更多的代码。通过专注于构建具有相应测试的高质量代码,覆盖率将自然增长。当我们开始关注覆盖率报告时,数字可能会增长得更快,但往往会更加人为。
不时地,当你充分利用你的信心并重写大块内容时,你应该感到有能力丢弃旧的垃圾。这也会以健康的方式增加你的覆盖度指标。
所有这些因素将导致质量和效率的提高。虽然你的代码可能最终会达到顶峰,然后下降,但由于新功能的增加,它最终会再次增长并不是不切实际的。
到那时,覆盖率可能会更高,因为你将与测试一起构建全新的功能,而不仅仅是维护传统的部分。
随机地打破你的应用程序可能会导致更好的代码
"避免失败的最好方法是不断地失败。" - Netflix
如何做到...
Netflix 建立了一个他们称之为混沌猴的工具。它的工作是随机杀死实例和服务。这迫使开发人员确保他们的系统可以平稳和安全地失败。为了建立我们自己的版本,我们需要它做以下事情:
-
随机杀死进程
-
在接口点注入错误数据
-
关闭分布式系统之间的网络接口
-
向子系统发出关闭命令。
-
通过向接口点过载太多数据来创建拒绝服务攻击
这只是一个起点。这个想法是在你能想象到的任何地方注入错误。这可能需要编写脚本、Cron 作业或任何必要的手段来引发这些错误。
它是如何工作的...
考虑到远程系统在生产中可能不可用的可能性,我们应该在开发环境中引入这种情况。这将鼓励我们在系统中编写更高的容错性。
在我们引入像 Netflix 那样的随机运行混沌猴之前,我们需要确保我们的系统可以手动处理这些情况。例如,如果我们的系统包括两个服务器之间的通信,一个公平的测试是拔掉一个盒子的网络电缆,模拟网络故障。当我们验证我们的系统可以继续工作并且采取了可接受的手段时,我们可以添加脚本来自动执行这些操作,最终是随机的。
审计日志是验证我们的系统是否处理这些随机事件的有价值的工具。如果我们可以读取一个显示强制网络关闭的日志条目,然后看到类似时间戳的日志条目,我们可以轻松地评估系统是否处理了这种情况。
在构建好之后,我们可以开始处理系统中随机引入的下一个错误。通过遵循这个循环,我们可以提高系统的健壮性。
还有更多...
这并不完全符合自动化测试的范畴。这也是非常高层次的。很难进一步详细说明,因为要注入的错误数据类型需要对实际系统有深入的了解。
这与模糊测试有什么区别?
模糊测试是一种测试方式,其中无效的、意外的和随机的数据被注入到软件的输入点中(en.wikipedia.org/wiki/Fuzz_testing)。如果应用程序失败,这被认为是一个失败。如果没有失败,那么它就通过了。这种测试方式走的方向类似,但 Netflix 撰写的博客文章似乎比简单地注入不同的数据要深入得多。它谈到了杀死实例和中断分布式通信。基本上,任何你能想到的在生产中可能发生的事情,我们都应该在测试环境中尝试复制。Fusil(bitbucket.org/haypo/fusil)是一个旨在提供模糊测试的 Python 工具。你可能想调查一下它是否适合你的项目需求。
有没有工具可以帮助这个?
Jester(用于 Java),Pester(用于 Python)和Nester(用于 C#)用于进行突变测试(jester.sourceforge.net/)。这些工具找出了测试用例未覆盖的代码,改变源代码,然后重新运行测试套件。最后,它们会提供一个报告,报告了什么被改变了,什么通过了,什么没有通过。它可以揭示测试套件覆盖和未覆盖的内容,这是覆盖工具无法做到的。
这并不是一个完整的混沌猴,但它通过试图破坏系统提供了一种帮助,迫使我们改进我们的测试制度。要真正构建一个完整的系统可能不适合在一个测试项目中,因为它需要根据它所要运行的环境编写自定义脚本。