您的位置 首页 php

PHPUnit单元测试新解-流,重构,TDD与设计模式

5.4 单元测试的应用

单元测试是一门注重实践的开发方式。虽然前面介绍了不少理论知识,但也是为了能让大家知其然,知其所以然。这一节将来介绍如何在平时开发中应用实践单元测试。

5.4.1 为Bugfixed编写单元测试用例

维护历史遗留系统的成本是很高的。这是因为旧系统经过几年的迭代,并且中间交接多个团队来维护,至今已经复杂到连曾经参与开发的人都难以理解。其次,虽然旧系统还可以继续运行,但时不时会出现一些问题,可能是功能需求未能充分满足,也可能是非功能性的需求达不到要求。最后,当然也是没有单元测试的。

如果你正在维护一个历史遗留系统,刚好它又出现问题了。先别急,也许这是一件好事。问题即机遇。采用与以往不同的解决方式,我们不仅能很好地解决当前这个问题,还能渐而远之,优化整个旧系统残留的更大问题。有时,要做一个敢搬石头的人。

旧系统是复杂的,它里面有很多业务规则,很多控制开关,很多嵌套层级,还充斥着很多临时粗糙的解决方案。为了能快速、有效解决所发现的缺陷,应坚持一个原则:改什么,测什么。

图5-4 改什么,测什么,图片素材摘自网络

犹如在一个管道系统里面,哪个部件坏了,就换一个新的元件。只要能保证新的元件,进来的口径以及输出的口径和将要替换的旧元件相符合,就可以完美替换,将新的元件融入到原来的旧系统中。对于旧系统的缺陷bugfixed也一样,我们可以编写新的接口,并对其进行充分测试,保证接口签名、参数列表和原来的一样,确保输出的结果、格式也和原来的一样,那么我们就有信心能实现顺利的替换,并修复已有的问题。

5.4.2 为重构优化编写单元测试套件

对旧系统的Bugfixed编写单元测试,是在点的应用;而为重构优化编写单元测试套件,则是线的应用。

仍然是以旧系统为主,在历史遗留系统中,到处是比长城还长的代码。有很长很长的事务型脚本,也有很大很大的上帝类。它们都是不分层级、不分关注点、不分查询和命令操作的,代码晦涩难懂,模型混乱不清。根据领域驱动开发,结合重构的手法,可以对其进行先局部、再整体的重构和优化。为保证重构优化后的代码,不改变原有的功能和特性,可以使用单元测试来搭建360度的安全网。

这样,原来一个PHP文件,就会分解成多个PHP文件。原来一个类,就会划分成多个高内聚、低耦合的小类。针对这些多个文件、多个小类进行单元测试,就可以构成一个完整的模块测试套件。

当整个测试套件都正常通过时,我们重构的信心也会随之大增。

例如,在我曾经参与的项目中,有一个车型搜索列表的功能模块,原来的做法是典型的事务型脚本写法,全部代码都放置在一个文件list.php里面。经过重构后,新的代码文件结构是:

 $ tree ./list/
./list/
├── list_controller_class.php
├── list_query_class.php
 └── list_query_item_class.php  

而与之平行的测试代码文件结构是:

 $ tree ./tests/list/
./tests/list/
├── list_controller_class_Test.php
 └── list_query_class_Test.php  

里面的实现细节,暂时可以不用关注。这里举个例子,重点在于如何为重构后的模块编写单元测试套件。

5.4.3 为新域编写单元测试体系

进入一家新公司,或者加入一个新的团队,也有时候是从零到一,研发一个新系统,启动一个新项目。这时,我们可以配套地为新域搭建单元测试体系。这是面的应用。

为整个系统编写单元测试,搭建测试体系,需要考虑的因素比较多。在初始化方面,可以将全部需要用到的通用桩、替身提前进行初始化,并且设定测试基境。在划分测试套件时,可以根据不同的业务线、模块之间的关系,组装不同的、多个测试套件。在代码测试覆盖率方面,可以在配置好xdebug和PHPUnit.xml的whitelist白名单后,就可以得到HTML、文本或者XML等格式的覆盖率报告。

此时,借助一键测试,可以快速运行整个系统的单元测试体系。下面来讲一下何谓一键测试。

当我们拥有越来越多的单元测试时,为了可以快速执行测试套件,我们可以通过PHPUint的XML配置文件来组织需要执行的单元测试。在此基础上,为了快速执行整个项目的全部测试套件(如各个产品线的测试套件),我们需要一个脚本:run_tests.sh。并放置以下shell脚本代码:

 #!/bin/bash
function showInfo()
{
    echo ""
    echo "========= [ $1 ] ========="
    echo ""
    echo ""
}
curPath=$(cd "$(dirname "$0")"; pwd)

# TODO: 以下是一些调用的测试套件或指定的脚本 ...
phpunit -c $curPath/phpunit_suite_1.xml

phpunit -c $curPath/phpunit_suite_2.xml

phpunit -c $curPath/phpunit_suite_3.xml  

当一键测试的脚本完成后,我们就可以不断把重要的、需要被执行的单元测试纳入此脚本,然后只需要跑一下该脚本,就可以快速看到类似以下的输出:

 $./run_tests.sh 
PHPUnit 3.7.29 by Sebastian Bergmann.
Configuration read from /path/to/tests/phpunit_suite_1.xml
..
Time: 4.52 seconds, Memory: 5.25Mb
OK (2 tests, 0 assertions)

……  

我们应该限制此脚本的总运行时间在5分钟之内,以便我们可以经常快速执行并得到我们希望得到的反馈:项目代码是否存在问题!尤其是发布分支代码在上线前和线上BUG紧急修复时。

基于一键测试,我们就可以实现自动化测试,例如按照以下架构部署,搭建自动化测试系统。

图5-5 简明部署的自动化测试系统

5.4.4 与持续集成的联动

更为专业的自动化测试,应该与目前主流的持续构建进行集成。网上已经有很多这方面的资料,这里不再重复赘述,只作简单提及。

最后,我们再简单认识一下渐进式单元测试,对于初学PHPUnit的同学,会有所帮助。

很多时候,我们一开始不知如何编写单元测试,原因在于我们需要面临的测试场景较为复杂。主要的测试难点有:数据多变,业务复杂。如果我们把整个流程梳理成各个环节,便可以得到以下的通用过程:数据源 -> 数据获取 -> 数据处理 -> 待测试的结果。这是基于数据流整理的关键路径。

由此看出,数据源越不稳定,待测试的结果也就越不稳定。从而导致了在同一个测试环境上不同时间点执行的测试结果不一样,或者同一时间但测试环境不同所执行的测试结果也不一样。但是,不稳定的数据可以在测试上使用内存数据库、测试替身、数据提供器等进行固定,在开发上可以使用分层结构、依赖注入、设计模式等方式解决。而复杂的业务正是我们需要验证的,更需要高度关注的热区。

鉴于此,我们除了可以利用已知的F.I.R.S.T.原则、构造-操作-检验(BUILD-OPERATE-CHECK)模式来指导如何快速编写和执行测试,我们还可以通过一些策略。我们根据从简单到复杂、由浅到深、逐步深入的策略来编写单元测试,我称之为渐进式单元测试。主要可以划分为三个阶段:

  • 1、对待测试的代码进行调用和执行,以确保没有低级的语法错误;
  • 2、对返回的结果进行结构格式验证,以确保返回的结果是符合指定类型和规范的;
  • 3、对业务的数据作精确验证,以确保业务功能是正常工作的;

如下面的测试代码片段:

     public function testGetBgImgForNewUser()
    {
        $data = new Query_Background();
        $data->userTypeTag = 'new_user';
        $data->appCacheRead = false;
        $data->requestTime = strtotime('2015-01-16 10:00:00');

    //1、对待测试的代码进行调用和执行,以确保没有低级的语法错误;
        $rs = Controller_Background_::getBgImg($data);

    //2、对返回的结果进行结构格式验证,以确保返回的结果是符合指定类型和规范的;
        $this->assertTrue(is_array($rs));
        $this->assertArrayHasKey('index_top_img', $rs);
        $this->assertArrayHasKey('index_right_img', $rs);
        $this->assertArrayHasKey('index_bottom_img', $rs);
        $this->assertArrayHasKey('index_bg_color', $rs);

    //3、对业务的数据作精确验证,以确保业务功能是正常工作的;
        $this->assertEquals('#39;, $rs['index_top_img']);
        $this->assertEquals('#112233', $rs['index_bg_color']);
        $this->assertEquals('#39;, $rs['index_top_img']);
        $this->assertEquals('#2344dF', $rs['index_bg_color']);
    }  

5.5 向前一步

以PHPunit单元测试为起点,向上向下,往左往右,都可以延伸出很多话题。

5.5.1 理性人

在经济学里,有一个术语,叫理性人,意思是指努力实现自己目标的人。

希望作为软件开发工程师,我们也应该学习理性人的做法。在编写单元测试代码时,其实我们是在用代码证明代码。你说你的代码没问题,那还不行,你要证明它。怎么证明?用代码来证明。结合测试驱动开发,我们可以做到突破思维、保障重构、增强设计、提高质量、提升效率。

5.5.2 流,重构,TDD与设计模式

虽然我不想刻意制造更多的术语,由于这里重复提到,我们暂且在这里把这三者称为:开发“三把斧”。它们之间的微妙关系,我一直都在构思总结。最后总结如下:

图5-6 重构、TDD和设计模式

从上图可以看出,最下面的是TDD,也就是说测试驱动开发是一项应该落地的实践,也是我们开发的基础。从头到尾,由始至终,从过去到现在,我们都应该遵循测试驱动开发。说白一点,还没开始编写产品代码时,我们就应该编写测试代码,哪怕最后完成了产品代码,我们依然还要运行测试套件。

在中间,在三个层级的代码,从左到右,我暂且命名为:粗糙的代码、合格的代码、精心的代码,分别代表坏代码、中等代码和好的代码。一般这些都是循序渐进的,即最初我们编写的是粗糙的代码,继而调整为合格的代码,最后雕琢成精心的代码。当然,也有一步到位的情况,但我们这里所讲的适用普通大众的情况。

而在这三等代码之间,要实现往上一级的转换,则需要使用到各种重构的手法。重构的手法多种多样,故而有多个箭头指向,这也就说明了不同的开发人员可以采用不同的重构方式,毕竟条条大路通罗马,代码没有绝对的表现形式。

再往上,即最顶上,则是我们的设计模式。注意,这里使用了虚线,即与实线的重构、实线的TDD不同,它是一种虚的东西。虽然设计模式也有其名称、实现步骤、成例和注意事项等具体的内容,但我觉得设计模式更像是高层的思想,正是它指导了我们往更好的方向前进。所以,在这里我把设计模式作为了我们最高的指导思想。

很明显,综合起来就是一个金字塔或拱形的结构,自上而下,分别是:设计模式、重构和测试驱动开发。

这三者应该是相互影响、相互促进的。缺少了TDD,我们就缺少了有力的保障,就没有了安全可靠的测试基础。缺少了重构,我们就会迷失于如何把代码变得更好的细节中。而缺少了设计模式,我们就会漫无目的地进行重构而不知所终,因为我们不知道最终该如何确切组织项目代码。

开发“三把斧”是很重要的,如果说我为什么开发效率那么高,是因为我和其他开发同学有一些不同之处,那就是掌握了这“三把斧”。如果你尚未对重构、TDD或设计模式有一个良好的理解,我建议你先补充相关的必要基础知识,再继续往下。因为我们将会探讨如何使用这“三把斧”进行高效开发。

5.6 本章小结

本章的主要内容是分享如何编写自动化PHPUnit单元测试,从而突破思维,追求更高的项目质量,提升开发效率。我们从三种不同的世界开始,慢慢进入到测试驱动开发的世界。在这里面,有很多新鲜的、启发性的理论知识,有指导如何编写测试用例的构造-操作-检验模式,F.I.R.S.T原则和三解验证。

代码编写是一定技巧的,例如通用的测试代码骨架就可以使用命令脚本来自动生成,可以节省很多宝贵的开发时间。执行单元测试的方式更是多种多样,从最小粒度的单个测试用例,到运行整个测试套件,都有对应的执行方式。而在编写测试用例时,重点和难点在于如何提供必要的测试数据,这时需要用到九个造假技巧。最后,对于页面渲染输出和接口结果返回,恰如其分的断言艺术,能让我们的测试事半功倍。

单元测试是注重实践的开发方式,我们可以在维护旧系统进行Bugfixed时编写单元测试,改什么,测什么;也可以在重构时,通过单元测试搭建安全网,确保重构后不改变代码原有的功能;也可以为新项目、新系统编写对应配套的自动化单元测试体系。最后,当时机成熟时,我们可以把自动化单元测试和持续构建集成起来,打造更全面、更专业、更系统化的工作方式和现代化开发流程。

单元测试,实际上是用代码证明代码,希望我们能都秉承理性人的作风,做到突破思维、保障重构、增强设计、提高质量、提升效率。最后,重构、TDD和设计模式这“三把斧”之间有着微妙的关系,值得深入去慢慢研究、学习和实践。

文章来源:智云一二三科技

文章标题:PHPUnit单元测试新解-流,重构,TDD与设计模式

文章地址:https://www.zhihuclub.com/154228.shtml

关于作者: 智云科技

热门文章

网站地图