Rumah >pembangunan bahagian belakang >C++ >MockManager dalam ujian unit - corak pembina yang digunakan untuk olok-olok

MockManager dalam ujian unit - corak pembina yang digunakan untuk olok-olok

Mary-Kate Olsen
Mary-Kate Olsenasal
2024-12-19 12:27:10222semak imbas

MockManager in unit tests - a builder pattern used for mocks

Beberapa tahun lalu saya menulis tentang perkara ini, tetapi kurang terperinci. Berikut ialah versi idea yang sama yang lebih halus.

Pengenalan

Ujian unit adalah kebaikan dan keburukan kepada pembangun. Mereka membenarkan ujian pantas kefungsian, contoh penggunaan yang boleh dibaca, percubaan pantas senario untuk komponen yang terlibat sahaja. Tetapi ia juga boleh menjadi kucar-kacir, memerlukan penyelenggaraan dan kemas kini dengan setiap perubahan kod dan, apabila dilakukan dengan malas, tidak boleh menyembunyikan pepijat daripada mendedahkannya.

Saya rasa sebab ujian unit begitu sukar adalah kerana ia dikaitkan dengan ujian, sesuatu selain daripada penulisan kod, dan juga ujian unit ditulis dengan cara yang bertentangan dengan kebanyakan kod lain yang kami tulis.

Dalam siaran ini, saya akan memberi anda corak mudah menulis ujian unit yang akan meningkatkan semua faedah, sambil menghapuskan kebanyakan disonans kognitif dengan kod biasa. Ujian unit akan kekal boleh dibaca dan fleksibel, sambil mengurangkan kod pendua dan tidak menambah kebergantungan tambahan.

Bagaimana untuk ujian unit

Tetapi pertama sekali, mari kita tentukan suite ujian unit yang baik.

Untuk menguji kelas dengan betul, ia perlu ditulis dengan cara tertentu. Dalam siaran ini, kami akan merangkumi kelas menggunakan suntikan pembina untuk kebergantungan, yang merupakan cara saya yang disyorkan untuk melakukan suntikan kebergantungan.

Kemudian, untuk mengujinya, kita perlu:

  • meliputi senario positif - apabila kelas melakukan perkara yang sepatutnya dilakukan, dengan pelbagai kombinasi tetapan dan parameter input untuk merangkumi keseluruhan fungsi
  • tutup senario negatif - apabila kelas gagal dengan cara yang betul apabila persediaan atau parameter input salah
  • ejek semua kebergantungan luar
  • simpan semua persediaan ujian, tindakan dan penegasan dalam ujian yang sama (yang biasanya dipanggil struktur Susun-Tindakan-Tegaskan)

Tetapi itu lebih mudah diucapkan daripada dilakukan, kerana ia juga membayangkan:

  • menyediakan kebergantungan yang sama untuk setiap ujian, dengan itu menyalin dan menampal banyak kod
  • menyediakan senario yang hampir sama, dengan hanya satu perubahan antara dua ujian, sekali lagi mengulangi banyak kod
  • mengeneralisasikan dan merangkum apa-apa, itulah yang biasanya dilakukan oleh pembangun dalam semua kod mereka
  • menulis banyak kes negatif untuk beberapa kes positif, yang rasanya seperti mempunyai lebih banyak kod ujian daripada kod berfungsi
  • perlu mengemas kini semua ujian ini untuk setiap perubahan pada kelas yang diuji

Siapa suka itu?

Penyelesaian

Penyelesaian adalah dengan menggunakan corak perisian pembina untuk mencipta ujian yang cair, fleksibel dan boleh dibaca dalam struktur Susun-Bertindak-Tegas, sambil merangkum kod persediaan dalam kelas yang melengkapkan suite ujian unit untuk perkhidmatan tertentu. Saya memanggil ini corak MockManager.

Mari kita mulakan dengan contoh mudah:

// the tested class
public class Calculator
{
    private readonly ITokenParser tokenParser;
    private readonly IMathOperationFactory operationFactory;
    private readonly ICache cache;
    private readonly ILogger logger;

    public Calculator(
        ITokenParser tokenParser,
        IMathOperationFactory operationFactory,
        ICache cache,
        ILogger logger)
    {
        this.tokenParser = tokenParser;
        this.operationFactory = operationFactory;
        this.cache = cache;
        this.logger = logger;
    }

    public int Calculate(string input)
    {
        var result = cache.Get(input);
        if (result.HasValue)
        {
            logger.LogInformation("from cache");
            return result.Value;
        }
        var tokens = tokenParser.Parse(input);
        IOperation operation = null;
        foreach(var token in tokens)
        {
            if (operation is null)
            {
                operation = operationFactory.GetOperation(token.OperationType);
                continue;
            }
            if (result is null)
            {
                result = token.Value;
                continue;
            }
            else
            {
                if (result is null)
                {
                    throw new InvalidOperationException("Could not calculate result");
                }
                result = operation.Execute(result.Value, token.Value);
                operation = null;
            }
        }
        cache.Set(input, result.Value);
        logger.LogInformation("from operation");
        return result.Value;
    }
}

Ini adalah kalkulator, seperti tradisi. Ia menerima rentetan dan mengembalikan nilai integer. Ia juga menyimpan hasil carian untuk input tertentu, dan log beberapa perkara. Operasi sebenar sedang diabstrak oleh IMathOperationFactory dan rentetan input diterjemahkan ke dalam token oleh ITokenParser. Jangan risau, ini bukan kelas sebenar, hanya contoh. Mari lihat ujian "tradisional":

[TestMethod]
public void Calculate_AdditionWorks()
{
    // Arrange
    var tokenParserMock = new Mock<ITokenParser>();
    tokenParserMock
        .Setup(m => m.Parse(It.IsAny<string>()))
        .Returns(
            new List<CalculatorToken> {
                CalculatorToken.Addition, CalculatorToken.From(1), CalculatorToken.From(1)
            }
        );

    var mathOperationFactoryMock = new Mock<IMathOperationFactory>();

    var operationMock = new Mock<IOperation>();
    operationMock
        .Setup(m => m.Execute(1, 1))
        .Returns(2);

    mathOperationFactoryMock
        .Setup(m => m.GetOperation(OperationType.Add))
        .Returns(operationMock.Object);

    var cacheMock = new Mock<ICache>();
    var loggerMock = new Mock<ILogger>();

    var service = new Calculator(
        tokenParserMock.Object,
        mathOperationFactoryMock.Object,
        cacheMock.Object,
        loggerMock.Object);

    // Act
    service.Calculate("");

    //Assert
    mathOperationFactoryMock
        .Verify(m => m.GetOperation(OperationType.Add), Times.Once);
    operationMock
        .Verify(m => m.Execute(1, 1), Times.Once);
}

Jom bongkar sikit. Kami terpaksa mengisytiharkan olok-olok untuk setiap pergantungan pembina, walaupun kami sebenarnya tidak mengambil berat tentang pembalak atau cache, sebagai contoh. Kami juga terpaksa menyediakan kaedah olok-olok yang mengembalikan olok-olok lain, dalam kes kilang operasi.

Dalam ujian khusus ini, kebanyakannya kami menulis persediaan, satu baris Act dan dua baris Assert. Lebih-lebih lagi, jika kami ingin menguji cara cache berfungsi di dalam kelas, kami perlu menyalin tampal keseluruhannya dan hanya mengubah cara kami menyediakan mock cache.

Dan terdapat ujian negatif untuk dipertimbangkan. Saya telah melihat banyak ujian negatif melakukan sesuatu seperti: "sediakan hanya apa yang sepatutnya gagal. uji bahawa ia gagal", yang memperkenalkan banyak masalah, terutamanya kerana ia mungkin gagal untuk sebab yang berbeza dan kebanyakan masa ujian ini mengikuti pelaksanaan dalaman kelas dan bukannya keperluannya. Ujian negatif yang betul sebenarnya adalah ujian positif sepenuhnya dengan hanya satu keadaan yang salah. Tidak begitu di sini, untuk kesederhanaan.

Jadi, tanpa berlengah lagi, inilah ujian yang sama, tetapi dengan MockManager:

[TestMethod]
public void Calculate_AdditionWorks_MockManager()
{
    // Arrange
    var mockManager = new CalculatorMockManager()
        .WithParsedTokens(new List<CalculatorToken> {
            CalculatorToken.Addition, CalculatorToken.From(1), CalculatorToken.From(1)
        })
        .WithOperation(OperationType.Add, 1, 1, 2);

    var service = mockManager.GetService();

    // Act
    service.Calculate("");

    //Assert
    mockManager
        .VerifyOperationExecute(OperationType.Add, 1, 1, Times.Once);
}

Membongkar, tidak ada menyebut tentang cache atau logger, kerana kami tidak memerlukan sebarang persediaan di sana. Semuanya dibungkus dan boleh dibaca. Salin tampal ini dan menukar beberapa parameter atau beberapa baris tidak lagi hodoh. Terdapat tiga kaedah yang dilaksanakan dalam Arrange, satu dalam Act dan satu dalam Assert. Hanya butiran ejekan yang ringkas sahaja yang disarikan: rangka kerja Moq tidak disebutkan di sini. Malah, ujian ini akan kelihatan sama tanpa mengira rangka kerja mengejek yang diputuskan untuk digunakan.

