一、安装
composer require --dev phpunit/phpunit ^6.5
composer require --dev phpunit/dbunit
二、编写测试
A.PHPUnit编写测试
1.基本惯例与步骤:
* 针对类Class的测试写在类ClassTest中
* ClassTest(通常)继承自PHPUnit\Framework\TestCase
* 测试都是命名为test*的公用方法,也可以在方法的文档注释块(docblock)中使用@test标注将其标记为测试方法
* 在测试方法内,类似于assertEquals()这样的断言方法用来对实际值与预期值的匹配做出断言
2.当你想把一些东西写到print语句或者调试表达式中时,别这么做,将其写成一个测试来代替
StackTest.php
B.测试的依赖关系
1.单元测试主要是作为一种良好实践来编写的,它能帮助开发人员识别并修复 bug、重构代码,还可以看作被测软件单元的文档。要实现这些好处,理想的单元测试应当覆盖程序中所有可能的路径。一个单元测试通常覆盖一个函数或方法中的一个特定路径。但是,测试方法并不一定非要是一个封装良好的独立实体。测试方法之间经常有隐含的依赖关系暗藏在测试的实现方案中
2.PHPUnit支持对测试方法之间的显式依赖关系进行声明。这种依赖关系并不是定义在测试方法的执行顺序中,而是允许生产者(producer)返回一个测试基境(fixture)的实例,并将此实例传递给依赖于它的消费者(consumer)们
* 生产者,是能生成被测单元将其作为返回值的测试方法
* 消费者,是依赖于一个或多个生产者及其返回值的测试方法
3.使用@depends标注来表达测试方法之间的依赖关系,如果需要传递对象副本而非引用,则应当用@depends clone替代@depends
4.测试可以使用多个@depends标注,需要保证某个测试所依赖的所有测试均出现于这个测试之前
5.拥有多个@depends标注的测试,其第一个参数是每一个生产者提供的基境,第二个参数是第二个生产者提供的基境,以此类推
MultipleDependenciesTest.php、DependencyFailureTest.php、DependencyAndDataProviderComboTest.php
C.数据供给器
1.测试方法可以接受任意参数。这些参数由数据供给器方法提供。用@dataProvider标注来指定使用哪个数据供给器方法
2.数据供给器方法必须声明为public,其返回值要么是一个数组,其每个元素也是数组;要么是一个实现了Iterator接口的对象。每个数组都是测试数据集的一部分,将以它的内容作为参数来调用测试方法
3.当使用到大量数据集时,最好逐个用字符串键名对其命名,避免用默认的数字键名,这样输出的信息会更加详细些
4.如果测试同时从@dataProvider方法和一个或多个@depends测试接收数据,那么来自于数据供给器的参数将先于来自所依赖的测试参数
5.如果一个测试依赖于另一个使用了数据供给器的测试,仅当被依赖的测试至少能在一组数据上成功时,依赖于它的测试才会运行。使用了数据供给器的测试,其运行结果是无法注入到依赖于此测试的其他测试中的
6.所有的数据供给器方法的执行都是在对setUpBeforeClass静态方法的调用和第一次对setUp方法的调用之前完成的。因此,无法在数据供给器中使用创建于这两个方法内的变量。这样PHPUnit才能计算测试的总数量。
DataTest.php
D.对异常进行测试
1.使用expectException()、expectExceptionCode()、expectExceptionMessage()、expectExceptionMessageRegExp()方法可以为被测代码所抛出的异常建立预期
2.也可以用@expectException、@expectExceptionCode、@expectExceptionMessage、@expectExceptionMessageRegExp标注
ExceptionTest.php、ExpectedErrorTest.php
E.对PHP错误进行测试
1.默认情况下PHPUnit将测试在执行中触发的PHP错误、警告、通知都转换为异常
2.PHP的error_reporting运行时配置会对PHPUnit将哪些错误转换为异常有所限制
3.对异常进行测试是越明确越好,对太笼统的类进行测试有可能导致不良副作用
4.如果测试依靠会触发错误的PHP函数,例如fopen,有时候在测试中使用错误抑制符会很有用。通过抑制住错误通知,就能对返回值进行检查,否则会导致抛出异常
ErrorSuppressionTest.php
F.对输出进行测试
1.有时候,想要断言(比如说)某方法的运行过程中生成了预期的输出(通过echo或print)。PHPUnit\Framework\TestCase类使用PHP的输出缓冲特性来为此提供必要的功能支持
2.使用expectOutputString()方法来设定所预期的输出,如果没有产生预期的输出,测试将计为失败
3.输出进行测试的方法
* expectOutputRegex(string $regularExpression)设置输出预期为输出应当匹配正则表达式
* expectOutputString(string @expectedString)设置输出预期为输出应当与$expectedString字符串相等
* setOutputCallback(callable $callback)设置回调函数,用来做诸如将实际输出规范化之类的动作
* string getActualOutpu()获取实际输出
4.严格模式下本身产生输出的测试将会失败
OutputTest.php
G.错误相关信息的输出
1.当有测试失败时,PHPUnit全力提供尽可能多的有助于找出问题所在的上下文信息
2.当生成的输出很长而难以阅读时,PHPUnit将对其进行分割,并在每个差异附近提供少数几行上下文信息
三、命令行测试执行器
1.对于每个测试的运行,PHPUint命令行工具输出一个字符来指示进展:
【.】当测试成功时输出
【F】当测试方法运行过程中一个断言失败时输出
【E】当测试方法运行过程中产生一个错误时输出
【R】当测试被标记为有风险时输出
【S】当测试被跳过时输出
【I】当测试被标记为不完整或未实现时输出
2.PHPUnit区分失败(failure)与错误(error),失败是违背了PHPUnit断言,错误是意料之外的异常,错误往往比失败更容易修复
A.命令行选项
* -h|--help,帮助
* UnitTest,运行由UnitTest类提供的测试
* --coverage-clover,为运行的测试生成带有代码覆盖率信息的XML格式的日志文件,仅当安装了tokenizer和Xdebug这两个PHP扩展后才可用
* --coverage-crap4j,生成Crap4j格式的代码覆盖率报告,仅当安装了tokenizer和Xdebug这两个PHP扩展后才可用
* --coverage-html,生成HTML格式的代码覆盖率报告,仅当安装了tokenizer和Xdebug这两个PHP扩展后才可用
* --coverage-php,生成一个序列化后的PHP_CodeCoverage对象,此对象含有代码覆盖率信息,仅当安装了tokenizer和Xdebug这两个PHP扩展后才可用
* --coverage-text,为运行的测试以人们可读的格式生成带有代码覆盖率信息的日志文件或命令行输出,仅当安装了tokenizer和Xdebug这两个PHP扩展后才可用
* --log-junit,为运行的测试生成JUnit XML格式的日志文件
* --testdox-html和--testdox-text,为运行的测试以HTML或纯文本格式生成敏捷文档
* --filter,只运行与给定模式匹配的测试
* --testsuite,只运行名称与给定模式匹配的测试套件
* --group,只运行来自指定分组(可以多个)的测试。可以用@group标注为测试标记其所属的分组,@author标注是@group的一个别名,允许按作者来筛选测试
* --exclude-group,排除来自指定分组的测试
* --list-groups,列出所有有效的测试分组
* --test-suffix,只查找文件名以指定后缀(可以多个)结尾的测试文件
* --report-useless-tests,更严格对待事实上不测试任何内容的测试
* --strict-global-state,更严格对待全局状态篡改
* --strict-coverage,更严格对待意外的代码覆盖
* --disallow-test-output,更严格对待测试执行期间产生的输出
* --disallow-todo-tests,不执行文档注释块中含有@todo标注的测试
* --enforce-time-limit,根据测试规模对其加上执行时长限制
* --process-isolation,每个测试都在独立的PHP进程中运行
* --no-globals-backup,不要备份并还原$GLOBALS
* --static-backup,备份并还原用户定义的类中的静态属性
* --colors,使用彩色输出,三个值:never完全不使用,auto当前终端默认,always总是彩色输出
* --columns,定义输出所使用的列数
* --stderr,选择输出到STDERR而非STDOUT
* --stop-on-error,首次错误出现后停止执行
* --stop-on-failure,首次错误或失败后停止执行
* --stop-on-risky,首次踫到有风险的测试时停止执行
* --stop-on-skipped,首次碰到到跳过的测试时停止执行
* --stop-on-incomplete,首次碰到不完整的测试时停止执行
* --verbose,输出更详尽的信息,如不完整或跳过的测试的名称
* --debug,输出调试信息,如当一个测试开始执行时输出其名称
* --loader,指定要使用的PHPUnit_Runner_TestSuiteLoader实现
* --repeat,将测试重复运行指定次数
* --testdox,将测试进度以敏捷文档方式报告
* --printer,指定要使用的结果输出器(printer)
* --bootstrap,在测试前先运行一个“bootstrap”PHP文件
* --configuration,-c,从XML文件中读取配置信息
* --no-configuration,忽略当前工作目录下的phpunit.xml与phpunit.xml.dist
* --include-path,向PHP的include_path开头添加指定路径(可以多个)
* -d,设置指定的PHP配置选项的值
四、基境(fixture)
1.在编写测试时,最费时的部分之一是编写代码来将整个场景设置成某个已知的状态,并在测试结束后将其复原到初始状态,这个已知的状态称为测试的基境(fixture)
2.PHPUnit支持共享建立基境的代码,在运行某个测试方法前,会调用一个名叫setUp()的模板方法,setUp()是创建测试所用对象的方法,当测试方法运行结束后,不管成功还是失败,都会调用另外一个名叫tearDown()的模板方法,清理测试所有对象的方法
3.测试类的每个测试方法都会运行一次setUp()和tearDown()模板方法,setUpBeforeClass()和tearDownAfterClass()模板方法将分别在测试用例类的第一个测试运行之前和测试用例类的最后一个测试运行之后调用
4.在setUp()中分配了诸如文件或套接字之类的外部资源时才需要实现tearDown(),如果setUp()中只创建纯PHP对象,通常可以忽略tearDown()
5.如果两个setUp()代码有微小差异,把有差异的内容从setUp()移到测试方法内;如果两个setUp()是确实不一样,那么需要另外一个测试用例类
6.在测试之间共享基境的需求都源于某个未解决的设计问题,有实际意义的多测试间共享基境的例子是数据库链接
7.在测试之间共享基境会降低测试的价值,潜在的设计问题是对象之间并非松散耦合
8.使用单件(singleton)的代码很难测试,使用全局变量的代码也一样,代码与全局变量之间会强烈耦合,一个测试对全局变量的改变可能会影响另一个
9.$backupGlobalsBlacklist,变量可以提供全局变量黑名单;@backupGlobals标注可以用来控制对全局变量的备份与还原操作;@backupStaticAttributes标注可以用于在每个测试之前备份所有已声明类的静态属性值并在其后恢复
StackTest4.php、TemplateMethodsTest.php、Database4_3.php
五、组织测试
A.用文件系统来编排测试套件
1.把所有测试用例源文件放在一个测试目录中,通过对测试目录进行递归遍历,PHPUnit能自动发现并运行测试
2.这种方法的缺点是无法控制测试的运行顺序,可能导致测试的依赖关系方面的问题
B.用XML配置来编排测试套件
1.如果phpunit.xml或phpunit.xml.dist存在于当前工作目录并且未使用--configuration,将自动从此文件中读取配置
/5_1test/phpunit.xml
六、有风险的测试
1.PHPUnit可以更严格对待事实上不测试任何内容的测试,可以用命令行--report-useless-tests或在PHPUnit的XML中设置beStrictAboutTestsThatDoNotTestAnything="true"来启用,如果某个测试未时行任何断言,它将被标记为有风险
2.可以更严格对待意外的代码覆盖,用命令行--strict-coverage或在XML配置文件中设置beStrictAboutCoversAnnotation="true"来启用,如果某个带有@covers标注的测试执行了未在@covers或@uses标注中列出的代码,它将被标记为有风险
3.可以更严格对待测试执行期间产生的输出,用命令行--disallow-test-output或在XML中设置beStrictAboutOutputDuringTests ="true"来启用,如果某个测试产生了输出,将被标记为有风险
4.测试执行时长的超时限制,如果安装了PHP_Invoker包并且pcntl扩展可用,可以对测试的执行时长进行限制
5.可以更严格的对待篡改全局状态的测试,用命令行--strict-global-state或在XML中配置beStrictAboutChangesToGlobalState="true"
七、未完成的测试与跳过的测试
A.未完成的测试
1.空测试的问题是PHPUnit框架会将它们解读为成功
2.PHPUnit_Framework_IncompleteTest是一个标记接口,用于将测试方法抛出的异常标记为测试未完成或目前尚未实现而导致的结果,PHPUnit_Framework_IncompleteTestError是这个接口的标准实现
4.命令行测试执行器中的输出标记为I
5.用于未完成测试的API,void markTestIncomplete(string $message),将当前测试标记为未完成,并用$message作为说明信息
B.跳过测试
1.并非所有测试都能在任何环境中运行,用markTestSkipped()方法来跳过此测试
2.命令行测试执行器中的输出标记为S(测试是R)
3.用于跳过测试的API,void markTestSkipped(string $message),将当前测试标记为已跳过,并用$message作为说明信息
C.用@requires来跳过测试
1.可以用@requires标注来跳过测试用例的一些常见前提条件
* @requires PHP 5.3|7.1……,PHP版本
* @requires PHPUnit 3.6.3…… PHPUnit版本
* @requires OS Linux|WIN32|WINNT 系统版本
* @requires function 任何对于 function_exists而言有效的参数
* @requires extension 任何扩展模块名
SampleTest7_1.php、DatabaseTest7_2.php、DatabaseTest7_3.php
八、数据库测试
A.数据库测试的难点
1.需要考虑的变数:
* 数据库和表
* 向表中插入测试所需要的行
* 测试运行完毕后验证数据库的状态
* 每个新测试都要清理数据库
2.测试代码应当尽可能简短精简:
* 你不希望因为生产代码的小变更而需要对测试代码进行数据可观的修改
* 你希望在哪怕好几个月以后也能轻松地阅读并理解测试代码
3.本质上说,数据库是全局输入变量
B.数据库测试的四个阶段
1.单元测试四个阶段:
* 建立基境(fixture)
* 执行被测系统
* 验证结果
* 拆除基境(fixture)
2.数据库扩展进行测试的流程:
* 清理数据库:在所有表上执行TRUNCATE操作清空
* 建立基境:将迭代所有指定的基境数据行并将其插入到对应的表里
* 运行测试
* 验证结果
* 拆除基境
C.PHPUnit数据库测试用例的配置
1.如果测试代码用到了数据库扩展模块,需要扩展另一个抽象TestCase(PHPUnit\DbUnit\TestCaseTrait)类,要求实现getConnection()和getDataSet()
2.PHPUnit的数据库扩展模块需要用PDO库来实现跨供应商抽象访问数据库连接,PDO连接仅仅用于清理和建立基境
3.getDataSet()方法定义了在每个测试执行之前的数据库初始状态应该是什么样,数据库的状态由PHPUnit_Extensions_Database_DataSet_IDataSet所代表的DataSet数据集和由PHPUnit_Extensions_Database_DataSet_IDataTable所代表的DataTable数据表这两个概念进行抽象
4.setUp()中会调用一次getDataSet()方法来接收基境数据集并将其插入数据库
D.理解DataSet(数据集)和DataTable(数据表)
1.DataSet和DataTable是围绕着数据库表、行、列的抽象层,通过一套简单的API,底层数据库内容被隐藏在对象结构之下,这个对象结构也可以用其他非数据库数据源来实现
2.预期内容可以用诸如XML、YAML、CSV文件或者PHP数组等方式来表达
3.在测试中,数据库断言的工作流由三个步骤组成:
* 用表名称来指定数据库中的一个或多个表(实际上是指定了一个数据集)
* 用你喜欢的格式(YAML、XML等等)来指定预期数据集
* 断言这两个数据集陈述是彼此相等的
4.数据库TestCase类强制要求定义一个基境数据集,用它来:
* 根据此数据集所指定的所有表名,将数据库中对应表内的行全部删除
* 将数据集内数据表中的所有行写入数据库
5.三种不同类型:基于文件的、基于查询的、筛选与组合
6.Flat XML DataSet(平直XML数据集):
* 一种非常简单的XML格式,根节点为,根节点下每个标签代表数据库中的一行数据,标签就等于表名,而每一个属性代表一个列
* 在Flat XML DataSet中,要处理NULL值会非常麻烦,必须保证每个表的第一行不包含NULL值,只有后继的那些行才能省略属性,建议只在不需要NULL值的情况下使用Flat XML DataSet
* 使用createFlatXmlDataset()创建实例对象
7.XML DataSet(XML数据集):
* 避免了NULL值问题,在根节点下,可以指定、column、row、value、null标签
* 使用createXmlDataSet()创建实例对象
8.MySQL XML DataSet(MySQL XML数据集):
* 可以用mysqldump工具来生成这种模式的文件
* 使用createMySQLXMLDataSet()来创建实例对象
9.YAML DataSet(YAML数据集):没有工厂方法,需要手动进行实例化
10.CSV DataSet(CSV数据集):无法指定NULL值
11.Array DataSet(数组数据集):可以处理NULL值,不需要为断言提供额外文件
12.Query(SQL)DataSet(查询SQL数据集)
13.Database (DB) DataSet(数据库数据集):通过访问测试所使用的数据库链接,可以自动创建包含数据库所有表以及其内容的DataSet
14.Replacement DataSet(替换数据集):是已有数据集的修饰器(decorator),能够将数据集中任意列的值替换为其他替代值
15.DataSet Filter(数据集筛选器):为需要包含在子数据集中的表和列指定白/黑名单
16.Composite DataSet(组合数据集):能将多个已存在的数据集聚合成单个数据集
17.假如数据库中使用了外键,必须指定好表的顺序,避免外键约束失败
E.数据库连接API
1.getConnection()方法返回的连接接口方法:
* createDataSet()方法创建一个Database (DB) DataSet
* createQueryTable()方法用于创建QueryTable实例,需要为其指定名称和所使用的SQL查询,当涉及到结果/表的断言这个方法会很方便
* getRowCount()提供了一种方便的方式来取得表中的行数,并且还可以选择附加一个WHERE子句来在计数前对数据行进行过滤
F.数据库断言API
1.对表中数据行的数量作出断言:$this->getConnection()->getRowCount('guestbook')
2.对查询的结果作出断言:assertTablesEqual();
G.常见问题
1.PHPUnit要求测试套件开始时所有数据库对象必须全部可用,由于每个测试都会彻底清空数据库,因此无须为每个测试重新创建数据库
2.只有在基境的清理与建立阶段还有断言检定时用到PDO
3.如果没有对TestCase中的getConnection()方法所创建PDO实例进行缓存,那么每个数据库测试都会增加一个名多个数据库连接
MyGuestbookTest8_1.php、MyApp_Tests_DatabaseTestCase8_3.php、GuestbookTest8_3.php、8_1Test/、数组DataSet类
九、测试替身
1.Gerard Meszaros介绍了测试替身的概念:
* 有时候对被测系统(SUT)进行测试是很困难的,因为它依赖于其他无法在测试环境中使用的组件。这有可能是因为这些组件不可用,它们不会返回测试所需要的结果,或者执行它们会有不良副作用。在其他情况下,我们的测试策略要求对被测系统的内部行为有更多控制或更多可见性。
* 如果在编写测试时无法使用(或选择不使用)实际的依赖组件(DOC),可以用测试替身来代替。测试替身不需要和真正的依赖组件有完全一样的行为方式;他只需要提供和真正的组件同样的API即可,这样被测系统会以为它是真正的组件!
2.PHPUnit提供的createMock($type)和getMockBuilder($type)方法可以在测试中用来自动生成对象,可以充当任意指定原版类型(接口或类名)的测试替身
3.createMock()方法直接返回指定类型(接口或类)的测试替身实例,替身的创建使用了最佳实践的默认值(不可执行原始类的__construct()和__clone()方法,且不对传递给测试替身的方法的参数进行克隆),如果这些默认值非你所需,可以用getMockBuilder()方法并使用流畅式接口来定制测试替身的生成过程
4.默认情况下,原版类的所有方法都会被替换为只会返回null的伪实现(其中不会调用原版方法)
5.局限性:final、private与static,无法对其进行上桩(stub)或模仿(mock)
A.Stubs(桩件)
1.将对象替换为(可选地)返回配置好的返回值的测试替身的实践方法称为上桩(stubbing)。可以用桩件(stub)来“替换掉被测系统所依赖的实际组件,这样测试就有了对被测系统的间接输入的控制点。这使得测试能强制安排被测系统的执行路径,否则被测系统可能无法执行”
2.仅当原始类中不包含名字为“method”的方法时,才能正常运行,如果包含,就必须用$stub->expects($this->any())->method('doSomething')->willReturn('foo');
3.willReturn($value)返回简单值,相当于will($this->returnValue($value))
4.有时想要将(未改变的)方法调用时所使用的参数之一作为桩件的方法的调用结果来返回,可以使用returnArgument()
5.在用流畅式接口进行测试时,让某个已上桩的方法返回对桩件对象的引用有时会很有用,使用returnSelf()
6.有时候,上桩的方法需要根据定义的参数清单来返回不同的值,可以用returnValueMap()方法将参数和相应的返回值关联起来建立映射
7.如果上桩的方法需要返回计算得到的值而不固定值或某个参数,可以用returnCallback()来让上桩的方法返回回调函数或方法的结果
8.相比于建立回调方法,更简单的选择是直接给出期望返回值的列表,可以用onConsecutiveCalls()方法来做到这个
9.除了返回一个值之外,上桩的方法还能用throwException()抛出一个异常
10.可以自行编写桩件,被广泛使用的资源是通过单个外观(facade)来访问的,因此很容易就能用桩件替换掉资源
11.需要上桩的功能往往集中在同一个对象中,这就改善了内聚度,将功能通过单一且一致的接口呈现出来,就降低了这部分与系统其他部分之间的耦合度
B.仿件对象(Mock Object)
1.将对象替换为能验证预期行为(例如断言某个方法必会被调用)的测试替身的实践方法称为模仿(mocking)
2.可以用仿件对象(mock object)“作为观察点来核实被测系统在测试中的间接输出。通常,仿件对象还需要包括桩件的功能,因为如果测试尚未失败则仿件对象需要向被测系统返回一些值,但是其重点还是在对间接输出的核实上。因此,仿件对象远不止是桩件加断言,它是以一种根本上完全不同的方式来使用的”
3.局限性:对预期的自动校验,只会对在某个测试的作用域内生成的仿件对象进行自动校验
4.with()方法可以携带任何数量的参数,对应于被模仿的方法的参数数量,可以对方法的参数指定更加高等的约束而不仅是简单的匹配
5.withConsecutive()方法可以接受任意多个数组作为参数,具体数量取决于欲测试的调用,每个数组都是对被仿方法的相应参数的一组约束,就像with()中那样
6.callback()约束用来进行更加复杂的参数校验,此约束的唯一参数是一个PHP回调项(callback),此PHP回调项接受需要校验的参数作为其唯一参数,并应当在参数通过校验时返回true,否则返回false
7.匹配器:
* any(),当被评定的方法执行0次或更多次时匹配成功
* never(),当被评定的方法从未执行时匹配成功
* atLeastOnce(),当被评定的方法执行至少一次时匹配成功
* once(),当被评定的方法执行恰好一次时匹配成功
* exactly(int $count),当被评定方法执行恰好$count次时匹配成功
* at(int $index),当被评定的方法是第$index个执行的方法时匹配成功
C.对特质(Trait)与抽象类进行模仿
1.getMockForTrait()方法返回一个使用了特定特质(trait)的仿件对象,给定特质的所有抽象方法将都被模仿
2.getMockForAbstractClass()方法返回一个抽象类的仿件对象,给定抽象类的所有抽象方法都被模仿
D.对Web服务(Web Services)进行上桩或模仿
1.使用getMockFromWsdl(),返回的桩件或者仿件是基于以WSDL描述的web服务
E.对文件系统进行模仿
1.vfsStream是对虚拟文件系统的流包覆器(stream wrapper),可用于模仿真实文件系统,composer安装:mikey197/vfsStream
2.如果不使用诸如vfsStream这样的虚拟文件系统,就无法在隔离外部影响的情况下对setDirectory()方法进行测试
SomeClass9_1.php、StubTest9_1.php、SubjectAndObserver9_2.php、SubjectTest9_2.php、TraitClassTest9_3.php、AbstractClassTest9_3.php、Example9_5.php、ExampleTest9_5.php
十、测试实践
A.在开发过程中
1.当需要对软件的内部结构进行更改时,实际上是要在不影响其可见行为的情况下让它更加容易理解、更加易于修改,测试套件对于重构而言是非常宝贵的
2.有助于改进项目的编码与设计:
* 所有单元测试均正确运行
* 代码传达其设计原则
* 代码没有冗余
* 代码所包含的类和方法的数量降至最低
B.在调试过程中
1.压住冲动:
* 确认能够重现此缺陷
* 在代码中寻找此缺陷的最小规模表达
* 编写一个目前会失败而缺陷修复后将会成功的自动测试
* 修复缺陷
2.寻找缺陷的最小可靠重现使你有机会去真正检查缺陷的原因。当修复了缺陷之后,所编写的测试则有助于提高缺陷真正被修复的几率,因为新加入的测试降低了未来修改代码时又破坏此修复的可能性。而之前所编写的所有测试则降低了在不经意间导致其他问题的可能性
3.进行单元测试的好处:
* 进行测试让代码的作者和评审者对补丁能够产生正确的结果有信心
* 编写测试用例对开发者而言是一种很好的发现边缘情况的原动力
* 进行测试提供了一种良好的方法来快速捕捉退步(Regression),并且能用来保证退步不会重复出现
* 单元测试就如何使用API提供了可正常工作的范例,能够大大帮助文档编制工作
十一、代码覆盖率分析
1.计算机科学中所说的代码覆盖率是一种用于衡量特定测试套件对程序源代码测试程度的指标。拥有高代码覆盖率的程序相较于低代码低概率的程序而言测试的更加彻底、包含软件bug的可能性更低
A.用于代码覆盖率的软件衡量标准
1.行覆盖率(Line Coverage)按单个可执行行是否已执行到进行计量
2.函数与方法覆盖率(Function and Method Coverage)按单个函数或方法是否已调用到进行计量。仅当函数或方法的所有可执行行全部已覆盖时PHP_CodeCoverage才将其视为已覆盖
3.类与物质覆盖率(Class and Trait Coverage)按单个类或特质的所有方法是否全部已覆盖进行计量。仅当一个类或性状的所有方法全部已覆盖时PHP_CodeCoverage才将其视为已覆盖
4.Opcode覆盖率(Opcode Coverage)按函数或方法对应的每条opcode在运行测试套件时是否执行到进行计量,一行代码通常会编译得到多条opcode,进行行覆盖率计量时,只要其中任何一条opcode被执行就视为此行已覆盖
5.分支覆盖率(Branch Coverage)按控制结构的分支进行计量,测试套件运行时每个控制结构的布尔表达式求值为true和false各自计为一个分支
6.路径覆盖率(Path Coverage)按测试套件运行时函数或者方法内部所经历的执行路径进行计量,一个执行路径指的是从进入函数或方法一直到离开的过程中经过各个分支的特定序列
7.变更风险反模式(CRAP)(Change Risk Anti-Patterns (CRAP) Index)基于代码单元的圈度(cyclomatic complexity)与代码覆盖率计算得出的,不太复杂并具有恰当测试覆盖率的代码将得出较低的CRAP指数
B.将文件列入白名单
1.可以用命令行选项--whitelist或通过配置文件来完成
2.可以在PHPUnit配置信息中设置addUncoveredFilesFromWhitelist="true"来将白名单中包含的所有文件全部加入到代码覆盖率报告中
C.略过代码块
1.一些代码是无法对其进行测试的,可以用@codeCoverageIgnore、@codeCoverageIgnoreStart与@codeCoverageIgnoreaEnd标注
2.标注将会计为已执行,并且不会在代码覆盖情况中被高亮标记
D.指明要覆盖的方法
1.@covers标注可以用在测试代码中来指明测试方法想要对哪些方法进行测试,如果提供了这个信息,则只有指定方法的代码覆盖率信息会被统计
2.可以用@coversNothing标注来指明一个测试不
BankAccountTest11_1.php
十二、测试的其他用途
A.敏捷文档
1.极限编程要求群体代码所有权(collective code ownership),因此所有开发者都需要知道整个系统是如何工作的
2.PHPUnit的TestDox功能着眼于测试类及其所有测试方法的名称
3.敏捷文档也可以以HTML或纯文本格式生成,并写入文件中,用--testdox-html和--testdox-text参数即可
B.跨团队测试
1.一旦用测试将假设文档化,你就拥有了测试
十三、Logging(日志记录)
1.PHPUnit所生成的测试结果XML日志文件是基于JUnit task for Apache Ant所使用的XML日志的
2.PHPUnit所生成的XML格式代码覆盖率信息日志记录不严格地基于Clover,所使用的XML日志的
3.以易于常人了解(human-readable)的格式生成代码覆盖率,输出到命令行或保存成文本文件
十四、扩展PHPUnit
1.编写自定义断言时,最佳实践是遵循PHPUnit自有断言的实现方式
https://github.com/zhangyue0503/php/tree/master/phpunit
https://phpunit.de/manual/current/zh_cn/phpunit-book.html