ホームページ  >  記事  >  バックエンド開発  >  PHPUnit テストにおけるモック オブジェクトを理解する

PHPUnit テストにおけるモック オブジェクトを理解する

DDD
DDDオリジナル
2024-09-22 16:17:02529ブラウズ

Understanding Mock Objects in PHPUnit Testing

単体テストを作成するときの重要な課題は、外部システムや依存関係からの干渉なしに、テスト対象のコードに焦点を当てたテストを行うことです。ここで、PHPUnit で モック オブジェクト が登場します。これにより、実際のオブジェクトの動作を制御された方法でシミュレートできるため、テストの信頼性が高まり、保守が容易になります。この記事では、モック オブジェクトとは何か、モック オブジェクトが役立つ理由、PHPUnit でモック オブジェクトを効果的に使用する方法について説明します。

モックオブジェクトとは何ですか?

モック オブジェクトは、単体テストで使用される実際のオブジェクトのシミュレートされたバージョンです。これらにより次のことが可能になります:

  • テスト対象のコードを分離する: モック オブジェクトは依存関係の動作をシミュレートし、テスト結果がそれらの依存関係の実際の実装によって影響を受けないようにします。
  • 依存関係の動作の制御: 特定のメソッドが呼び出されたときにモックがどのように動作するかを指定でき、さまざまなシナリオをテストできます。
  • 相互作用の検証: モックはメソッド呼び出しとそのパラメーターを追跡し、テスト対象のコードが依存関係と正しく相互作用していることを確認します。

モックオブジェクトを使用する理由

モックは、次のシナリオで特に役立ちます:

  • 複雑な依存関係: コードがデータベース、API、サードパーティ サービスなどの外部システムに依存している場合、モック オブジェクトを使用すると、それらのシステムと対話する必要がなくなり、テストが簡素化されます。
  • インタラクション テスト: モックを使用すると、特定のメソッドが正しい引数で呼び出されているかどうかを検証し、コードが期待どおりに動作することを確認できます。
  • テスト実行の高速化: データベース クエリや API リクエストなどの実際の操作により、テストの速度が低下する可能性があります。これらの依存関係をモックすると、テストの実行がより速くなります。

スタブとモッキング: 違いは何ですか?

モック オブジェクトを扱うときは、スタブモッキング という 2 つの用語に遭遇します。

  • スタブ: モック オブジェクトのメソッドの動作を定義することを指します。たとえば、メソッドに特定の値を返すように指示します。
  • モッキング: メソッド呼び出しの数とそのパラメーターの検証など、メソッドの呼び出し方法に関する期待値の設定が含まれます。

PHPUnit でモック オブジェクトを作成して使用する方法

PHPUnit を使用すると、createMock() メソッドを使用してモック オブジェクトを簡単に作成および使用できます。以下は、モック オブジェクトを効果的に操作する方法を示すいくつかの例です。

例 1: 基本的なモック オブジェクトの使用法

この例では、クラスの依存関係のモック オブジェクトを作成し、その動作を指定します。

use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    public function testMockExample()
    {
        // Create a mock for the SomeClass dependency
        $mock = $this->createMock(SomeClass::class);

        // Specify that when the someMethod method is called, it returns 'mocked value'
        $mock->method('someMethod')
             ->willReturn('mocked value');

        // Pass the mock object to the class under test
        $unitUnderTest = new ClassUnderTest($mock);

        // Perform the action and assert that the result matches the expected value
        $result = $unitUnderTest->performAction();
        $this->assertEquals('expected result', $result);
    }
}

説明:

  • createMock(SomeClass::class) は SomeClass のモック オブジェクトを作成します。
  • Method('someMethod')->willReturn('mocked value') はモックの動作を定義します。
  • モック オブジェクトはテスト対象のクラスに渡され、実際の SomeClass 実装が使用されないようにします。

例 2: メソッド呼び出しの検証

場合によっては、メソッドが正しいパラメーターで呼び出されているかどうかを確認する必要があります。その方法は次のとおりです:

public function testMethodCallVerification()
{
    // Create a mock object
    $mock = $this->createMock(SomeClass::class);

    // Expect the someMethod to be called once with 'expected argument'
    $mock->expects($this->once())
         ->method('someMethod')
         ->with($this->equalTo('expected argument'))
         ->willReturn('mocked value');

    // Pass the mock to the class under test
    $unitUnderTest = new ClassUnderTest($mock);

    // Perform an action that calls the mock's method
    $unitUnderTest->performAction();
}

キーポイント:

  • Expects($this->once()) は、someMethod が 1 回だけ呼び出されることを保証します。
  • with($this->equalTo('expected argument')) は、メソッドが正しい引数で呼び出されているかどうかを検証します。

例: PaymentProcessor を使用したテスト

モック オブジェクトの実際のアプリケーションを示すために、外部の PaymentGateway インターフェイスに依存する PaymentProcessor クラスの例を見てみましょう。 PaymentGateway の実際の実装に依存せずに、PaymentProcessor の processPayment メソッドをテストしたいと考えています。

PaymentProcessor クラスは次のとおりです:

class PaymentProcessor
{
    private $gateway;

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

    public function processPayment(float $amount): bool
    {
        return $this->gateway->charge($amount);
    }
}

これで、PaymentGateway のモックを作成して、実際の支払いゲートウェイと対話せずに processPayment メソッドをテストできるようになりました。

モックオブジェクトを使用した PaymentProcessor のテスト

use PHPUnit\Framework\TestCase;

class PaymentProcessorTest extends TestCase
{
    public function testProcessPayment()
    {
        // Create a mock object for the PaymentGateway interface
        $gatewayMock = $this->createMock(PaymentGateway::class);

        // Define the expected behavior of the mock
        $gatewayMock->method('charge')
                    ->with(100.0)
                    ->willReturn(true);

        // Inject the mock into the PaymentProcessor
        $paymentProcessor = new PaymentProcessor($gatewayMock);

        // Assert that processPayment returns true
        $this->assertTrue($paymentProcessor->processPayment(100.0));
    }
}

テストの内訳:

  • createMock(PaymentGateway::class) creates a mock object simulating the PaymentGateway interface.
  • method('charge')->with(100.0)->willReturn(true) specifies that when the charge method is called with 100.0 as an argument, it should return true.
  • The mock object is passed to the PaymentProcessor class, allowing you to test processPayment without relying on a real payment gateway.

Verifying Interactions

You can also verify that the charge method is called exactly once when processing a payment:

public function testProcessPaymentCallsCharge()
{
    $gatewayMock = $this->createMock(PaymentGateway::class);

    // Expect the charge method to be called once with the argument 100.0
    $gatewayMock->expects($this->once())
                ->method('charge')
                ->with(100.0)
                ->willReturn(true);

    $paymentProcessor = new PaymentProcessor($gatewayMock);
    $paymentProcessor->processPayment(100.0);
}

In this example, expects($this->once()) ensures that the charge method is called exactly once. If the method is not called, or called more than once, the test will fail.

Example: Testing with a Repository

Let’s assume you have a UserService class that depends on a UserRepository to fetch user data. To test UserService in isolation, you can mock the UserRepository.

class UserService
{
    private $repository;

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

    public function getUserName($id)
    {
        $user = $this->repository->find($id);
        return $user->name;
    }
}

To test this class, we can mock the repository:

use PHPUnit\Framework\TestCase;

class UserServiceTest extends TestCase
{
    public function testGetUserName()
    {
        // Create a mock for the UserRepository
        $mockRepo = $this->createMock(UserRepository::class);

        // Define that the find method should return a user object with a predefined name
        $mockRepo->method('find')
                 ->willReturn((object) ['name' => 'John Doe']);

        // Instantiate the UserService with the mock repository
        $service = new UserService($mockRepo);

        // Assert that the getUserName method returns 'John Doe'
        $this->assertEquals('John Doe', $service->getUserName(1));
    }
}

Best Practices for Using Mocks

  1. Use Mocks Only When Necessary: Mocks are useful for isolating code, but overuse can make tests hard to understand. Only mock dependencies that are necessary for the test.
  2. Focus on Behavior, Not Implementation: Mocks should help test the behavior of your code, not the specific implementation details of dependencies.
  3. Avoid Mocking Too Many Dependencies: If a class requires many mocked dependencies, it might be a sign that the class has too many responsibilities. Refactor if needed.
  4. Verify Interactions Sparingly: Avoid over-verifying method calls unless essential to the test.

Conclusion

Mock objects are invaluable tools for writing unit tests in PHPUnit. They allow you to isolate your code from external dependencies, ensuring that your tests are faster, more reliable, and easier to maintain. Mock objects also help verify interactions between the code under test and its dependencies, ensuring that your code behaves correctly in various scenarios

以上がPHPUnit テストにおけるモック オブジェクトを理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。