web-dev-qa-db-ja.com

PHPUnitを使用してSymfony2でデータベースを多用した単体テストを設定するにはどうすればよいですか?

私はテストの世界に非常に慣れていないので、正しい方向に進んでいることを確認したいと思います。

phpunitを使用してsymfony2プロジェクトでユニットテストをセットアップしようとしています。

PHPUnitが機能しており、シンプルなデフォルトのコントローラーテストが正常に機能しています。 (ただし、これは機能テストではなく、アプリケーションの単体テストです。)

私のプロジェクトはデータベースの相互作用に大きく依存していますが、 phpunitのドキュメント から理解できる限り、\PHPUnit_Extensions_Database_TestCaseに基づいてクラスを設定し、dbのフィクスチャを作成してそこから作業する必要があります。

しかし、symfony2は、\PHPUnit_Framework_TestCaseから拡張されたWebTestCaseクラスのみを提供します。

それで、私は自分のDataBaseTestCaseを作成する必要があると思いますか。これは主にWebTestCaseをコピーします。違いは、それが\PHPUnit_Extensions_Database_TestCaseから拡張され、そのすべての抽象メソッドを実装することだけです。

または、データベース中心のテストに関するsymfony2の別の「組み込み」推奨ワークフローはありますか?

モデルが適切なデータを保存および取得することを確認したいので、誤ってdoctrineの詳細をテストしてしまいたくありません。

43
k0pernikus

tl; dr:

  • 機能テストのルート全体に行きたい場合に限り、 Sgoettschkesの回答 を調べることをお勧めします。
  • アプリケーションの単体テストを行い、データベースとやり取りするコードをテストする必要がある場合は、読むか、直接ジャンプしてください symfony2 docs
    (さらに最新バージョンがあります symfony5のフィクスチャを使用したテスト

単体テストは、サービスでは意味があり、リポジトリでは意味がありません。そして、それらのサービスはエンティティーマネージャーのモックを使用できます。 (私は言うまでもします:可能であれば、エンティティーのみがそれらに渡されることを期待するサービスを記述します。次に、それらのエンティティーのモックを作成するだけでよく、ビジネスロジックの単体テストは非常に簡単になります。)

アプリケーションの実際の使用例は、symfony2のドキュメント データベースと対話するコードをテストする方法 にかなり反映されていました。

彼らはサービステストのためにこの例を提供します:

サービスクラス:

use Doctrine\Common\Persistence\ObjectManager;

class SalaryCalculator
{
    private $entityManager;

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

    public function calculateTotalSalary($id)
    {
        $employeeRepository = $this->entityManager
            ->getRepository('AppBundle:Employee');
        $employee = $employeeRepository->find($id);

        return $employee->getSalary() + $employee->getBonus();
    }
}

サービステストクラス:

namespace Tests\AppBundle\Salary;

use AppBundle\Salary\SalaryCalculator;
use AppBundle\Entity\Employee;
use Doctrine\ORM\EntityRepository;
use Doctrine\Common\Persistence\ObjectManager;

class SalaryCalculatorTest extends \PHPUnit_Framework_TestCase
{
    public function testCalculateTotalSalary()
    {
        // First, mock the object to be used in the test
        $employee = $this->getMock(Employee::class);
        $employee->expects($this->once())
            ->method('getSalary')
            ->will($this->returnValue(1000));
        $employee->expects($this->once())
            ->method('getBonus')
            ->will($this->returnValue(1100));

        // Now, mock the repository so it returns the mock of the employee
        $employeeRepository = $this
            ->getMockBuilder(EntityRepository::class)
            ->disableOriginalConstructor()
            ->getMock();
        $employeeRepository->expects($this->once())
            ->method('find')
            ->will($this->returnValue($employee));

        // Last, mock the EntityManager to return the mock of the repository
        $entityManager = $this
            ->getMockBuilder(ObjectManager::class)
            ->disableOriginalConstructor()
            ->getMock();
        $entityManager->expects($this->once())
            ->method('getRepository')
            ->will($this->returnValue($employeeRepository));

        $salaryCalculator = new SalaryCalculator($entityManager);
        $this->assertEquals(2100, $salaryCalculator->calculateTotalSalary(1));
    }
}

これらの種類のテストに必要なテストデータベースはありません。

永続層ではなく、ビジネスロジックをテストすることが重要です。

機能テストの場合のみ、後で構築して破棄する独自のテストデータベースを用意することは理にかなっており、大きな問題は次のとおりです。

機能テストはいつ意味がありますか?

以前はすべてのものをテストするが正しい答えだと思っていました。まだそれ自体がほとんどテスト駆動ではなかった多くのレガシーソフトウェアを使用した後、私はもう少しなりました 怠惰な実用的であり、バグによって別の方法で証明されるまで、特定の機能が機能していると見なします。

XMLを解析し、XMLからオブジェクトを作成し、それらのオブジェクトをデータベースに格納するアプリケーションがあるとします。オブジェクトをデータベースに格納するロジックが機能することがわかっている場合(たとえば、会社はデータを必要とし、現時点では壊れていません)、そのロジックがひどい醜い山積みであったとしても、 imminentはそれをテストする必要があります。すべてのように、私のXMLパーサーが正しいデータを抽出することを確認する必要があります。適切なデータが保存されることを経験から推測できます。

機能テストが非常に重要なシナリオがあります。つまり、オンラインショップを作成する場合などです。購入したアイテムをデータベースに保存することはビジネスに不可欠であり、ここではテストデータベース全体を使用した機能テストが絶対的な意味を持ちます。

3
k0pernikus

PHPUnit_Extensions_Database_TestCaseを使用したことはありませんが、主に次の2つの理由によります。

  • うまくスケーリングしません。テストごとにデータベースをセットアップして破棄し、データベースに大きく依存するアプリケーションがある場合、同じスキーマを何度も作成および削除することになります。
  • テスト内だけでなく、開発データベース内にもフィクスチャを置きたいのですが、一部のフィクスチャは本番環境(初期管理ユーザーや製品カテゴリなど)にも必要です。 phpunitでのみ使用できるxml内にそれらを置くことは、私には正しくないようです。

理論上の私のやり方...

doctrine/doctrine-fixtures-bundle をフィクスチャに使用し(どのような目的でも)、データベース全体をすべてのフィクスチャでセットアップします。次に、このデータベースに対してすべてのテストを実行し、テストによってデータベースが変更された場合は必ずデータベースを再作成します。

利点は、テストが読み取るだけで何も変更しない場合、データベースを再度セットアップする必要がないことです。変更の場合は、それをドロップして再度作成するか、変更を元に戻す必要があります。

データベースをセットアップしてからsqliteファイルをコピーし、クリーンなファイルに置き換えて元のデータベースに戻すことができるため、テストにはsqliteを使用しています。そうすれば、データベースを削除して作成し、すべてのフィクスチャを再度ロードしてクリーンなデータベースにする必要がなくなります。

...そしてコード内

symfony2とphpunitを使用してデータベーステストを行う方法に関する記事 を作成しました。

Sqliteを使用していますが、MySQLやPostgresなどを使用するように簡単に変更を加えることができると思います。

さらに考える

うまくいくかもしれない他のいくつかのアイデアはここにあります:

  • データベースを使用する前に(setUpメソッド内で)トランザクションを開始し、tearDownを使用してロールバックするテストセットアップについて一度読んだことがあります。そうすれば、データベースを再度セットアップする必要がなく、一度初期化するだけで済みます。
  • 上記の私の設定には、データベースとの対話なしに単体テストをいくつか実行しただけでも、phpunitが実行されるたびにデータベースが設定されるという欠点があります。データベースが設定されているかどうかを示すグローバル変数を使用し、テスト中にこの変数をチェックしてデータベースが初期化されていないメソッドを呼び出す設定を試しています。そうすれば、テストでデータベースが必要な場合にのみ、セットアップが行われます。
  • Sqliteの1つの問題は、まれにMySQLと同じように機能しないことです。 MySQLとSQLiteで動作が異なる問題がありましたが、MySQLですべてが機能するときにテストが失敗しました。それが何であったかを思い出せません。
35
Sgoettschkes

このクラスを使用できます。

<?php

namespace Project\Bundle\Tests;

require_once dirname(__DIR__).'/../../../app/AppKernel.php';

use Doctrine\ORM\Tools\SchemaTool;

abstract class TestCase extends \PHPUnit_Framework_TestCase
{
/**
* @var Symfony\Component\HttpKernel\AppKernel
*/
protected $kernel;

/**
 * @var Doctrine\ORM\EntityManager
 */
protected $entityManager;

/**
 * @var Symfony\Component\DependencyInjection\Container
 */
protected $container;


public function setUp()
{
    // Boot the AppKernel in the test environment and with the debug.
    $this->kernel = new \AppKernel('test', true);
    $this->kernel->boot();

    // Store the container and the entity manager in test case properties
    $this->container = $this->kernel->getContainer();
    $this->entityManager = $this->container->get('doctrine')->getEntityManager();

    // Build the schema for sqlite
    $this->generateSchema();


    parent::setUp();
}

public function tearDown()
{
    // Shutdown the kernel.
    $this->kernel->shutdown();

    parent::tearDown();
}

protected function generateSchema()
{
    // Get the metadatas of the application to create the schema.
    $metadatas = $this->getMetadatas();

    if ( ! empty($metadatas)) {
        // Create SchemaTool
        $tool = new SchemaTool($this->entityManager);
        $tool->createSchema($metadatas);
    } else {
        throw new Doctrine\DBAL\Schema\SchemaException('No Metadata Classes to process.');
    }
}

/**
 * Overwrite this method to get specific metadatas.
 *
 * @return Array
 */
protected function getMetadatas()
{
    return $this->entityManager->getMetadataFactory()->getAllMetadata();
}
}

その後、エンティティをテストできます。このようなもの(エンティティUserがあると仮定)

//Entity Test
class EntityTest extends TestCase {

    protected $user;

    public function setUp()
    {
         parent::setUp();
         $this->user = new User();
         $this->user->setUsername('username');
         $this->user->setPassword('p4ssw0rd');


         $this->entityManager->persist($this->user);
         $this->entityManager->flush();

    }

    public function testUser(){

         $this->assertEquals($this->user->getUserName(), "username");
         ...

    }

}

この助けを願っています。

ソース:theodo.fr/blog/2011/09/symfony2-unit-database-tests

0
Munir