jobeet第八天:单元测试

*这一系列文章来源于Fabien Potencier,基于Symfony1.4编写的Jobeet Tutirual

Symfony中的测试

Symfony中有两种类型的测试:单元测试功能测试。单元测试是验证每个方法或者函数是否能够按照预期的结果正确运行。每个单元测试应该尽可能不要对其它模块有依赖关系。而功能测试则是验证整个应用程序的行为是否正确。今天我们先来讲解单元测试,而明天我们将会讲解功能测试。

Symfony2集成了一个独立的测试库:PHPUtilPHPUtil为我们提供了一个好用的测试框架。为了能够运行测试,我们需要安装PHPUtil3.5.11或者更新的版本。

如果你还没安装过PHPUtil,你可以按照下面进行安装:

不管是单元测试还是功能测试,它都是一个存放在包(bundle)里的Tests/子目录下的一个PHP类。如果你遵循了这个约定,那么我们就可以在终端下运行下面的命令来对我们的应用程序进行测试:

-c选项是告诉PHPUtilapp/目录下查找配置文件。如果你对PHPUtil的可选项感到好奇的话,你可以去查看app/phputil.xml.dist文件。

一个单元测试往往是只对一个指定的PHP类进行测试。现在我们来开始为Jobeet::slugify()方法写测试吧。

src/Ibw/JobeetBundle/Tests/Utils目录下创建一个JobeetTest.php文件。通常来说,Test/下的目录结构应该和bundle目录下的结构是一样的,所以当我们为一个类进行单元测试时,我们需要把它放在Tests/Utils/目录下:

如果需要对指定的类进行单元测试的话,我们可以在命令后面带上参数:

如果测试运行通过,我们应该会得到下面的结果:

我们可以查看PHPUtil文档来了解完整的assertions列表。

为新功能添加测试

对一个空字符串使用slug()方法得到的结果是一个空字符串。我们可以测试一下,它可以得到正确的结果。但一个空字符串作为URL并不是什么好主意。我们来修改slugify()方法,使它处理空字符串的时候返回的结果是”n-a“。

我们可以先写一个测试,然后修改slugify()方法,一直到让测试能够通过,或者我们也可以使用其它类似的办法。虽然一开始编写的测试是不能被通过的,但先写好测试能让我们对预期要实现的代码有一个清晰的方法,这样我们就更加有信心确保编写出的代码的正确性。

现在我们再次运行测试,测试是不能通过的:

现在我们来修改Jobeet::slugify()方法,在方法的开头添加下面的条件判断:

现在测试能通过了,享受一下green bar带来的乐趣吧。

为Bug添加测试

我们可以设想一下,当随着时间的推移,突然有一天你的一位用户给你反馈了一个奇怪的bug:有一些Job信息的链接被错误地转向到了404页面。经过了仔细的调查,你发现了其中的原因,因为有些Job数据中的companyposition或者location的值是空的。

这可能吗?

于是你仔细地查看了数据库中的数据,而且数据表中的这几个列都是定义成不为空(not null)的,所以这是不可能的。

好吧,于是你再仔细想想… 有了,问题找到了。你发现当一个字符串只包含非ASCII字符时,slugify()方法会把它转成一个空字符串。找到了问题的原因是多么愉快的事情呀,于是你马上去修改Jobeet.php改正错误,不过这个主意很差。首先,我们应该来添加一个测试:

运行测试将不会通过。我们修改Jobeet.php,把空字符串判断移动到slugify()方法的末尾:

现在测试已经能通过了。虽然我们测试的覆盖率是100%,但slugfiy()有一个Bug

当写测试的时候,我们可能没有考虑到所有边界值测试用例的情况。当我们发现有Bug时候,我们应该在纠正代码之前先编写测试。这是一件好事,这也意味着我们的代码会一次比一次好。

更好的slugfiy方法

你可能不知道Symfony是法国人开发的,没关系,让我们来添加一个带法国口音(accent)的测试用例(看代码)吧:

运行测试是不能通过的,slugify()方法把é替换成了-,而不是e。一个棘手的问题,这个问题被叫做“transliteration”。哈,幸运的是,如果你安装了iconv库的话,那么问题马上就能被解决了。用下面的代码替换slugfiy()方法:

我们要记得把PHP文件保存为UTF-8编码,这个是Symfony的默认编码,iconvtransliteration使用的也是UTF-8

修改测试文件,只有当iconv有效时才进行测试:

 

代码覆盖率

当编写测试时,我们往往很容易忽略了代码,从而没对这些代码编写适当的测试。如果我们为程序添加了一个新功能或者仅仅只想要得到代码的覆盖率,我们可以使用–coverage-html选项来检查代码的覆盖率:

只有安装了XDebug和所有的相关依赖之后,代码覆盖率检查才能够运行: sudo apt-get install php5-xdebug

我们的cov/index.html看起来应该像下面那样:

08-01

我们需要记住的是,上面的结果仅仅说明所有代码都通过了单元测试,这仅仅意味着每一行都被执行过,不代表所有的边缘用例都被测试过。

Doctrine单元测试

Doctrine进行单元测试是个比较复杂的任务,因为它需要连接数据库。我们已经在开发中使用过Doctrine了,我们来为它编写一个单元测试吧,这是个好习惯哦。

在教程的前几章内容中,我们介绍了可以通过设置来改变应用程序的环境。默认的,Symfony的所有测试都是运行在Test环境下,所以我们来为Test环境配置不同的数据库吧。

app/config/目录下,复制一份parameters.yml并改名为parameters_test.yml。打开parameters_test.yml文件,修改数据库名为jobeet_test。这个文件需要被导入(import),所以我们把它加入到config_test.yml文件中:

 

测试Job实体

首先我们需要在Tests/Entiry/目录下创建一个JobTest.php文件。

setUp()方法会在每次运行测试的时候对数据库进行操作(连接,插入数据)。首先,它会删除当前存在的数据库,然后重新创建数据库并从Fixtures中加载数据。这些操作能够帮助我们在测试环境中每次运行测试的时候都能够有相同的初始数据供测试使用。

 

测试Repository

现在我们为JobRepository类编写测试,来看看我们在前几天内容中实现的方法有没有返回正确的值:

CategoryRepository类编写测试:

写完测试之后运行下面的命令,目的是为了生成所有方法的代码覆盖率:

现在打开浏览器访问http://jobeet.local/cov/Repository.html,我们会看到Repository Tests的代码覆盖率并没有达到100%。

08-02

我们再来为JobRepository类添加一些测试来达到100%的覆盖率。 现在,我们的数据库中有三个Category数据,其中有两个Category中只有0行有效的Job数据,另外一个Category仅仅只有一行有效的Job数据。我们会对少于三行有效的Job数据的Category进行$max$offset参数的测试。为了完成测试,我们用下面的代码替换testGetActiveJobs()函数中的foreach语句块:

再次运行下面的命令:

这次我们再去看看代码覆盖率,你会发现代码覆盖率已经完成到100%了。

08-03

今天我们就讲这些了,我们明天会讲解功能测试,那么我们明天见吧!

 

原文链接:http://www.intelligentbee.com/blog/2013/08/14/symfony2-jobeet-day-8-the-unit-tests/

发表评论