Lietuvių

Sužinokite, kaip efektyviai naudoti imituotas funkcijas testuojant, kad programinė įranga būtų patikima. Šis vadovas su pavyzdžiais aiškina, kada ir kaip jas diegti.

Imituotos funkcijos (Mock Functions): Išsamus vadovas programuotojams

Programinės įrangos kūrimo pasaulyje patikimo ir stabilaus kodo rašymas yra svarbiausia. Išsamus testavimas yra būtinas norint pasiekti šį tikslą. Ypač modulinis testavimas (unit testing) yra skirtas testuoti atskirus komponentus ar funkcijas izoliuotai. Tačiau realaus pasaulio programose dažnai pasitaiko sudėtingų priklausomybių, todėl testuoti modulius visiškai izoliuotai tampa iššūkiu. Būtent čia į pagalbą ateina imituotos funkcijos.

Kas yra imituotos funkcijos?

Imituota funkcija (mock function) yra simuliuota tikros funkcijos versija, kurią galite naudoti savo testuose. Vietoj to, kad vykdytų tikrosios funkcijos logiką, imituota funkcija leidžia jums valdyti jos elgseną, stebėti, kaip ji yra iškviečiama, ir apibrėžti jos grąžinamas reikšmes. Jos yra testavimo pakaitalo (test double) tipas.

Pagalvokite apie tai taip: įsivaizduokite, kad testuojate automobilio variklį (testuojamą modulį). Variklis priklauso nuo įvairių kitų komponentų, pavyzdžiui, kuro įpurškimo ir aušinimo sistemų. Vietoj to, kad variklio bandymo metu naudotumėte tikras kuro įpurškimo ir aušinimo sistemas, galite naudoti imituotas sistemas, kurios simuliuoja jų elgseną. Tai leidžia jums izoliuoti variklį ir sutelkti dėmesį būtent į jo veikimą.

Imituotos funkcijos yra galingi įrankiai, skirti:

Kada naudoti imituotas funkcijas

Mocks yra naudingiausios šiose situacijose:

1. Modulių su išorinėmis priklausomybėmis izoliavimas

Kai jūsų testuojamas modulis priklauso nuo išorinių paslaugų, duomenų bazių, API ar kitų komponentų, realių priklausomybių naudojimas testavimo metu gali sukelti keletą problemų:

Pavyzdys: Įsivaizduokite, kad testuojate funkciją, kuri gauna vartotojo duomenis iš nuotolinės API. Užuot vykdę realius API iškvietimus testavimo metu, galite naudoti imituotą funkciją, kuri simuliuoja API atsakymą. Tai leidžia jums testuoti funkcijos logiką, nepriklausomai nuo išorinės API prieinamumo ar našumo. Tai ypač svarbu, kai API turi užklausų limitus ar susijusias išlaidas už kiekvieną užklausą.

2. Sudėtingų sąveikų testavimas

Kai kuriais atvejais jūsų testuojamas modulis gali sudėtingai sąveikauti su kitais komponentais. Imituotos funkcijos leidžia stebėti ir patikrinti šias sąveikas.

Pavyzdys: Apsvarstykite funkciją, kuri apdoroja mokėjimo operacijas. Ši funkcija gali sąveikauti su mokėjimo šliuzu, duomenų baze ir pranešimų paslauga. Naudodami imituotas funkcijas, galite patikrinti, ar funkcija iškviečia mokėjimo šliuzą su teisinga operacijos informacija, atnaujina duomenų bazę su operacijos būsena ir siunčia pranešimą vartotojui.

3. Klaidų sąlygų simuliavimas

Klaidų apdorojimo testavimas yra labai svarbus jūsų programos patikimumui užtikrinti. Imituotos funkcijos leidžia lengvai simuliuoti klaidų sąlygas, kurias sunku ar neįmanoma atkurti realioje aplinkoje.

Pavyzdys: Tarkime, testuojate funkciją, kuri įkelia failus į debesijos saugyklą. Galite naudoti imituotą funkciją, kad simuliuotumėte tinklo klaidą įkėlimo proceso metu. Tai leidžia patikrinti, ar funkcija teisingai apdoroja klaidą, bando įkelti iš naujo ar praneša vartotojui.

4. Asinchroninio kodo testavimas

Asinchroninį kodą, pavyzdžiui, kodą, kuris naudoja atgalinio iškvietimo funkcijas (callbacks), pažadus (promises) ar async/await, gali būti sudėtinga testuoti. Imituotos funkcijos gali padėti valdyti asinchroninių operacijų laiką ir elgseną.

Pavyzdys: Įsivaizduokite, kad testuojate funkciją, kuri asinchronine užklausa gauna duomenis iš serverio. Galite naudoti imituotą funkciją, kad simuliuotumėte serverio atsakymą ir valdytumėte, kada atsakymas bus grąžintas. Tai leidžia jums testuoti, kaip funkcija apdoroja skirtingus atsakymų scenarijus ir laiko limitus.

5. Nenumatytų šalutinių poveikių prevencija

Kartais realios funkcijos iškvietimas testavimo metu gali sukelti nenumatytų šalutinių poveikių, tokių kaip duomenų bazės keitimas, el. laiškų siuntimas ar išorinių procesų paleidimas. Imituotos funkcijos apsaugo nuo šių šalutinių poveikių, leisdamos pakeisti tikrąją funkciją valdoma simuliacija.

Pavyzdys: Testuojate funkciją, kuri siunčia pasveikinimo el. laiškus naujiems vartotojams. Naudodami imituotą el. pašto paslaugą, galite užtikrinti, kad el. laiškų siuntimo funkcionalumas faktiškai nesiųs laiškų realiems vartotojams jūsų testų rinkinio vykdymo metu. Vietoj to, galite patikrinti, ar funkcija bando išsiųsti el. laišką su teisinga informacija.

Kaip naudoti imituotas funkcijas

Konkretūs imituotų funkcijų naudojimo žingsniai priklauso nuo jūsų naudojamos programavimo kalbos ir testavimo karkaso. Tačiau bendras procesas paprastai apima šiuos veiksmus:

  1. Identifikuokite priklausomybes: Nustatykite, kurias išorines priklausomybes reikia imituoti.
  2. Sukurkite imituotus objektus: Sukurkite imituotus objektus ar funkcijas, kad pakeistumėte tikrąsias priklausomybes. Šios imitacijos dažnai turės savybes, tokias kaip `called`, `returnValue` ir `callArguments`.
  3. Konfigūruokite imituotą elgseną: Apibrėžkite imituotų funkcijų elgseną, pvz., jų grąžinamas reikšmes, klaidų sąlygas ir iškvietimų skaičių.
  4. Įdiekite imitacijas: Pakeiskite tikrąsias priklausomybes imituotais objektais jūsų testuojamame modulyje. Tai dažnai daroma naudojant priklausomybių injekciją.
  5. Vykdykite testą: Paleiskite testą ir stebėkite, kaip testuojamas modulis sąveikauja su imituotomis funkcijomis.
  6. Patikrinkite sąveikas: Patikrinkite, ar imituotos funkcijos buvo iškviestos su laukiamais argumentais, grąžino laukiamas reikšmes ir buvo iškviestos reikiamą kartų skaičių.
  7. Atkurkite pradinį funkcionalumą: Po testo atkurkite pradinį funkcionalumą, pašalindami imituotus objektus ir grįždami prie realių priklausomybių. Tai padeda išvengti šalutinių poveikių kitiems testams.

Imituotų funkcijų pavyzdžiai skirtingose kalbose

Čia pateikiami imituotų funkcijų naudojimo pavyzdžiai populiariose programavimo kalbose ir testavimo karkasuose:

JavaScript su Jest

Jest yra populiarus JavaScript testavimo karkasas, kuris turi integruotą palaikymą imituotoms funkcijoms.

// Testuojama funkcija
function fetchData(callback) {
  setTimeout(() => {
    callback('Data from server');
  }, 100);
}

// Testo atvejis
test('fetchData calls callback with correct data', (done) => {
  const mockCallback = jest.fn();
  fetchData(mockCallback);

  setTimeout(() => {
    expect(mockCallback).toHaveBeenCalledWith('Data from server');
    done();
  }, 200);
});

Šiame pavyzdyje `jest.fn()` sukuria imituotą funkciją, kuri pakeičia tikrąją atgalinio iškvietimo funkciją. Testas patikrina, ar imituota funkcija yra iškviečiama su teisingais duomenimis, naudojant `toHaveBeenCalledWith()`.

Pažangesnis pavyzdys naudojant modulius:

// user.js
import { getUserDataFromAPI } from './api';

export async function displayUserName(userId) {
  const userData = await getUserDataFromAPI(userId);
  return userData.name;
}

// api.js
export async function getUserDataFromAPI(userId) {
  // Simuliuoti API iškvietimą
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({ id: userId, name: 'John Doe' });
    }, 50);
  });
}

// user.test.js
import { displayUserName } from './user';
import * as api from './api';

describe('displayUserName', () => {
  it('should display the user name', async () => {
    // Imituoti getUserDataFromAPI funkciją
    const mockGetUserData = jest.spyOn(api, 'getUserDataFromAPI');
    mockGetUserData.mockResolvedValue({ id: 123, name: 'Mocked Name' });

    const userName = await displayUserName(123);
    expect(userName).toBe('Mocked Name');

    // Atkurti pradinę funkciją
    mockGetUserData.mockRestore();
  });
});

Čia `jest.spyOn` naudojamas sukurti imituotą funkciją `getUserDataFromAPI` funkcijai, importuotai iš `./api` modulio. `mockResolvedValue` naudojamas nurodyti imituotos funkcijos grąžinamą reikšmę. `mockRestore` yra būtinas, norint užtikrinti, kad kiti testai netyčia nepanaudotų imituotos versijos.

Python su pytest ir unittest.mock

Python siūlo keletą bibliotekų imitavimui, įskaitant `unittest.mock` (integruota) ir bibliotekas, tokias kaip `pytest-mock`, supaprastintam naudojimui su pytest.

# Testuojama funkcija
def get_data_from_api(url):
    # Realiame scenarijuje tai atliktų API iškvietimą
    # Dėl paprastumo simuliuojame API iškvietimą
    if url == "https://example.com/api":
        return {"data": "API data"}
    else:
        return None

def process_data(url):
    data = get_data_from_api(url)
    if data:
        return data["data"]
    else:
        return "No data found"

# Testo atvejis naudojant unittest.mock
import unittest
from unittest.mock import patch

class TestProcessData(unittest.TestCase):
    @patch('__main__.get_data_from_api') # Pakeisti get_data_from_api pagrindiniame modulyje
    def test_process_data_success(self, mock_get_data_from_api):
        # Konfigūruoti imitaciją
        mock_get_data_from_api.return_value = {"data": "Mocked data"}

        # Iškviesti testuojamą funkciją
        result = process_data("https://example.com/api")

        # Patikrinti rezultatą
        self.assertEqual(result, "Mocked data")
        mock_get_data_from_api.assert_called_once_with("https://example.com/api")

    @patch('__main__.get_data_from_api')
    def test_process_data_failure(self, mock_get_data_from_api):
        mock_get_data_from_api.return_value = None
        result = process_data("https://example.com/api")
        self.assertEqual(result, "No data found")

if __name__ == '__main__':
    unittest.main()

Šis pavyzdys naudoja `unittest.mock.patch`, kad pakeistų `get_data_from_api` funkciją imitacija. Testas konfigūruoja imitaciją, kad ji grąžintų konkrečią reikšmę, ir tada patikrina, ar `process_data` funkcija grąžina laukiamą rezultatą.

Štai tas pats pavyzdys naudojant `pytest-mock`:

# pytest versija
import pytest

def get_data_from_api(url):
    # Realiame scenarijuje tai atliktų API iškvietimą
    # Dėl paprastumo simuliuojame API iškvietimą
    if url == "https://example.com/api":
        return {"data": "API data"}
    else:
        return None

def process_data(url):
    data = get_data_from_api(url)
    if data:
        return data["data"]
    else:
        return "No data found"


def test_process_data_success(mocker):
    mocker.patch('__main__.get_data_from_api', return_value={"data": "Mocked data"})
    result = process_data("https://example.com/api")
    assert result == "Mocked data"


def test_process_data_failure(mocker):
    mocker.patch('__main__.get_data_from_api', return_value=None)
    result = process_data("https://example.com/api")
    assert result == "No data found"

`pytest-mock` biblioteka suteikia `mocker` priemonę (fixture), kuri supaprastina imitacijų kūrimą ir konfigūravimą pytest testuose.

Java su Mockito

Mockito yra populiarus imitavimo karkasas Java kalbai.

import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

interface DataFetcher {
    String fetchData(String url);
}

class DataProcessor {
    private final DataFetcher dataFetcher;

    public DataProcessor(DataFetcher dataFetcher) {
        this.dataFetcher = dataFetcher;
    }

    public String processData(String url) {
        String data = dataFetcher.fetchData(url);
        if (data != null) {
            return "Processed: " + data;
        } else {
            return "No data";
        }
    }
}

public class DataProcessorTest {

    @Test
    public void testProcessDataSuccess() {
        // Sukurti imituotą DataFetcher
        DataFetcher mockDataFetcher = mock(DataFetcher.class);

        // Konfigūruoti imitaciją
        when(mockDataFetcher.fetchData("https://example.com/api")).thenReturn("API Data");

        // Sukurti DataProcessor su imitacija
        DataProcessor dataProcessor = new DataProcessor(mockDataFetcher);

        // Iškviesti testuojamą funkciją
        String result = dataProcessor.processData("https://example.com/api");

        // Patikrinti rezultatą
        assertEquals("Processed: API Data", result);

        // Patikrinti, ar imitacija buvo iškviesta
        verify(mockDataFetcher).fetchData("https://example.com/api");
    }

    @Test
    public void testProcessDataFailure() {
        DataFetcher mockDataFetcher = mock(DataFetcher.class);
        when(mockDataFetcher.fetchData("https://example.com/api")).thenReturn(null);

        DataProcessor dataProcessor = new DataProcessor(mockDataFetcher);
        String result = dataProcessor.processData("https://example.com/api");
        assertEquals("No data", result);
        verify(mockDataFetcher).fetchData("https://example.com/api");
    }
}

Šiame pavyzdyje `Mockito.mock()` sukuria imituotą objektą `DataFetcher` sąsajai. `when()` naudojamas konfigūruoti imituoto objekto grąžinamą reikšmę, o `verify()` naudojamas patikrinti, ar imituotas objektas buvo iškviestas su laukiamais argumentais.

Geriausios imituotų funkcijų naudojimo praktikos

Imituotų funkcijų alternatyvos

Nors imituotos funkcijos yra galingas įrankis, jos ne visada yra geriausias sprendimas. Kai kuriais atvejais kitos technikos gali būti tinkamesnės:

Išvada

Imituotos funkcijos yra esminis įrankis rašant efektyvius modulinius testus, leidžiančius izoliuoti modulius, valdyti elgseną, simuliuoti klaidų sąlygas ir testuoti asinchroninį kodą. Laikydamiesi geriausių praktikų ir suprasdami alternatyvas, galite pasinaudoti imituotomis funkcijomis kurdami patikimesnę, stabilesnę ir lengviau prižiūrimą programinę įrangą. Nepamirškite įvertinti kompromisų ir pasirinkti tinkamą testavimo techniką kiekvienai situacijai, kad sukurtumėte išsamią ir efektyvią testavimo strategiją, nesvarbu, kurioje pasaulio vietoje kuriate.

Imituotos funkcijos (Mock Functions): Išsamus vadovas programuotojams | MLOG