大家该当都有印象,在最初学习编程时,利用C++写一个main函数的代码,实在是分为三部分的:输入、处理和输出。后来,进入实际项目开拓后,随着系统的繁芜度越来越高,规模越来越大,可能对这不雅观念就麻木了。实在,不管是一段程序,还是一个模块,一个框架,一个别系,实质上都可以划分为输入、输出和处理这三部分。这便是大道至简。
还是拿PHP开源框架来说,框架要办理参数输入的问题,这些参数可能是通过php-fpm运行办法通报进来的GET、POST、多媒体数据等,也可能是通过php-cli运动办法通报的命令行参数、流、输入参数等;输出可以是常见的页面渲染返回,也可能是JSON接口数据结果,或者是非常失落败情形下的4xx和5xx页面;处理环节则是前面先容的动态调用,以及前后穿插的事宜侦听、钩子函数、回调函数等。
那么输入、处理和输出,与布局-操作-考验模式之间的关系又是什么呢?大概,细心的读者已经创造了个中的奥妙。这里暂时不给出答案,给大家留一个念想。由于,你思考所收成的,与别人见告你而习得的,深刻程度是不一样的。

布局-操作-考验(BUILD-OPERATE-CHECK)模式,可以理解成:“当... 做...,该当...”。个中,布局包括测试环境的搭建、测试数据前期的准备;操作是指对被测试工具的调用,以及被测试工具之间的通信和帮忙交互;末了考验则是指对业务规则的断言、对功能需求的验证。
例如,对付1+2=3,大略实现的代码和对应的单元测试代码如下,个中可以按布局-操作-考验模式分为三步编写单元测试用例。第一步,布局必要的参数,即高下文场景信息;第二步进行操作,调用计数器的相加方法;第三步,对相加的结果进行自我断言,核对1加2的结果是否即是3。
<?phpuse PHPUnit\Framework\TestCase;class CalculatorTest extends TestCase { public function testAdd() { // Step 1. 布局 $left = 1; $right = 2; // Step 2. 操作 $calculator = new Calculator(); $sum = $calculator->add($left, $right); // Step 3. 考验 $this->assertSame(3, $sum); }}class Calculator { public function add($left, $right) { return $left + $right; }}
这是编写测试用例的基本模式,如果对付编写单元测试代码没有头绪时,可以参考此模式。
5.2.2 F.I.R.S.T.原则在编写单元测试代码时,除了要遵照构成-操作-考验模式外,还要坚持F.I.R.S.T.原则。这五个原则分别是:
Fast 快速Independent 独立Repeatable 可重复Self-validating 自我验证Timely 及时首先,单元测试的实行速率一定假如快速的,能快速运行、快速反馈。如果运行一个测试用例,要等上好几分钟,或者须要好长一段韶光,是对开拓职员自身的不友好,更别说蜗牛般的测试速率。最好的速率便是“秒杀”,即能在1秒钟内完成一个单元测试用例。对付全体测试套件,可以轻微放宽一点韶光限定,总的运行韶光一样平常不要超过去饮水间打杯水的韶光。在我们曾经实行的一键测试里,便是这个韶光哀求。能让开发职员运行一键测试后,打杯水回来就能看到哪里有问题,哪里不对。快速这一特性,非常主要。
其次是独立性。在编写代码,设计系统时,我们都会哀求能做到“高内聚、低耦合”,把类与类之间的依赖关系大略化,避免不必要的认识关系。同样地,在编写单元测试代码时,也须要遵照独立原则,即各个测试用例之间是相互独立的。你失落败了,不会影响我的测试;我非常了,也不会对你的测试造成滋扰。虽然xUnit家族里是可以支持指定测试用例之间的前后顺序和依赖关系,但那是故意识的设计,故意而为的动作,与这里所说的情形不一样。
可重复性是指同一个单元测试用例,该当是可以反复运行并能得到相同的结果。这一点很好理解,就即是对付特定的一个函数,我每次供应的参数都是一样的,那么每次这个函数给我返回的结果该当是一样的。既然这点很大略,乃至我们已经是这样做了,为什么还要再提一次呢?这是由于,和前面独立性的原则类似,很多时候我们都是在无意识层面下犯了缺点。打仗过命令查询职责分离(CQRS)模式的同学都知道,接口可以分为两种,分别是命令操作和查询操作。命令操作是会产生副浸染的,而查询操作则是幂等操作。当待校验的操作是命令操作时,那么就会改变内部状态或数据的值,当重复实行单元测试时,有可能就会得到不一样的结果。由于工具的方法常日都不是一个纯函数。为了能重复实行,不断验证,遵照这个原则很主要。
自我验证,是单元测试的一大特色,基本上我们编写单元测试都是为了这一原则。具备了自我验证的单元测试,组合起来便是自动化单元测试。传统的开拓办法,基本是写代码、手工测试与调试,个中在调试这一过程花费的韶光是非常多的,韶光占比也很高。至于手工测试,可以想象得到的便是,一个开拓职员写好代码后,打开浏览器,然后通过页面点击、输入一系列表单数据,或者通过在链接后面手动加上必要的参数,然后进入到刚才编写开拓模块,进行测试验证。正所谓,机关重重,想要进行到待测试的代码,就要历经千山万水,重重困难,并穿过十面埋伏。更别提,还要重复测试,还要在一个月后再回来重复验证。可想而知人工验证会有多痛楚。而代码自我验证则不同,它是可以做到自动化的,而且一旦实现自动化,离高效就不远矣。由于开拓职员可以从重复繁琐的人工操作抽身出来去做一些更有代价的事情。
末了一点是及时性。这一点很大略理解,但也是很主要的,特殊在测试驱动开拓时。如果有测试不通过的情形,就该当及时创造、及时报告出来,以便能及时改动,形成一个快速反馈的闭环机制。
关于这五个原则,大略先容到这里。
5.2.3 三角验证接下来是三角验证。闇练节制这个思维工具,对付中型项目、乃至核心底层模块、大型系统都非常有启示性。一样平常在辩论某个不雅观点时,都会从正反两面来论述。在进行单元测试时,则要从多个维度进行验证、判断、检测,不要轻易放过任何一个可疑之处。
测试路径也可以分为两大类,一类是正常情形的Happy Path快乐路径,另一类是快乐路径的反面,如:Sad Path悲哀路径、Bad Path缺点路径或者Exception Path非常路径。以是在布局测试用例时,要对期望输入的参数进行等价类的划分,并且为这几类不同的路径准备相应的测试数据。我们可以结合示例来加深对这块的理解。
回到上面两数相加的单元测试,刚才只是测试了正常快乐路径,1加2即是3。但如果客户端(这里所说的客户端是指利用Calculator计数器的客户端代码)通报过来的是非数字而是字符串呢,这时会发生什么,或者我须要若何去验证?通过主动设想可能的场景并提前为之设计和开拓,而不是等到末了发生了问题再来被动填补,前者的上风会更大。对付字符串相加会发生什么,这取决于我们对付计数器的目标和定位,以及它所处的高下文约束。如果我们要实现的便是一个小学生利用的数字打算器,那么字符串相加是不许可的,是该当禁止的。那终极呢?我是该断言有非常抛出,还是断言是否返回一个默认值,还是直接代码因参数类型缺点而结束运行?通过这样一系列的自我提问或者集体聪慧谈论,从不同的维度开始,到提出问题,到如何断言,末了可以引出详细的实现办法。
还记得我们前面说过的吗?在面向过程天下里,如果一个函数处理失落败了,按照老例是返回一个FALSE默认值,并伴随一个缺点编码。而在面向工具的天下里,如果失落败,按照老例是抛出一个特定的非常。
因此,为了使得计数器的代码更健壮,可以处理造孽的参数,可以添加一个针对缺点路径的测试用例。
class CalculatorTest extends TestCase { / @expectedException IllegalParameterException / public function testAddString() { // Step 1. 布局 $left = "逆流而上"; $right = "PHP企业级系统开拓"; // Step 2. 操作 $calculator = new Calculator(); $sum = $calculator->add($left, $right); }}
这里利用了PHPUnit的expectedException表明,表示期望抛出的非常是IllegalParameterException。为使得此测试通过,再来根据约束实当代码就很随意马虎了,以下是一个参考的实现版本。
class Calculator { public function add($left, $right) { if (!is_numeric($left) || !is_numeric($right)) { throw new IllegalParameterException('参数造孽'); } return $left + $right; }}class IllegalParameterException extends Exception { }
现在,正反两面我们都验证了,但这里说的是三角验证,最最少还有第三个维度的测试。那第三个维度的测试又是什么呢?三,历来都是一个泛数,三角验证并不就只是三个不同维度的验证,还可以是四角、五角、八角验证。我们要懂得举一反三,变通。第三个维度,可以说是一个开放式的验证,你可以根据当前的情形、关注点和项目的需求,进行补充。个中一个方向便是对未来可预见的扩展性的测试。
例如在这里的计数器,如果我们想实现复数的相加呢?现在不支持,如果往后须要支持时,是否可以在不影响原来的设计、原来已经供应的接口根本上完美过渡、顺利升级、完备兼容呢?这是一个值得思考的问题。提前考虑可预见的扩展性,不是让大家过度设计,而是哀求我们编写交付的代码能具备一定的生存能力,能在不断变革的繁芜场景下具备快速迭代和演进的更新能力温柔应能力。
大略回顾一下,布局-操作-考验模式是针对单个测试用例而提出来的,目的是为了编写的测试代码更有条理性。F.I.R.S.T.这五个原则,不仅适用于单个测试用例,还适用于大部分的测试套件,在整体上都是有辅导意义的。末了,三角验证则是从断言层面提出更多启示性的测试哀求。
这三部分理论知识都是别人之前总结和分享的,这里只是轻微再分享一下我的心得。