当前位置: 首页 > 编程日记 > 正文

PHP 单元测试

本文首发于 https://jaychen.cc/article/34
作者 Jaychen

朋友,你听说过安。。。不是,写过单元测试吗。

单元测试是开发过程中必不可少的一环,一个项目有良好的单元测试代码,重构的勇气都大很多。这次写一篇小文来介绍一下 PHP 的单元测试工具 PHPUnit 的使用。

PHPUnit 的使用并不难,这篇文章主要还是充当一个引子,介绍基本概念和使用,有了这篇文章的基础之后,去看官网的文档就会更加顺风顺水。

安装

安装 PHPUnit 的方式很简单,使用 composer 可以一行代码就可以安装。

composer require --dev phpunit/phpunit

安装之后,在 vendor/bin 目录下有一个 phpunit 的可执行文件,这个就是 phpunit 本体了。假设我们项目的目录结构如下:

➜  phpunit tree .├── controller
├── model
├── service
├── test
└── vendor
├── composer.json

其中我们的单元测试代码都放在 test 目录下。使用 composer 来为我们解决 autoload 的问题。

{"autoload": {"psr-4": {"Controller\\": "controller/","Model\\": "model/","Service\\": "service/","Test\\": "test/",}},
}

如果你还不懂 composer 自动加载的使用,可以参考这篇文章。最后执行 composer dumpautoload -o 让自动加载生效。

到这里我们的安装就算结束了。如果你使用 phpstorm 进行开发,那么你需要进行如下的配置:

这里指明了从哪里加载 PHPUnit,由于我们使用 composer 安装,所以,这里的文件选择 composer 生成的 autoload.php 文件即可。

使用

好了,假设我们现在进行开发,在 service 目录中添加了一个 CalculateService 的文件,并且编写了一个 abs 的函数。

namespace Service;class CalculateService
{public function abs($num){return abs($num);}
}

现在我们对 abs 函数进行单元测试,PHPUnit 规定了一个测试类必须遵守如下的规定:

  • 单元测试类名必须以 Test 结尾,必须继承 \PHPUnit\Framework\TestCase 基类。
  • 每个测试函数必须以 test 开头。

上面的规定是必须遵守的,如果代码没有遵守规定 PHPUnit 不会把他当做单元测试代码。除了以上的两条,还有一些良好的编码习惯可以参考:

  • 单元测试代码都放在 test 目录下。
  • 每个单元测试类以被测试的类名开头。例如被测试类为 CalculateService,那么单元测试类应该为 CalculateServiceTest
  • 每个单元测试函数应该为被测试函数名结尾。例如被测试函数为 abs,那么单元测试函数应该为 testAbs

根据上面的规范,编写单元测试代码

class UserServiceTest extends \PHPUnit\Framework\TestCase
{public function testAbs(){$userService = new \Service\CalculateService();$this->assertEquals(4, $userService->abs(4));}
}

在上面的测试代码中,调用了我们要测试的函数 abs,然后断言 $userService->abs(4) 的结果为 4。在 phpstorm 中直接在 testAbs 函数处右键选择 run UserServiceTest 执行:

发现在控制台会输出如下内容

Time: 17 ms, Memory: 4.00MBOK (1 test, 1 assertion)

表明 abs 通过了 $userService->abs(4) == 4 的测试用例。这里注意一点,这里并不表明 abs 函数已经通过测试,一个良好的测试应该包含多个测试用例来覆盖尽可能多的可能性。

现在 PHPUnit 基本的单元测试已经运行成功了,在 PHPUnit 的文档中,有更多关于测试的用法。由于 PHPUnit 的用法过多,这里不能一一说明,这里提一些其他用法。

  • PHPUnit 提供了 @test 的注解,如果一个测试函数添加了 @test 注解,那么测试函数名字就不必以 test 开头。
  • \PHPUnit\Framework\TestCase 有一个 setUp 函数,如果自己编写的测试类重写了这个函数,那么每次在开始执行测试函数之前,会先执行 setUp 进行测试之前的初始化。同样,也有一个 tearDown 的函数,如果重写,那么在测试函数执行完毕之后调用 tearDown 函数。
  • .... 更多的内容需参考 PHPUnit 的文档。

phpunit.xml 文件

在上面的例子中,我们使用 phpstorm 逐个执行测试函数,但是如果我们需要一次性执行所有的单元测试,那么我们可以编写 phpunit.xml 文件来实现。

给出一个 phpunit.xml 的编写例子来讲解 phpunit.xml 的作用

<?xml version="1.0" encoding="UTF-8"?>
<phpunit><testsuites><testsuite><directory>test</directory></testsuite></testsuites>
</phpunit>

这里 <directory>test</directory> 指定了测试代码都放在 test 目录下,在 phpstorm 下右键点击 phpunit.xml 文件选择 Run phpunit.xml,phpunit 就会到 test 目录下查找所有单元测试并逐个执行。

除了使用 phpunit.xml 来一次性执行所有的单元测试,还可以在 phpunit.xml 中配置单元测试结果的输出日志。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit>.....<logging><log type="testdox-html" target="tmp/log.html"/></logging>
</phpunit>

此时在执行 phpunit.xml 文件,就会在项目目录下生成一个 tmp/log.html 文件,这个文件记录了所有单元测试的结果。

当然,,,更多 phpunit.xml 配置相关的内容,还是需要查看文档。

Mock 测试

PHPUnit 还提供了 Mock 测试。这里先介绍一下什么是 Mock 测试。

假设 foo 函数调用了 bar 函数,那么在对 foo 函数进行单元测试会有两个问题:

  • foo 函数依赖于 bar 函数的结果,那么在对 foo 进行单元测试的时候必然会引入 bar ,那么这样子单元测试就没意义了,如果测试不通过,那么无法保证 bug 出在 foo 还是 bar。
  • bar 函数可能在测试环境不可执行,那么 foo 无法获取 bar 的执行结果,从而无法对 foo 进行单元测试。

Mock 测试就是为了解决上面的问题而出现的,使用 Mock 我们可以虚拟出一个 bar 的调用,并且假设 bar 调用返回结果。如果还是听不懂,上一段代码就知道了。

class MockTest extends \PHPUnit\Framework\TestCase {public function testGet(){  $stub = $this->createMock(\App\UserService::class);     //1$stub->method('get')->willReturn(3);                     //2$this->assertEquals(3,$stub->get(1));                      //3} 
}

上面的测试函数就使用到了 Mock,一行一行代码来分析:

  • 第一行创建了一个虚拟的 UserService 对象。
  • 第二行假设 UserService 中的 get 函数的返回值为 3。
  • 第三行调用 $stub->get(1) 不会真的去执行 get 函数,而是根据第二行的 willReturn 函数直接返回 3。

以上就是一个简单的 Mock 测试,当然 Mock 测试还有很多复杂的用法,这里没办法一一展开,其实掌握基本的用法,更多复杂的高级用法在实践中碰到了再去查看文档也不迟。

好了,PHPUnit 的基本操作就这些了,单元测试本身并不是一个很难的东西,阻碍单元测试的进行并不是在技术上,更多的是一个项目时间安排的衡量与考虑。

相关文章:

CI框架如何删除地址栏的 index.php

默认 CI 框架显示地址是 http://localhost/ci/index.php/test/ 去掉index.php这样会更好些。1.修改Http.conf的 LoadModule rewrite_module modules/mod_rewrite.so 去掉注释2.ci根目录增加.htaccess文件 <IfModule mod_rewrite.c>RewriteEngine OnRewriteBase /ci#Remov…

网站安全配置(Nginx)防止网站被攻击(包括使用了CDN加速之后的配置)

2019独角兽企业重金招聘Python工程师标准>>> 网站被攻击是一个永恒不变的话题&#xff0c;网站攻击的方式也是一个永恒不变的老套路。找几百个电脑&#xff08;肉鸡&#xff09;&#xff0c;控制这些电脑同时访问你的网站&#xff0c;超过你网站的最大承载能力&…

除了 AI,这些技术为 IIoT 插上飞向“4.0”的翅膀

“中国制造2025、德国工业4.0、美国先进制造”——在21世纪的工业领域&#xff0c;“制造强国”开启新一轮角逐。尽管实现路径各有侧重&#xff0c;但题中之义均是通过互联网和智能技术实现企业生产和管理的降本增效。正因此&#xff0c;IIoT被视为助推智能制造转型升级的“催化…

[20171225]查看并行执行计划注意的问题.txt

[20171225]查看并行执行计划注意的问题.txt--//如果使用dbms_xplan.display_cursor查看并行执行计划注意一些问题,通过例子说明:1.环境:SCOTTbook> &r/ver1PORT_STRING VERSION BANNER------------------------------ -------------- ------…

expires与etag控制页面缓存的优先级

expires指令控制HTTP应答中的“Expires”和“Cache-Control”Header头部信息&#xff0c;启动控制页面缓存的作用time:可以使用正数或负数。“Expires”头标的值将通过当前系统时间加上设定time值来设定。time值还控制"Cache-Control"的值&#xff1a;负数表示no-cac…

API 大赛决赛名单出炉,速来围观!

创新云转型&#xff0c;智慧云服务2021年移动云API应用创新开发大赛正在火热进行中各个赛道激烈PK优秀开发者同台切磋彰显实力&#xff01;10月14日&#xff0c;2021年移动云API应用创新开发大赛复赛在中移软件园双创路演大厅圆满举办。本次复赛分为移动赛道和企业赛道分别进行…

将日期yyyy-MM-dd转为数字大写的形式

/*** 将日期转大写* 例如&#xff1a;2013-05-13转为 二0一三年五月十三日* param date* return */public static String getDxDate(String date){String dateArr[] date.split("-");String year dateArr[0];String month dateArr[1];String day dateArr[2];Str…

DevExpress v17.2新版亮点—WPF篇(四)

2019独角兽企业重金招聘Python工程师标准>>> DevExpress年终击穿底价&#xff0c;单套授权低至67折&#xff01;仅剩最后6天&#xff01;查看详情>>> 用户界面套包DevExpress v17.2终于正式发布&#xff0c;本站将以连载的形式为大家介绍各版本新增内容。本…

CI框架验证码CAPTCHA 辅助函数的使用

使用CAPTCHA 辅助函数很方便生成验证码&#xff0c;但是图片是存储在文件夹下&#xff0c;不是输出流&#xff0c;感觉不够完美&#xff0c;可以拿来用用。 说明&#xff1a;产生4位的随机数&#xff0c;CI根目录下建立captcha文件夹。 <?php $this->load->helper(ca…

GitLab 上市,市值高达 149 亿美元!GitHub 的头号劲敌来了

整理 | 祝涛 出品 | CSDN当地时间周四&#xff0c;知名代码和资源托管服务平台 GitLab&#xff08;股票代码GTLB&#xff09;完成了IPO&#xff08;首次公开募股&#xff09;&#xff0c;在纳斯达克成功上市。GitLab在本次 IPO 中筹集了约 6.5 亿美元。GitLab此前曾计划I…

将ubuntu系统设置静态ip及ssh

2019独角兽企业重金招聘Python工程师标准>>> sudo vim /etc/network/interfaces 输入以下&#xff1a;auto lo iface lo inet loopback auto eno1 iface eno1 inet static address 192.168.1.197 netmask 255.255.255.0 gateway 192.168.1.1 dns-nameserver 192.168…

ECMAScript 5 —— 单体内置对象之Math对象

ECMAScript 还为保存数学公式和信息提供了一个公共位置&#xff0c;即 Math 对象。与我们在 JavaScript 直接编写的计算功能相比&#xff0c;Math 对象提供的计算功能执行起来要快得多。Math 对象中还提供了辅助完成这些计算的属性和方法。 一. Math 对象的属性 Math 对象包含的…

织梦内容管理系统修改

1.如何去掉互动中心 修改根目录下templates——default——index.htm文件&#xff0c;删除以下内容 <div id"rightAD1" style:margin:10px auto"></div> <div class"usercenter">.....到</div> <!-- /usercenter --&…

斯坦福大学 AI100 报告发布:AI 发展速度惊人,但风险也正走进现实

‍‍作者 | 阳光来源 | 学术头条语言处理、计算机视觉和模式识别的巨大进步&#xff0c;意味着人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;每天都在与人们的生活进行交互&#xff0c;从帮助人们选择电影&#xff0c;到帮助人们解决医疗诊断难题方面…

linux===Ubuntu 上安装 Node.js

https://www.cnblogs.com/andfly/p/6681487.html转载于:https://www.cnblogs.com/botoo/p/8118903.html

非计算机专业的学生,从事编程工作的上升壁垒是什么?

很多同学自己的专业并不是计算机&#xff0c;但是看到如今IT行业发展的这么好&#xff0c;也想转行学习编码。但是自己非科班出身&#xff0c;又到了快毕业的年纪&#xff0c;开始学习编程来得及吗&#xff1f;这些年来&#xff0c;很多培训机构都借鉴了少儿编程培训的经验&…

HTTP头信息中的参数Etag

服务器如果是集群&#xff0c;不同服务器返回的 Http Header 中的 Etag 参数不一样。如果是图片是程序生成的&#xff0c;我们可以用 no-cache 这些 header 来控制&#xff0c;但如果这些图片是 apache 或 nginx 等呢&#xff1f;下面开始介绍 Etag&#xff1a;Etag在HTTP1.1中…

利用JS使用POST方式提交请求的方法

2019独角兽企业重金招聘Python工程师标准>>> function post(url, params) {var temp document.createElement("form");temp.action url;temp.method "post";temp.style.display "none";for (var x in params) {var opt document…

input框取消光标颜色手机端不生效

<style> input{ color:transparent; } </style> <input value"我要隐藏光标">//文字颜色可以使用text-shadow属性<style> input{ color:transparent; text-shadow:0 0 0 red; } </style> PC端没问题&#xff0c;但是手机端测试无效。…

http响应Last-Modified和ETag以及Apache和Nginx中的配置

基础知识   1) 什么是”Last-Modified”?   在浏览器第一次请求某一个URL时&#xff0c;服务器端的返回状态会是200&#xff0c;内容是你请求的资源&#xff0c;同时有一个Last-Modified的属性标记此文件在服务期端最后被修改的时间&#xff0c;格式类似这样&#xff1a; …

全面分析再动手的习惯:链表的反转问题(递归和非递归方式)

定义一个方法&#xff08;函数&#xff09;&#xff0c;实现输入一个链表的头结点&#xff0c;然后可以反转这个链表的方向&#xff0c;并输出反转之后的链表的头结点。 typedef struct Node{int data;Node *next; } Node, *List; 链表类的问题&#xff0c;涉及到了很多指针的操…

Facebook 正在研究新型 AI 系统,以自我视角与世界进行交互

编译 | 禾木木出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;你是否能想象 AI 以第一人称视角来理解世界是什么样的呢&#xff1f;未来&#xff0c;以第一人称视角理解世界的 AI 可以开启沉浸式体验的新时代。增强现实&#xff08;AR&#xff09;眼镜和虚拟现实&…

docker-2-简单使用docker-ce

安装请看docker-ce安装文档 docker命令合集 参考docker --help 选项 -D 使用debug模式-l 日志级别(有debug|info|warn|error|fatal)&#xff0c;默认info-v 显示版本管理命令config 管理docker配置container 管理容器image 管理镜像network 管理网络service swarm 常用命令atta…

Apache启用mod_expires模块

mod_expires可以减少10%左右的重复请求&#xff0c;让重复的用户对指定的页面请求结果都CACHE在本地&#xff0c;根本不向服务器发出请求。 在使用之前,首先要确认一下”mod_expires”模组是否有启用.如果是自己安装Apache来架设网页主机的话,这里我们可以透过编辑Apache的”ht…

用 Pyecharts 制作炫酷的可视化大屏

作者 | 俊欣来源 | 关于数据分析与可视化前两篇Pyecharts的文章来帮我们简单的梳理了一下可以用Pyecharts来绘制哪些图表之后&#xff0c;本篇文章我们用pyecharts里面的一些组件&#xff0c;将绘制的图表都组合起来首先Grid组件首先介绍Pyecharts模块当中的Grid组件&#xff0…

compass安装使用960 Grid System

960 Grid System 是一个CSS的页面布局框架 demo: http://960.gs/demo.html 前提&#xff1a;安装Ruby 、NodeJS 步骤1&#xff1a;在命令行下安装css插件&#xff1a; gem install compass-960-plugin 步骤2&#xff1a;创建my_project项目&#xff1a; compass create -r nin…

C语言竟成TIOBE年度编程语言候选!苹果iPhone 7卖得最好!

每年这个时候&#xff0c;都是TIOBE榜单评选年度编程语言的时候。今年&#xff0c;Kotlin成为竞争的热门&#xff0c;让人意外的是&#xff0c;C语言居然也成为了候选编程语言之一。自从被Java摘走王者桂冠&#xff0c;C语言几乎是处于持续下滑状态&#xff0c;没想到2017年竟然…

奇怪吸引子---QiChen

奇怪吸引子是混沌学的重要组成理论&#xff0c;用于演化过程的终极状态&#xff0c;具有如下特征&#xff1a;终极性、稳定性、吸引性。吸引子是一个数学概念&#xff0c;描写运动的收敛类型。它是指这样的一个集合&#xff0c;当时间趋于无穷大时&#xff0c;在任何一个有界集…

简介+原理+绘制,详解 Python「瀑布图」的整个制作流程!

作者|黄伟呢来源|数据分析与统计学之美简介瀑布图&#xff0c;由麦肯锡顾问公司所独创的图表类型&#xff0c;因为形似瀑布流水&#xff0c;所以被大家称之为瀑布图(Waterfall Plot)&#xff0c;在企业经营分析、财务分析中使用较多&#xff0c;用以表示企业成本的构成、变化等…

Ubuntu 忘记root登录密码的解决办法

2019独角兽企业重金招聘Python工程师标准>>> 之前做了个虚拟机&#xff0c;最近需要用到&#xff0c;密码忘记了&#xff0c;下面是在忘记密码的情况下登录系统休修改密码&#xff0c;需要进入GRUB修改kernel镜像启动参数 1、重启电脑长按shift键直到进入下图进入GR…