Latviešu

Uzziniet, kā efektīvi izmantot aizstājējfunkcijas savā testēšanas stratēģijā, lai izstrādātu robustu un uzticamu programmatūru. Šī rokasgrāmata ar praktiskiem piemēriem aptver, kad, kāpēc un kā ieviest aizstājējus.

Aizstājējfunkcijas: Visaptveroša rokasgrāmata izstrādātājiem

Programmatūras izstrādes pasaulē robusta un uzticama koda rakstīšana ir vissvarīgākā. Rūpīga testēšana ir būtiska, lai sasniegtu šo mērķi. Jo īpaši vienībtestēšana koncentrējas uz atsevišķu komponenšu vai funkciju izolētu testēšanu. Tomēr reālās pasaules lietojumprogrammas bieži ietver sarežģītas atkarības, kas apgrūtina vienību testēšanu pilnīgā izolācijā. Šeit talkā nāk aizstājējfunkcijas.

Kas ir aizstājējfunkcijas?

Aizstājējfunkcija (mock function) ir reālas funkcijas simulēta versija, ko varat izmantot savos testos. Tā vietā, lai izpildītu faktiskās funkcijas loģiku, aizstājējfunkcija ļauj jums kontrolēt tās uzvedību, novērot, kā tā tiek izsaukta, un definēt tās atgrieztās vērtības. Tās ir viens no testa dublieru (test double) veidiem.

Iedomājieties to šādi: jūs testējat automašīnas dzinēju (testējamo vienību). Dzinējs ir atkarīgs no dažādiem citiem komponentiem, piemēram, degvielas iesmidzināšanas sistēmas un dzesēšanas sistēmas. Tā vietā, lai dzinēja testēšanas laikā darbinātu reālās degvielas iesmidzināšanas un dzesēšanas sistēmas, varat izmantot aizstājējsistēmas, kas simulē to uzvedību. Tas ļauj izolēt dzinēju un koncentrēties tieši uz tā veiktspēju.

Aizstājējfunkcijas ir jaudīgi rīki, lai:

Kad izmantot aizstājējfunkcijas

Mocks ir visnoderīgākās šajās situācijās:

1. Vienību ar ārējām atkarībām izolēšana

Kad jūsu testējamā vienība ir atkarīga no ārējiem pakalpojumiem, datu bāzēm, API vai citiem komponentiem, reālu atkarību izmantošana testēšanas laikā var radīt vairākas problēmas:

Piemērs: Iedomājieties, ka testējat funkciju, kas iegūst lietotāja datus no attāla API. Tā vietā, lai testēšanas laikā veiktu reālus API izsaukumus, varat izmantot aizstājējfunkciju, lai simulētu API atbildi. Tas ļauj pārbaudīt funkcijas loģiku, nepaļaujoties uz ārējā API pieejamību vai veiktspēju. Tas ir īpaši svarīgi, ja API ir pieprasījumu ierobežojumi vai ar katru pieprasījumu saistītas izmaksas.

2. Sarežģītu mijiedarbību testēšana

Dažos gadījumos jūsu testējamā vienība var sarežģīti mijiedarboties ar citiem komponentiem. Aizstājējfunkcijas ļauj jums novērot un pārbaudīt šīs mijiedarbības.

Piemērs: Apsveriet funkciju, kas apstrādā maksājumu transakcijas. Šī funkcija var mijiedarboties ar maksājumu vārteju, datu bāzi un paziņojumu pakalpojumu. Izmantojot aizstājējfunkcijas, jūs varat pārbaudīt, vai funkcija izsauc maksājumu vārteju ar pareiziem transakcijas datiem, atjaunina datu bāzi ar transakcijas statusu un nosūta paziņojumu lietotājam.

3. Kļūdu situāciju simulēšana

Kļūdu apstrādes testēšana ir būtiska, lai nodrošinātu jūsu lietojumprogrammas robustumu. Aizstājējfunkcijas ļauj viegli simulēt kļūdu situācijas, kuras ir grūti vai neiespējami reproducēt reālā vidē.

Piemērs: Pieņemsim, ka testējat funkciju, kas augšupielādē failus mākoņa krātuves pakalpojumā. Jūs varat izmantot aizstājējfunkciju, lai simulētu tīkla kļūdu augšupielādes procesā. Tas ļauj pārbaudīt, vai funkcija pareizi apstrādā kļūdu, mēģina atkārtot augšupielādi vai paziņo lietotājam.

4. Asinhronā koda testēšana

Asinhrono kodu, piemēram, kodu, kas izmanto atzvanus (callbacks), solījumus (promises) vai async/await, var būt grūti testēt. Aizstājējfunkcijas var palīdzēt kontrolēt asinhrono operāciju laiku un uzvedību.

Piemērs: Iedomājieties, ka testējat funkciju, kas iegūst datus no servera, izmantojot asinhronu pieprasījumu. Jūs varat izmantot aizstājējfunkciju, lai simulētu servera atbildi un kontrolētu, kad atbilde tiek atgriezta. Tas ļauj pārbaudīt, kā funkcija apstrādā dažādus atbildes scenārijus un taimautus.

5. Nevēlamu blakusefektu novēršana

Dažreiz, izsaucot reālu funkciju testēšanas laikā, var rasties nevēlami blakusefekti, piemēram, datu bāzes modificēšana, e-pastu sūtīšana vai ārēju procesu iedarbināšana. Aizstājējfunkcijas novērš šos blakusefektus, ļaujot jums aizstāt reālo funkciju ar kontrolētu simulāciju.

Piemērs: Jūs testējat funkciju, kas sūta sagaidīšanas e-pastus jauniem lietotājiem. Izmantojot aizstājējpakalpojumu e-pastam, jūs varat nodrošināt, ka e-pasta sūtīšanas funkcionalitāte testa komplekta izpildes laikā faktiski nesūta e-pastus reāliem lietotājiem. Tā vietā jūs varat pārbaudīt, vai funkcija mēģina nosūtīt e-pastu ar pareizo informāciju.

Kā izmantot aizstājējfunkcijas

Konkrētie soļi aizstājējfunkciju izmantošanai ir atkarīgi no jūsu izmantotās programmēšanas valodas un testēšanas ietvara. Tomēr vispārīgais process parasti ietver šādus soļus:

  1. Identificējiet atkarības: Nosakiet, kuras ārējās atkarības jums ir nepieciešams aizstāt.
  2. Izveidojiet aizstājējobjektus: Izveidojiet aizstājējobjektus vai funkcijas, lai aizstātu reālās atkarības. Šiem aizstājējiem bieži būs tādas īpašības kā `called` (izsaukts), `returnValue` (atgrieztā vērtība) un `callArguments` (izsaukuma argumenti).
  3. Konfigurējiet aizstājēja uzvedību: Definējiet aizstājējfunkciju uzvedību, piemēram, to atgrieztās vērtības, kļūdu nosacījumus un izsaukumu skaitu.
  4. Ievadiet aizstājējus: Aizstājiet reālās atkarības ar aizstājējobjektiem savā testējamajā vienībā. To bieži veic, izmantojot atkarību ievadi (dependency injection).
  5. Izpildiet testu: Palaidiet testu un novērojiet, kā testējamā vienība mijiedarbojas ar aizstājējfunkcijām.
  6. Pārbaudiet mijiedarbības: Pārbaudiet, vai aizstājējfunkcijas tika izsauktas ar gaidītajiem argumentiem, atgrieztajām vērtībām un noteiktu reižu skaitu.
  7. Atjaunojiet sākotnējo funkcionalitāti: Pēc testa atjaunojiet sākotnējo funkcionalitāti, noņemot aizstājējobjektus un atgriežoties pie reālajām atkarībām. Tas palīdz izvairīties no blakusefektiem citos testos.

Aizstājējfunkciju piemēri dažādās valodās

Šeit ir piemēri, kā izmantot aizstājējfunkcijas populārās programmēšanas valodās un testēšanas ietvaros:

JavaScript ar Jest

Jest ir populārs JavaScript testēšanas ietvars, kas nodrošina iebūvētu atbalstu aizstājējfunkcijām.

// Testējamā funkcija
function fetchData(callback) {
  setTimeout(() => {
    callback('Data from server');
  }, 100);
}

// Testa gadījums
test('fetchData calls callback with correct data', (done) => {
  const mockCallback = jest.fn();
  fetchData(mockCallback);

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

Šajā piemērā `jest.fn()` izveido aizstājējfunkciju, kas aizstāj reālo atzvana funkciju. Tests pārbauda, vai aizstājējfunkcija tiek izsaukta ar pareizajiem datiem, izmantojot `toHaveBeenCalledWith()`.

Sarežģītāks piemērs, izmantojot moduļus:

// 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) {
  // Simulēt API izsaukumu
  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 () => {
    // Aizstāt getUserDataFromAPI funkciju
    const mockGetUserData = jest.spyOn(api, 'getUserDataFromAPI');
    mockGetUserData.mockResolvedValue({ id: 123, name: 'Mocked Name' });

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

    // Atjaunot sākotnējo funkciju
    mockGetUserData.mockRestore();
  });
});

Šeit `jest.spyOn` tiek izmantots, lai izveidotu aizstājējfunkciju funkcijai `getUserDataFromAPI`, kas importēta no `./api` moduļa. `mockResolvedValue` tiek izmantots, lai norādītu aizstājēja atgriezto vērtību. `mockRestore` ir būtisks, lai nodrošinātu, ka citi testi netīšām neizmanto aizstāto versiju.

Python ar pytest un unittest.mock

Python piedāvā vairākas bibliotēkas aizstāšanai, tostarp `unittest.mock` (iebūvēta) un bibliotēkas, piemēram, `pytest-mock`, vienkāršotai lietošanai ar pytest.

# Testējamā funkcija
def get_data_from_api(url):
    # Reālā scenārijā tas veiktu API izsaukumu
    # Vienkāršības labad mēs simulējam API izsaukumu
    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"

# Testa gadījums, izmantojot unittest.mock
import unittest
from unittest.mock import patch

class TestProcessData(unittest.TestCase):
    @patch('__main__.get_data_from_api') # Aizstāt get_data_from_api galvenajā modulī
    def test_process_data_success(self, mock_get_data_from_api):
        # Konfigurēt aizstājēju
        mock_get_data_from_api.return_value = {"data": "Mocked data"}

        # Izsaukt testējamo funkciju
        result = process_data("https://example.com/api")

        # Pārbaudīt rezultātu
        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 piemērs izmanto `unittest.mock.patch`, lai aizstātu `get_data_from_api` funkciju ar aizstājēju. Tests konfigurē aizstājēju, lai tas atgrieztu noteiktu vērtību, un pēc tam pārbauda, vai `process_data` funkcija atgriež gaidīto rezultātu.

Šeit ir tas pats piemērs, izmantojot `pytest-mock`:

# pytest versija
import pytest

def get_data_from_api(url):
    # Reālā scenārijā tas veiktu API izsaukumu
    # Vienkāršības labad mēs simulējam API izsaukumu
    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` bibliotēka nodrošina `mocker` fiksatoru (fixture), kas vienkāršo aizstājēju izveidi un konfigurēšanu pytest testos.

Java ar Mockito

Mockito ir populārs aizstāšanas ietvars priekš Java.

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() {
        // Izveidot aizstājēju DataFetcher
        DataFetcher mockDataFetcher = mock(DataFetcher.class);

        // Konfigurēt aizstājēju
        when(mockDataFetcher.fetchData("https://example.com/api")).thenReturn("API Data");

        // Izveidot DataProcessor ar aizstājēju
        DataProcessor dataProcessor = new DataProcessor(mockDataFetcher);

        // Izsaukt testējamo funkciju
        String result = dataProcessor.processData("https://example.com/api");

        // Pārbaudīt rezultātu
        assertEquals("Processed: API Data", result);

        // Pārbaudīt, vai aizstājējs tika izsaukts
        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");
    }
}

Šajā piemērā `Mockito.mock()` izveido aizstājējobjektu `DataFetcher` interfeisam. `when()` tiek izmantots, lai konfigurētu aizstājēja atgriezto vērtību, un `verify()` tiek izmantots, lai pārbaudītu, vai aizstājējs tika izsaukts ar gaidītajiem argumentiem.

Labākā prakse aizstājējfunkciju izmantošanā

Alternatīvas aizstājējfunkcijām

Lai gan aizstājējfunkcijas ir jaudīgs rīks, tās ne vienmēr ir labākais risinājums. Dažos gadījumos citas tehnikas varētu būt piemērotākas:

Noslēgums

Aizstājējfunkcijas ir būtisks rīks efektīvu vienībtestu rakstīšanai, kas ļauj izolēt vienības, kontrolēt uzvedību, simulēt kļūdu situācijas un testēt asinhrono kodu. Ievērojot labāko praksi un izprotot alternatīvas, jūs varat izmantot aizstājējfunkcijas, lai veidotu robustāku, uzticamāku un uzturamāku programmatūru. Atcerieties apsvērt kompromisus un izvēlēties pareizo testēšanas tehniku katrai situācijai, lai izveidotu visaptverošu un efektīvu testēšanas stratēģiju neatkarīgi no tā, kurā pasaules daļā jūs izstrādājat.