モックとスタブの利用方法と実装例

mock-stub

今日のトピックは「モックとスタブの利用方法」です。ソフトウェアテストにおいて、モックとスタブは依存関係をシミュレートするために使用されるテストダブルです。モックは、テスト中に呼び出しの回数やパラメータを検証するオブジェクトで、スタブは特定の入力に対して事前定義された出力を返すオブジェクトです。これらを使用することで、テストの範囲を制御し、依存関係に依存しないテストを実施できます。

目次

基本概念の説明

モック (Mock)

モックは、テスト中にオブジェクトのメソッドが正しく呼び出されているかを検証するために使用されます。通常、モックはテストフレームワークによって生成され、メソッドの呼び出し回数、引数、そして最終的な結果を検証するための機能を提供します。

スタブ (Stub)

スタブは、テスト中に特定の条件下で事前に定義された出力を返すオブジェクトです。スタブは、外部依存関係を取り除き、テストの対象となる機能だけに焦点を当てることを可能にします。通常、スタブは単純なメソッドを持ち、そのメソッドが呼び出されたときに固定された結果を返します。

各言語でのサンプルコード

Python

from unittest import TestCase, mock

class PaymentProcessor:
    def __init__(self, payment_gateway):
        self.payment_gateway = payment_gateway

    def process_payment(self, amount):
        if self.payment_gateway.charge(amount):
            return "Payment successful"
        else:
            return "Payment failed"

class TestPaymentProcessor(TestCase):
    def test_process_payment_success(self):
        mock_gateway = mock.Mock()
        mock_gateway.charge.return_value = True

        processor = PaymentProcessor(mock_gateway)
        result = processor.process_payment(100)

        self.assertEqual(result, "Payment successful")
        mock_gateway.charge.assert_called_once_with(100)

    def test_process_payment_failure(self):
        mock_gateway = mock.Mock()
        mock_gateway.charge.return_value = False

        processor = PaymentProcessor(mock_gateway)
        result = processor.process_payment(100)

        self.assertEqual(result, "Payment failed")
        mock_gateway.charge.assert_called_once_with(100)

C#

using NUnit.Framework;
using Moq;

public interface IPaymentGateway {
    bool Charge(int amount);
}

public class PaymentProcessor {
    private readonly IPaymentGateway _paymentGateway;

    public PaymentProcessor(IPaymentGateway paymentGateway) {
        _paymentGateway = paymentGateway;
    }

    public string ProcessPayment(int amount) {
        return _paymentGateway.Charge(amount) ? "Payment successful" : "Payment failed";
    }
}

[TestFixture]
public class PaymentProcessorTests {
    [Test]
    public void TestProcessPaymentSuccess() {
        var mockGateway = new Mock<IPaymentGateway>();
        mockGateway.Setup(g => g.Charge(It.IsAny<int>())).Returns(true);

        var processor = new PaymentProcessor(mockGateway.Object);
        var result = processor.ProcessPayment(100);

        Assert.AreEqual("Payment successful", result);
        mockGateway.Verify(g => g.Charge(100), Times.Once);
    }

    [Test]
    public void TestProcessPaymentFailure() {
        var mockGateway = new Mock<IPaymentGateway>();
        mockGateway.Setup(g => g.Charge(It.IsAny<int>())).Returns(false);

        var processor = new PaymentProcessor(mockGateway.Object);
        var result = processor.ProcessPayment(100);

        Assert.AreEqual("Payment failed", result);
        mockGateway.Verify(g => g.Charge(100), Times.Once);
    }
}

C++

#include <gtest/gtest.h>
#include <gmock/gmock.h>

class PaymentGateway {
public:
    virtual bool charge(int amount) = 0;
    virtual ~PaymentGateway() = default;
};

class PaymentProcessor {
    PaymentGateway* paymentGateway;
public:
    PaymentProcessor(PaymentGateway* gateway) : paymentGateway(gateway) {}

    std::string process_payment(int amount) {
        if (paymentGateway->charge(amount)) {
            return "Payment successful";
        } else {
            return "Payment failed";
        }
    }
};

class MockPaymentGateway : public PaymentGateway {
public:
    MOCK_METHOD(bool, charge, (int amount), (override));
};

TEST(PaymentProcessorTest, ProcessPaymentSuccess) {
    MockPaymentGateway mockGateway;
    EXPECT_CALL(mockGateway, charge(100)).WillOnce(::testing::Return(true));

    PaymentProcessor processor(&mockGateway);
    EXPECT_EQ(processor.process_payment(100), "Payment successful");
}

TEST(PaymentProcessorTest, ProcessPaymentFailure) {
    MockPaymentGateway mockGateway;
    EXPECT_CALL(mockGateway, charge(100)).WillOnce(::testing::Return(false));

    PaymentProcessor processor(&mockGateway);
    EXPECT_EQ(processor.process_payment(100), "Payment failed");
}

Java

import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;

public class PaymentProcessor {
    private PaymentGateway paymentGateway;

    public PaymentProcessor(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }

    public String processPayment(int amount) {
        if (paymentGateway.charge(amount)) {
            return "Payment successful";
        } else {
            return "Payment failed";
        }
    }
}

interface PaymentGateway {
    boolean charge(int amount);
}

public class PaymentProcessorTest {
    @Test
    public void testProcessPaymentSuccess() {
        PaymentGateway mockGateway = mock(PaymentGateway.class);
        when(mockGateway.charge(100)).thenReturn(true);

        PaymentProcessor processor = new PaymentProcessor(mockGateway);
        String result = processor.processPayment(100);

        assertEquals("Payment successful", result);
        verify(mockGateway).charge(100);
    }

    @Test
    public void testProcessPaymentFailure() {
        PaymentGateway mockGateway = mock(PaymentGateway.class);
        when(mockGateway.charge(100)).thenReturn(false);

        PaymentProcessor processor = new PaymentProcessor(mockGateway);
        String result = processor.processPayment(100);

        assertEquals("Payment failed", result);
        verify(mockGateway).charge(100);
    }
}

JavaScript

const assert = require('assert');
const sinon = require('sinon');

class PaymentProcessor {
    constructor(paymentGateway) {
        this.paymentGateway = paymentGateway;
    }

    processPayment(amount) {
        return this.paymentGateway.charge(amount) ? "Payment successful" : "Payment failed";
    }
}

// 使用例
describe('PaymentProcessor', function() {
    it('should return "Payment successful" when payment succeeds', function() {
        const mockGateway = sinon.createStubInstance(Object);
        mockGateway.charge.returns(true);

        const processor = new PaymentProcessor(mockGateway);
        const result = processor.processPayment(100);

        assert.strictEqual(result, "Payment successful");
        assert(mockGateway.charge.calledOnceWith(100));
    });

    it('should return "Payment failed" when payment fails', function() {
        const mockGateway = sinon.createStubInstance(Object);
        mockGateway.charge.returns(false);

        const processor = new PaymentProcessor(mockGateway);
        const result = processor.processPayment(100);

        assert.strictEqual(result, "Payment failed");
        assert(mockGateway.charge.calledOnceWith(100));
    });
});

各言語の解説

言語モックの実装方法スタブの実装方法テストフレームワーク
Pythonunittest.mockモジュールを使用unittest.mockモジュールを使用unittest
C#Moqライブラリを使用Moqライブラリを使用NUnit
C++gmockライブラリを使用gmockライブラリを使用Google Test
JavaMockitoライブラリを使用Mockitoライブラリを使用JUnit
JavaScriptsinonライブラリを使用sinonライブラリを使用Mocha

まとめ

モックとスタブは、ソフトウェアテストにおいて依存関係を制御し、対象となるコードのテストを容易にするための重要なツールです。モックはメソッドの呼び出しやパラメータを検証するのに適しており、スタブは事前に定義された出力を返すため、テストの範囲を明確に制限できます。次回は「テスト駆動開発(TDD)の基本概念と実践方法」について学習しましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次