你会如何测试这段代码? (PHPUnit的)

时间:2010-09-23 07:50:51

标签: database unit-testing tdd phpunit

您如何使用phpUnit测试以下代码?

class Product {

    protected $id = null;

    public function __construct($id) {
        $this->id = $id;
    }

    public function getDescription($language_id) {
        $db_query = mysql_query("SELECT * FROM products_description WHERE products_id = " . (int) $this->id . " AND language = " . (int) $language_id);
        return mysql_fetch_array($db_query);
    }

}


$product = new Product(1);
$product->getDescription(1); // Test this

目前我的测试方式如下:

public function testShouldGetProductsDescription() {
    $product = new Product(1);
    $description = $product->getDescription(1);
    $expected_description = array(
        'name' => 'car',
        'short_text' => 'this is a car',
        'long_text' => 'longer description for the car'
    );
    $this->assertEquals($description, $expected_description);
}

但对我来说这似乎不是一个好的解决方案。 (此代码只是一个示例,可能无法正常工作)

3 个答案:

答案 0 :(得分:2)

我之前已经编写了这样的测试代码,而且让我担心的是,如果数据库的内容发生变化,即使代码仍然正确,我的测试也会失败。

有几个解决方案:

  1. 截断您的产品表并首先插入测试套件所需的记录。 phpUnit有一些可能对此有所帮助的工具,请参阅http://www.phpunit.de/manual/3.2/en/database.html以获取更多信息。
  2. 如果您的Product对象也可用于将产品保存到数据库,则可以测试往返。即使用您的产品对象创建新产品。获取新创建的产品的ID。然后扔掉Product产品对象并创建一个新对象来加载它。这不是真正的单元测试,因为您正在测试对象的两个不同功能,但它确实允许您在不依赖于数据库中的现有数据或截断表的情况下进行测试。
  3. 在运行时注入Product时,将数据库访问权限提取到单独的类。然后可以模拟数据库类以进行测试。这并不一定要求您使用ORM,但它确实意味着您需要更改类接口,这在测试遗留代码时可能无法实现。

答案 1 :(得分:2)

在简单的情况下,特别是与数据库耦合一样,我通常只使用存根对象来存根数据库调用。然后就像这样编写测试:

 public function productFixture($id)
 {  
    $constructor_arg = $id;

    //Array of methods to stub out
    $stubbed_method = array('getDescription'); 

    //Create a mock for the Product class.
    $product = $this->getMock('Product', $stubbed_method, $constructor_arg);

    //Stub out expected results
    $expected_description = array(
        'name'       => 'car',
        'short_text' => 'this is a car',
        'long_text'  => 'longer description for the car'
    );

    //Configure the stub method and its return value
    $product->expects($this->any())
            ->method('getDescription')
            ->will($this->returnValue($expected_description));

    return $product;
}

public function testShouldGetProductsDescription()
{
    $product = $this->productFixture(1);
    $description = $product->getDescription(1);

    $this->assertNotNull($description);

    //You could test the description using assertEquals
    //but I find just testing the field names is less brittle
    $this->assertArrayHasKey('name', $description);
    $this->assertArrayHasKey('short_text', $description);
    $this->assertArrayHasKey('long_text', $description);
}

public function productFixture($id) { $constructor_arg = $id; //Array of methods to stub out $stubbed_method = array('getDescription'); //Create a mock for the Product class. $product = $this->getMock('Product', $stubbed_method, $constructor_arg); //Stub out expected results $expected_description = array( 'name' => 'car', 'short_text' => 'this is a car', 'long_text' => 'longer description for the car' ); //Configure the stub method and its return value $product->expects($this->any()) ->method('getDescription') ->will($this->returnValue($expected_description)); return $product; } public function testShouldGetProductsDescription() { $product = $this->productFixture(1); $description = $product->getDescription(1); $this->assertNotNull($description); //You could test the description using assertEquals //but I find just testing the field names is less brittle $this->assertArrayHasKey('name', $description); $this->assertArrayHasKey('short_text', $description); $this->assertArrayHasKey('long_text', $description); }

有关Stubs / Mocks和PHPUnit的更多信息:PHPUnit Manual: Test Doubles

答案 2 :(得分:1)

您的Product类与数据库紧密耦合,因为它直接调用mysql_query。如果将它松散地耦合到数据库,例如使用Database对象或ProductStore对象,则可以在不实际使用数据库的情况下测试Product类。