使用PHPUnit数据库扩展对数据库交互进行防弹

There’s already a great article here that discusses Test Driven Development, but did you know that you can also test the code that interacts with your database? Especially if your application is data intensive, subjecting your CRUD code to a battery of tests is good practice that helps ensure that your application is working correctly.

这里已经有一篇很棒的文章讨论了“测试驱动开发” ,但是您知道您还可以测试与数据库交互的代码吗? 特别是如果您的应用程序是数据密集型应用程序,则对CRUD代码进行一系列测试是一种很好的做法,有助于确保您的应用程序正常运行。

In this article you will be learning how to write database tests in PHP using PHPUnit and its database extension. Accompanying code can be found on GitHub, so feel free to check the source and play with it.

在本文中,您将学习如何使用PHPUnit及其数据库扩展用PHP编写数据库测试。 可以在GitHub上找到随附的代码 ,因此随时检查源代码并使用它。

For this exercise you’ll need PHPUnit and the PHPUnit Database extension. You can install both using PEAR. Just follow the instructions listed in the PHPUnit documentation.

对于本练习,您将需要PHPUnit和PHPUnit数据库扩展。 您可以使用PEAR进行安装。 只需按照PHPUnit文档中列出的说明进行操作即可。

Editor Note Oct 20 2012: You can install both PHPUnit and the PHPUnit Database extension using Composer. The accompanying code on GitHub has been updated to use Composer for installing these dependencies.

编者注:2012年10月20日:您可以使用Composer安装PHPUnit和PHPUnit数据库扩展。 GitHub上的随附代码已更新为使用Composer来安装这些依赖项。

热身 (Warming Up)

It’s time to get your feet wet! That means, just like in any software project, we’ll first examine what we are going to program.

是时候弄湿你的脚了! 这意味着,就像在任何软件项目中一样,我们将首先检查要编程的内容。

Imagine you are the back-end ninja of an online webzine and you’ve just received the spec for a new magazine you’ll be building:

想象一下,您是在线网络杂志的后端忍者,而您刚刚收到了将要制作的新杂志的规格:

  • The magazine will be composed of many sections or webpages.

    该杂志将由许多部分或网页组成。
  • Each section shows at most four feature articles which are set by the editor of that section.

    每个部分最多显示四篇由该部分的编辑者设置的专题文章。

For this article, we’ll only test retrieving the articles when they are going to be displayed on the page. Hence we won’t be concerned with the use case where the editor chooses the articles. We’ll assume the feature articles have already been set and are in the database. This way we are isolating the behavior we want to test so we can focus solely on it (just like how you will actually do it in practice).

对于本文,我们将仅在将其显示在页面上时测试检索文章。 因此,我们将不必担心编辑者选择文章的用例。 我们假设功能文章已经设置好并且在数据库中。 这样,我们隔离了我们要测试的行为,因此我们可以只专注于它(就像您在实践中将如何做)。

Of course a necessary step here since we are writing a database test is to set up data. After all, we need something to test the CRUD operations on!

当然,由于我们正在编写数据库测试,因此此处必需的步骤是设置数据。 毕竟,我们需要一些东西来测试CRUD操作!

Use the database dump included in the source code section to create the database in MySQL. Here’s a bird’s eye view:

使用源代码部分中包含的数据库转储在MySQL中创建数据库。 这是鸟瞰图:

alt

There are three ways to set up file-based test data, but for our purposes here we’ll use the XML Data Set. Create an XML file named seed.xml; the root element of the document should be dataset. Next, type in the table definitions and row data. For example, here’s how it looks for the articles table:

共有三种方法来设置基于文件的测试数据,但是出于我们的目的,我们将使用XML数据集。 创建一个名为seed.xml的XML文件; 文档的根元素应该是dataset 。 接下来,键入表定义和行数据。 例如,这是它对articles表的查找方式:

<dataset>
 <table name="sections">
  <column>id</column>
  <column>name</column>
  <row>
   <value>1</value>
   <value>Gadgets</value>
  </row>
 </table>
 <table name="articles">
  <column>id</column>
  <column>title</column>
  <column>description</column>
  <column>content</column>
  <column>preview_image</column>
  <column>section_id</column>
  <row>
   <value>1</value>
   <value>Android vs iOS</value>
   <value>Lorem ipsum dolor sit amet</value>
   <value>Aliquam scelerisque rhoncus porttitor. Nullam eget pulvinar magna. In vel lectus ut diam adipiscing porta vitae id nisi.</value>
   <value>android.jpg</value>
   <value>1</value>
  </row>
...
 </table>
</dataset>

I recommend you to write your own data just for practice, although you can always use the sample dataset included with the files for this article. All the tables there are filled already.

我建议您编写自己的数据只是为了练习,尽管您始终可以使用本文文件中附带的示例数据集。 那里的所有表都已填满。

编写测试课 (Writing the Test Class)

At this point we are now actually going to code something! We need to tell PHPUnit to insert the test data into the database every time we run our test, which is done by creating a test class. You can think of the test class as just like any other class but which contains test cases for some other class being tested.

至此,我们实际上将要编写一些代码! 每次运行测试时,我们都需要告诉PHPUnit将测试数据插入数据库中,这是通过创建测试类来完成的。 您可以将测试类视为任何其他类,但是其中包含正在测试的某些其他类的测试用例。

For database tests using PHPUnit, the test class extends PHPUnit_Extensions_Database_TestCase. We can tell PHPUnit how to connect to the database by overriding the getConnection() method, and then where to get our test data by overriding the getDataSet() method:

对于使用PHPUnit的数据库测试,测试类扩展了PHPUnit_Extensions_Database_TestCase 。 我们可以通过覆盖getConnection()方法来告诉PHPUnit如何连接到数据库,然后通过覆盖getDataSet()方法来告诉我们如何获取测试数据:

<?php
require_once "PHPUnit/Autoload.php";

class ArticleDAOTest extends PHPUnit_Extensions_Database_TestCase
{
    public function getConnection() {
        $db = new PDO(
            "mysql:host=localhost;dbname=bulletproof", 
            "root", "password");
        return $this->createDefaultDBConnection($db, "bulletproof");
    }

    public function getDataSet() {
        return $this->createXMLDataSet("seed.xml");
    }
...
}

Suppose you and the other developers agree on the following interface:

假设您和其他开发人员在以下界面上达成共识:

<?php
interface IArticleDAO
{
    public function getArticles($sectionId, $isHome);
}

The first test to write is also the simplest. That is, we are first coding for the case where a section is not a home page. We expect to get the articles that only belong to that section. According to my seed.xml file, one such section is the Gadgets section. It has an id of 1 and has the following articles:

编写的第一个测试也是最简单的。 也就是说,我们首先对部分不是主页的情况进行编码。 我们希望获得仅属于该部分的文章。 根据我的seed.xml文件 ,“小工具”部分就是这样的部分。 id为1,并包含以下文章:

  • Android vs iOS

    Android与iOS
  • Android vs Wp7

    Android与Wp7
  • iOS 5

    iOS 5

Thus, when we get all the articles for this section, the above articles should be returned. This is reflected in the test as shown below (for brevity, I have omitted selecting the other attributes).

因此,当我们获得本节的所有文章时,应返回以上文章。 如下所示,这反映在测试中(为简便起见,我省略了选择其他属性的操作)。

<?php
require_once "PHPUnit/Autoload.php";
require_once "ArticleDAO.php";

class ArticleDAOTest extends PHPUnit_Extensions_Database_TestCase
{
...
    public function testGetArticlesNonHome() {
        $articleDAO = new ArticleDAO();
        $articles = $articleDAO->getArticles(1, false);
        $this->assertEquals(
            array(
                array("id" => 1, "title" => "Android vs iOS"),
                array("id" => 2, "title" => "Android vs Wp7"),
                array("id" => 3, "title" => "iOS 5")),
            $articles);
    }
...
}

The testGetArticlesNonHome() method first instantiate a new instance of the article data access object (DAO) just as if this were being used in production code already. The DAO is responsible for encapsulating and abstracting the calls to the database. Then the DAO’s getArticles() method is called. This is the method we want to test, so the results are placed in the $articles variable for inspection.

testGetArticlesNonHome()方法首先实例化商品数据访问对象 (DAO)的新实例,就像已在生产代码中使用它一样。 DAO负责封装和抽象对数据库的调用。 然后调用DAO的getArticles()方法。 这是我们要测试的方法,因此将结果放置在$articles变量中以进行检查。

Once we have the results of the method call in $articles, they are compared against something that is expected. In this case, we expect an array of the three articles that the Gadgets section has. The assertEquals() convenience method that comes with PHPUnit is used to compare equality.

一旦我们在$articles有了方法调用的结果,就将它们与预期的结果进行比较。 在这种情况下,我们期望Gadgets部分拥有三篇文章的数组。 PHPUnit附带的assertEquals()便捷方法用于比较相等性。

通过测试 (Passing the Test )

In TDD, failure is the first step. If you run the test now it will fail because we haven’t written the implementation yet.

在TDD中,失败是第一步。 如果您现在运行测试,则它将失败,因为我们尚未编写实现。

jeune@Miakka:~/Bulletproofing$ phpunit ArticleDAOTest.php
PHPUnit 3.6.7 by Sebastian Bergman.

PHP Fatal error:  Class 'ArticleDAO' not found in /home/jeune/ArticleTest/ArticleDAOTest.php on line 18
PHP Stack trace:
...

The next step is to write the code that will let us pass the test (that is, get the articles from the database according to the business logic). Let’s start a class that implements the interface agreed on earlier with the other developers:

下一步是编写使我们通过测试的代码(即根据业务逻辑从数据库中获取文章)。 让我们开始一个类,该类实现先前与其他开发人员约定的接口:

<?php
class ArticleDAO implements IArticleDAO
{
    public function getArticles($sectionId, $isHome) {
        $db = new PDO(
            "mysql:host=localhost;dbname=bulletproof", 
            "root", "password");

        $result = $db->query("SELECT a.id, a.title FROM features f LEFT JOIN articles a ON f.article_id = a.id AND f.section_id = 1");
        $articles = array();
        while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
            $articles[] = $row;
        }
        $result->closeCursor();

        return $articles;
    }
}

Don’t forget to include the above class into the test class’s file.

不要忘记将上述类包含在测试类的文件中。

Now run the test again and you should see a passing test:

现在再次运行测试,您应该看到通过测试:

jeune@Miakka:~/Bulletproofing$ phpunit ArticleDAOTest.php
PHPUnit 3.6.7 by Sebastian Bergman.

.

Time: 0 seconds, Memory: 6.50Mb

OK (1 test, 1 assertion)

修复错误 (Fixing Bugs)

You’ve passed the test, committed your code, and sent it off for QA. You give yourself a pat on the back and start bumming around at 9gag. But wait, QA detects a bug!

您已通过测试,提交了代码,并将其发送给质量检查人员。 您可以拍一下自己的背部,然后在9gag左右开始蹦蹦跳跳 。 但是,等等,质量检查发现了错误!

When a section doesn’t have any articles set, it returns articles from nowhere! To confirm, you need to write your seed.xml to have a section with no articles.

当某节没有任何文章设置时,它将无处返回文章! 为了确认,您需要编写您的seed.xml来包含没有文章的部分。

<dataset>
 <table name="sections">
  <column>id</column>
  <column>name</column>
  <row>
   <value>1</value>
   <value>Gadgets</value>
  </row>
  <row>
   <value>2</value>
   <value>Programming</value>
  </row>
 </table>
...
<dataset>

Account for the new bug by writing a new test for the case where a section has no articles:

通过针对某节没有文章的情况编写新的测试来解决新错误:

<?php
class ArticleDAOTest extends PHPUnit_Extensions_Database_TestCase
{
...
    public function testGetArticlesNonHomeNoArticles() {
        $articleDAO = new ArticleDAO();
        $articles = $articleDAO->getArticles(2, false);
        $this->assertEquals(array(), $articles);
    }
...
}

True enough, the implementation fails when we run the test:

确实,当我们运行测试时,实现失败:

jeune@Miakka:~/Bulletproofing$ phpunit ArticleDAOTest.php
PHPUnit 3.6.7 by Sebastian Bergman.

F.

Time: 0 seconds, Memory: 6.75Mb

There was 1 failure:
1) ArticleDAOTest::testGetArticlesNonHomeNoArticles
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ @@
 Array (
+    0 => Array (...)
+    1 => Array (...)
+    2 => Array (...)
 )

/home/jeune/ArticleTest/ArticleDAOTest.php:33

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

The error message says we expected an array with no contents but the method call returned an array with contents. That is obviously a bug! Try to pass the new test by correcting the code.

错误消息说我们期望一个没有内容的数组,但是方法调用返回了一个有内容的数组。 那显然是一个错误! 尝试通过更正代码来通过新测试。

摘要 (Summary)

In this article we’ve seen how to write a database test in PHP using PHPUnit’s Database Extension which makes it easier to ensure that code that interfaces with the database is correct. More importantly, it gave us confidence to make changes when fixing bugs and when improving/refactoring our code. Software is subject to a lot of changes, just like anything else in life. Writing automated tests can help you adapt your code to those changes more efficiently and confidently.

在本文中,我们已经看到了如何使用PHPUnit的数据库扩展用PHP编写数据库测试,这使得更容易确保与数据库接口的代码正确。 更重要的是,它使我们有信心在修复错误以及改进/重构代码时进行更改。 就像生活中的其他任何事物一样,软件也会发生很多变化。 编写自动化测试可以帮助您更有效,更自信地使代码适应这些更改。

Image via Tatiana Popova / Shutterstock

图片来自Tatiana Popova / Shutterstock

翻译自: https://www.sitepoint.com/bulletproofing-database-interactions/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值