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

Dec 19, 2024 pm 12:27 PM

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 Laman Web ini
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

Alat AI Hot

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

Video Face Swap

Video Face Swap

Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas

Tutorial PHP
1596
276
Contoh Ekspresi C Lipat Contoh Ekspresi C Lipat Jul 28, 2025 am 02:37 AM

C FolderExpressions adalah ciri yang diperkenalkan oleh C 17 untuk memudahkan operasi rekursif dalam templat parameter variadik. 1. 2. Logik dan (args && ...) Tentukan sama ada semua parameter adalah benar, dan paket kosong kembali benar; 3. Gunakan (std :: cout

C char array ke contoh rentetan C char array ke contoh rentetan Aug 02, 2025 am 05:52 AM

Jawapannya ialah: Gunakan std :: String constructor untuk menukar array char ke std :: string. Jika array mengandungi pertengahan '\ 0', panjang mesti ditentukan. 1. Untuk rentetan gaya C yang berakhir dengan '\ 0', gunakan std :: stringstr (charArray); untuk melengkapkan penukaran; 2. Jika array char mengandungi pertengahan '\ 0' tetapi perlu menukar aksara n pertama, gunakan std :: stringstr (chararray, panjang); untuk menentukan panjangnya; 3. Apabila memproses pelbagai saiz tetap, pastikan ia berakhir dengan '\ 0' dan kemudian tukarnya; 4. Gunakan Str.Assign (CharArray, CharArray Strl

C Padamkan dari vektor semasa melangkah C Padamkan dari vektor semasa melangkah Aug 05, 2025 am 09:16 AM

Jika ia meleleh apabila memadam elemen, anda mesti mengelakkan menggunakan Iterator yang gagal. ① Cara yang betul adalah menggunakannya = vec.erase (IT), dan gunakan iterator yang sah yang dikembalikan dengan memadam untuk terus melintasi; ② Idiom yang disyorkan untuk penghapusan batch: vec.erase (std :: rove_if (vec.begin (), vec.end (), syarat), vec.end ()), yang selamat dan cekap; ③ Anda boleh menggunakan iterator terbalik untuk memadam dari belakang ke depan, logiknya jelas, tetapi anda perlu memberi perhatian kepada arah keadaan. Kesimpulan: Sentiasa mengemas kini iterator dengan nilai pulangan memadam, melarang operasi pada Iterator yang gagal, jika tidak, tingkah laku yang tidak ditentukan akan dihasilkan.

Contoh kata kunci auto c Contoh kata kunci auto c Aug 05, 2025 am 08:58 AM

Theautokeywordinc deducesthetypeofavariableFromitsinitializer, MakingCodeCleanerAndmoremaintainable.1.itreduceRosities, terutamanyaWithcomplextypesikeiterators.2.itenhancesmaintabilitybyautomaticallyAdAdAdAdAttottoTypeChanges.3.Isisnessaryaryaryypechanges

Bagaimana untuk menggunakan std :: source_location dari c 20 untuk pembalakan yang lebih baik? Bagaimana untuk menggunakan std :: source_location dari c 20 untuk pembalakan yang lebih baik? Aug 11, 2025 pm 08:55 PM

Gunakan std :: source_location :: current () sebagai parameter lalai untuk secara automatik menangkap nama fail, nombor baris dan nama fungsi titik panggilan; 2. Anda boleh memudahkan panggilan log melalui makro seperti log #DefineLog (MSG) (MSG, STD :: source_location :: current ()); 3. Anda boleh mengembangkan kandungan log dengan tahap log, timestamp dan maklumat lain; 4. Untuk mengoptimumkan prestasi, nama fungsi boleh ditinggalkan atau maklumat lokasi boleh dilumpuhkan dalam versi pelepasan; 5. lajur () dan butiran lain jarang digunakan, tetapi boleh didapati. Menggunakan std :: source_location dapat meningkatkan nilai debugging log dengan overhead yang sangat rendah tanpa lulus secara manual dalam fil

Contoh pokok carian carian binari Contoh pokok carian carian binari Jul 28, 2025 am 02:26 AM

AbinarySearchtree (BST) IsabinaryTreewheretheleftsubtreecontainsonsonlynodeswithvalueslessthanthenode'svalue, TherightSubtreecontainsonlynodeswithValueRheatthanthenode'sValue, danBothsubtreesMustalsoBebsts;

C MUTEX Contoh C MUTEX Contoh Aug 03, 2025 am 08:43 AM

STD :: MUTEX digunakan untuk melindungi sumber bersama untuk mencegah persaingan data. Contohnya, penguncian automatik dan pembukaan std :: lock_guard digunakan untuk memastikan keselamatan multi-thread; 1. Menggunakan std :: mutex dan std :: lock_guard boleh mengelakkan risiko abnormal yang dibawa oleh pengurusan manual kunci; 2. Pembolehubah yang dikongsi seperti kaunter mesti dilindungi dengan mutex apabila mengubah suai multi-threads; 3. Pengurusan kunci gaya RAII disyorkan untuk memastikan keselamatan pengecualian; 4. Elakkan kebuntuan dan banyak kunci dalam urutan tetap; 5. Sebarang senario akses berbilang thread kepada sumber yang dikongsi harus menggunakan penyegerakan mutex, dan program akhir dengan betul output yang dijangkakan: 10000 dan sebenarnya: 10000.

C Cari dalam contoh vektor C Cari dalam contoh vektor Aug 02, 2025 am 08:40 AM

Kaedah yang paling biasa untuk mencari elemen vektor di C adalah menggunakan std :: cari. 1. Gunakan std :: cari untuk mencari dengan julat iterator dan nilai sasaran. Dengan membandingkan sama ada Iterator yang dikembalikan adalah sama dengan akhir (), kita boleh menilai sama ada ia dijumpai; 2. Untuk jenis tersuai atau keadaan kompleks, std :: find_if harus digunakan dan fungsi predikat atau ekspresi lambda harus diluluskan; 3. Apabila mencari jenis standard seperti rentetan, anda boleh lulus rentetan sasaran secara langsung; 4. Kerumitan setiap carian adalah O (n), yang sesuai untuk data berskala kecil. Untuk carian yang kerap, anda harus mempertimbangkan menggunakan std :: set atau std :: unordered_set. Kaedah ini mudah, berkesan dan meluas untuk pelbagai senario carian.

See all articles