Mari kita lihat kelas MockManager. Sekarang ini akan kelihatan rumit, tetapi ingat bahawa kami hanya menulis ini sekali dan menggunakannya berkali-kali. Keseluruhan kerumitan kelas ada untuk menjadikan ujian unit boleh dibaca oleh manusia, mudah difahami, dikemas kini dan diselenggara.

public class CalculatorMockManager
{
    private readonly Dictionary<OperationType,Mock<IOperation>> operationMocks = new();

    public Mock<ITokenParser> TokenParserMock { get; } = new();
    public Mock<IMathOperationFactory> MathOperationFactoryMock { get; } = new();
    public Mock<ICache> CacheMock { get; } = new();
    public Mock<ILogger> LoggerMock { get; } = new();

    public CalculatorMockManager WithParsedTokens(List<CalculatorToken> tokens)
    {
        TokenParserMock
            .Setup(m => m.Parse(It.IsAny<string>()))
            .Returns(
                new List<CalculatorToken> {
                    CalculatorToken.Addition, CalculatorToken.From(1), CalculatorToken.From(1)
                }
            );
        return this;
    }

    public CalculatorMockManager WithOperation(OperationType operationType, int v1, int v2, int result)
    {
        var operationMock = new Mock<IOperation>();
        operationMock
            .Setup(m => m.Execute(v1, v2))
            .Returns(result);

        MathOperationFactoryMock
            .Setup(m => m.GetOperation(operationType))
            .Returns(operationMock.Object);

        operationMocks[operationType] = operationMock;

        return this;
    }

    public Calculator GetService()
    {
        return new Calculator(
                TokenParserMock.Object,
                MathOperationFactoryMock.Object,
                CacheMock.Object,
                LoggerMock.Object
            );
    }

    public CalculatorMockManager VerifyOperationExecute(OperationType operationType, int v1, int v2, Func<Times> times)
    {
        MathOperationFactoryMock
            .Verify(m => m.GetOperation(operationType), Times.AtLeastOnce);
        var operationMock = operationMocks[operationType];
        operationMock
            .Verify(m => m.Execute(v1, v2), times);
        return this;
    }
}

Semua olok-olok yang diperlukan untuk kelas ujian diisytiharkan sebagai harta awam, membenarkan sebarang penyesuaian ujian unit. Terdapat kaedah GetService, yang akan sentiasa mengembalikan contoh kelas yang diuji, dengan semua kebergantungan diejek sepenuhnya. Kemudian terdapat kaedah With* yang secara atom menyediakan pelbagai senario dan sentiasa mengembalikan pengurus olok-olok, supaya mereka boleh dirantai. Anda juga boleh mempunyai kaedah khusus untuk penegasan, walaupun dalam kebanyakan kes anda akan membandingkan beberapa output dengan nilai yang dijangkakan, jadi ini di sini hanya untuk mengabsahkan kaedah Sahkan rangka kerja Moq.

Kesimpulan

Corak ini kini menjajarkan penulisan ujian dengan penulisan kod:

  • abstrak perkara yang anda tidak kisah dalam sebarang konteks
  • tulis sekali dan guna berkali-kali
  • Kod pendokumentasian diri yang boleh dibaca oleh manusia
  • kaedah kecil dengan kerumitan cyclomatic yang rendah
  • penulisan kod intuitif

Menulis ujian unit sekarang adalah remeh dan konsisten:

  1. segera pengurus olok-olok kelas yang anda ingin uji (atau tulis satu berdasarkan langkah di atas)
  2. karang senario khusus untuk ujian (dengan auto lengkap untuk langkah senario yang sedia ada)
  3. laksanakan kaedah yang anda ingin uji dengan parameter ujian
  4. semak semuanya adalah seperti yang diharapkan

Pengabstrakan tidak berhenti pada rangka kerja mengejek. Corak yang sama boleh digunakan dalam setiap bahasa pengaturcaraan! Binaan pengurus olok-olok akan sangat berbeza untuk TypeScript atau JavaScript atau sesuatu yang lain, tetapi ujian unit akan kelihatan dengan cara yang sama.

Semoga ini membantu!

Atas ialah kandungan terperinci MockManager dalam ujian unit - corak pembina yang digunakan untuk olok-olok. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn