如何写单元测试

2016-06-09 17:48:53 +08:00
 corboy

最近想学写单元测试,但是无从下手。有什么写的好的单元测试可以参考吗?

PHP

3384 次点击
所在节点    程序员
11 条回复
murmur
2016-06-09 17:55:14 +08:00
单元测试就是软件测试教程讲那些 正常值 边界值 异常值 不符合规定的输入 都来一套
不过话说回来有保证全周期覆盖单元测试的么来回一下。。军工的不算
iyaozhen
2016-06-09 18:23:52 +08:00
建议先看着 PHPUnit 的入门文档,讲得还比较清楚。

然后我再无耻的“ SEO ”之前在公司小组内分享的《 PHP 单元测试-mock 和数据库测试》 https://iyaozhen.com/php-unit-test-mock-and-db-test.html

个人感觉写单元测试和调试时自测差多不,主要是一些规范化的套路。
julyclyde
2016-06-09 20:24:45 +08:00
哈,很容易写着写着就发现自己写的模块太大了,得拆
fyibmsd
2016-06-09 20:31:09 +08:00
有一个项目, PHPDesignPattern 就是 php 各种设计模式的实现,有完整的测试用例,可以参考下
wujunze
2016-06-09 21:03:47 +08:00
PHPUnit +1
nonesuccess
2016-06-09 22:21:41 +08:00
带数据库或者外部资源的单元测试要怎么搞?

或者说,怎样才能把单元测试和数据库分开?
murmur
2016-06-09 22:24:01 +08:00
@nonesuccess 数据库不建议 mockup 有些东西上了数据库才发现不对就不好玩了
还是用真实数据吧 junit 的话一般写一些代码在 before after 里捏造一些数据 测试完再把数据删掉
feiyuanqiu
2016-06-09 22:45:21 +08:00
@nonesuccess 一般来说,如果待测试的代码使用到了第三方的 api ,应该尽可能地避免在代码里直接依赖,尽量使用依赖注入,然后测试的时候注入一个 mock 的依赖对象,就可以很简单地进行测试了,比如有这样的代码:
class Sample {
function sampleMethod() {
$client = new ThirdpartyClient();
$client->doSomething();
...
}
}
这种代码就非常难以测试,因为你的测试结果会依赖于这个真实的 ThirdpartyClient ,而它的行为是不可控制的,这个时候最好重构一下代码,将 ThirdpartyClient 作为方法的参数(更好的方式是作为这个类的构造方法的参数,然后用依赖注入容器去处理依赖的注入):
class Sample {
function sampleMethod(ThirdpartyClient $client) {
$client->doSomething();
...
}
}
这样,在写测试的时候,就可以注入一个模拟 ThirdpartyClient 行为的 Mock 对象( PHPUnit 的 getMockBuilder 方法可以很方便地进行这个工作),来进行测试工作。
你需要记住的就是单元测试的对象是当前这个代码单元的逻辑, ThirdpartyClient 的行为不在这个测试的范畴内,你只需要假设它始终会按照它的接口说明进行返回就可以了

对于数据库测试,我目前采用的方式是 PHPUnit 的 PHPUnit_Extensions_Database_TestCase 工具来做的
构造一个 fake DAO (这个 DAO 连接的是我的测试数据库),每个模块的测试会单独提供测试数据集, PHPUnit 在执行每个测试用例前,会调用 setUp 方法,这个方法会将测试数据集初始化到测试数据库,然后将这个测试 Dao 注入到待测试的模块,这个模块之后的所有数据库操作,都会通过这个测试 Dao 来执行,就保证了每次执行测试,数据的一致。
这里也有个简单的例子:
class OrderServiceTest extends GenericDatabaseTestCase
{
/**
* 订单模块实例
*
* @var OrderService
*/
protected $service;

/**
* 初始化测试数据集
*
* @return \PHPUnit_Extensions_Database_DataSet_CompositeDataSet
* @throws \Exception
*/
public function getDataSet()
{
return $this->loadTestDataset(
[
'Order/orders',
'Order/order_items',
]
);
}

/**
* 初始化基境
*
* @return void
* @throws \Exception
*/
public function setUp()
{
parent::setUp();

$this->service->setDao($this->getMockDao());
}
feiyuanqiu
2016-06-09 22:52:48 +08:00
没写完,按错回复了...那就不多说了
大致的流程就是:
1 、准备测试数据集,测试数据集会填充到测试数据库里
2 、 setUp 方法会在执行每个测试用例前执行,在这里会将测试数据库的连接替换掉模块正常的数据库连接,保证每次执行测试案例的时候测试数据都是一致的,不影响其他环境的
3 、执行测试方法,因为待测试方法的数据库连接已经被我们的测试数据库连接劫持了,所以这个方法里面操作的所有数据库数据都是测试数据库中的数据
4 、执行 tearDown 方法

一般的单元测试看看官方文档就行了

另外,可以看看 google 测试工程师的这个文档 Guide: Writing Testable Code http://misko.hevery.com/code-reviewers-guide/

单元测试对代码质量还是很有帮助的,而且写了几天之后就会对依赖注入非常痴迷...
msg7086
2016-06-09 22:55:07 +08:00
其实做网站的话推荐你看看集成测试(也就是功能测试)。
codeek
2016-06-10 10:37:18 +08:00
单元测试最好的入门方式是 TDD (Test Driven Development),即测试驱动开发。简单来讲,就是先写测试,后写实现代码。

TDD 不是什么高深的概念,只是一种 Agile 的实践方式。很多人习惯先写实现代码,后“加测试”的编码方式,原因有二,一是实现代码还没有,不知道对哪个方法进行测试;二是懒,觉得测试是额外的工作负担,不到万不得已(比如:项目组强制)不会写测试,即使写也就挑几个好弄的方法,加一个正常流的测试。

这样的写法很容易导致几个问题:
1. 实现代码耦合,不便测试;
2. 实现代码过度设计,类文件剧增,代码量一多,维护性绝对不好;
3. 测试覆盖率很低,单元测试是拿来忽悠领导的,跟质量无关。

楼主说自己无从下手,估计是用了“加测试”的方式。如果是这样,那么 refactor (重构) 是你该先学的技能。设计良好的代码,接口很清晰,耦合度低(比如:楼上提到的 DI [依赖注入] ),一般写单元测试非常容易。

TDD 具体的实践方式,这里我暂按不表,网上的教程多如牛毛。其实它的核心理念就是让你知道如何从需求出发,拆接出任务( tasking ),按照任务一条条来写完测试。然后按照测试->实现->重构(红->绿->黄)的圈完善所有的功能。坚持这样,功能就从需求逐步演化成可维护的代码了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/284648

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX