Čeština

Naučte se efektivně používat mockovací funkce ve své testovací strategii pro robustní a spolehlivý vývoj softwaru. Tento průvodce se zabývá tím, kdy, proč a jak implementovat mocky s praktickými příklady.

Mockovací funkce: Komplexní průvodce pro vývojáře

Ve světě vývoje softwaru je psaní robustního a spolehlivého kódu prvořadé. Pro dosažení tohoto cíle je klíčové důkladné testování. Zejména unit testování se zaměřuje na testování jednotlivých komponent nebo funkcí izolovaně. Reálné aplikace však často obsahují složité závislosti, což ztěžuje testování jednotek v úplné izolaci. A právě zde přicházejí na řadu mockovací funkce.

Co jsou mockovací funkce?

Mockovací funkce je simulovaná verze skutečné funkce, kterou můžete použít ve svých testech. Místo vykonávání logiky skutečné funkce vám mockovací funkce umožňuje řídit její chování, sledovat, jak je volána, a definovat její návratové hodnoty. Jsou typem testovacího dvojníka.

Představte si to takto: testujete motor automobilu (testovanou jednotku). Motor se spoléhá na různé další komponenty, jako je systém vstřikování paliva a chladicí systém. Místo toho, abyste během testu motoru spouštěli skutečné systémy vstřikování paliva a chlazení, můžete použít mockovací systémy, které simulují jejich chování. To vám umožní izolovat motor a zaměřit se specificky na jeho výkon.

Mockovací funkce jsou mocnými nástroji pro:

Kdy používat mockovací funkce

Mocky jsou nejužitečnější v těchto situacích:

1. Izolace jednotek s externími závislostmi

Když vaše testovaná jednotka závisí na externích službách, databázích, API nebo jiných komponentách, použití skutečných závislostí během testování může přinést několik problémů:

Příklad: Představte si, že testujete funkci, která získává uživatelská data ze vzdáleného API. Místo provádění skutečných volání API během testování můžete použít mockovací funkci k simulaci odpovědi API. To vám umožní testovat logiku funkce bez spoléhání se na dostupnost nebo výkon externího API. To je obzvláště důležité, když má API omezení počtu požadavků (rate limits) nebo jsou s každým požadavkem spojeny náklady.

2. Testování složitých interakcí

V některých případech může vaše testovaná jednotka interagovat s jinými komponentami složitými způsoby. Mockovací funkce vám umožňují tyto interakce sledovat a ověřovat.

Příklad: Zvažte funkci, která zpracovává platební transakce. Tato funkce může interagovat s platební bránou, databází a notifikační službou. Pomocí mockovacích funkcí můžete ověřit, že funkce volá platební bránu se správnými detaily transakce, aktualizuje databázi se stavem transakce a odesílá oznámení uživateli.

3. Simulace chybových stavů

Testování zpracování chyb je klíčové pro zajištění robustnosti vaší aplikace. Mockovací funkce usnadňují simulaci chybových stavů, které je v reálném prostředí obtížné nebo nemožné reprodukovat.

Příklad: Předpokládejme, že testujete funkci, která nahrává soubory do cloudového úložiště. Můžete použít mockovací funkci k simulaci chyby sítě během procesu nahrávání. To vám umožní ověřit, že funkce správně zpracuje chybu, zkusí nahrávání znovu nebo upozorní uživatele.

4. Testování asynchronního kódu

Asynchronní kód, jako je kód používající zpětná volání (callbacks), přísliby (promises) nebo async/await, může být náročný na testování. Mockovací funkce vám mohou pomoci řídit časování a chování asynchronních operací.

Příklad: Představte si, že testujete funkci, která získává data ze serveru pomocí asynchronního požadavku. Můžete použít mockovací funkci k simulaci odpovědi serveru a řídit, kdy je odpověď vrácena. To vám umožní testovat, jak funkce zpracovává různé scénáře odpovědí a časové limity.

5. Prevence nezamýšlených vedlejších efektů

Někdy může volání skutečné funkce během testování mít nezamýšlené vedlejší efekty, jako je úprava databáze, odesílání e-mailů nebo spouštění externích procesů. Mockovací funkce těmto vedlejším efektům zabraňují tím, že vám umožní nahradit skutečnou funkci řízenou simulací.

Příklad: Testujete funkci, která odesílá uvítací e-maily novým uživatelům. Použitím mockovací e-mailové služby můžete zajistit, že funkce pro odesílání e-mailů během běhu vaší testovací sady skutečně neposílá e-maily skutečným uživatelům. Místo toho můžete ověřit, že se funkce pokusí odeslat e-mail se správnými informacemi.

Jak používat mockovací funkce

Konkrétní kroky pro použití mockovacích funkcí závisí na programovacím jazyce a testovacím frameworku, který používáte. Obecný postup však obvykle zahrnuje následující kroky:

  1. Identifikujte závislosti: Určete, které externí závislosti potřebujete mockovat.
  2. Vytvořte mockovací objekty: Vytvořte mockovací objekty nebo funkce, které nahradí skutečné závislosti. Tyto mocky budou často mít vlastnosti jako `called`, `returnValue` a `callArguments`.
  3. Nakonfigurujte chování mocku: Definujte chování mockovacích funkcí, jako jsou jejich návratové hodnoty, chybové stavy a počet volání.
  4. Vložte mocky: Nahraďte skutečné závislosti mockovacími objekty ve vaší testované jednotce. To se často provádí pomocí vkládání závislostí (dependency injection).
  5. Spusťte test: Spusťte váš test a sledujte, jak testovaná jednotka interaguje s mockovacími funkcemi.
  6. Ověřte interakce: Ověřte, že mockovací funkce byly volány s očekávanými argumenty, návratovými hodnotami a v očekávaném počtu.
  7. Obnovte původní funkcionalitu: Po testu obnovte původní funkcionalitu odstraněním mockovacích objektů a návratem ke skutečným závislostem. To pomáhá předejít vedlejším efektům na ostatní testy.

Příklady mockovacích funkcí v různých jazycích

Zde jsou příklady použití mockovacích funkcí v populárních programovacích jazycích a testovacích frameworcích:

JavaScript s Jestem

Jest je populární JavaScriptový testovací framework, který poskytuje vestavěnou podporu pro mockovací funkce.

// Testovaná funkce
function fetchData(callback) {
  setTimeout(() => {
    callback('Data from server');
  }, 100);
}

// Testovací případ
test('fetchData calls callback with correct data', (done) => {
  const mockCallback = jest.fn();
  fetchData(mockCallback);

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

V tomto příkladu `jest.fn()` vytvoří mockovací funkci, která nahradí skutečnou callback funkci. Test ověřuje, že mockovací funkce je volána se správnými daty pomocí `toHaveBeenCalledWith()`.

Pokročilejší příklad s použitím modulů:

// 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) {
  // Simulace volání API
  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 () => {
    // Mockování funkce getUserDataFromAPI
    const mockGetUserData = jest.spyOn(api, 'getUserDataFromAPI');
    mockGetUserData.mockResolvedValue({ id: 123, name: 'Mocked Name' });

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

    // Obnovení původní funkce
    mockGetUserData.mockRestore();
  });
});

Zde je `jest.spyOn` použit k vytvoření mockovací funkce pro funkci `getUserDataFromAPI` importovanou z modulu `./api`. `mockResolvedValue` se používá k určení návratové hodnoty mocku. `mockRestore` je nezbytný k zajištění, aby ostatní testy neúmyslně nepoužily mockovanou verzi.

Python s pytest a unittest.mock

Python nabízí několik knihoven pro mockování, včetně `unittest.mock` (vestavěná) a knihoven jako `pytest-mock` pro zjednodušené použití s pytestem.

# Testovaná funkce
def get_data_from_api(url):
    # V reálném scénáři by zde proběhlo volání API
    # Pro zjednodušení simulujeme volání API
    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"

# Testovací případ s použitím unittest.mock
import unittest
from unittest.mock import patch

class TestProcessData(unittest.TestCase):
    @patch('__main__.get_data_from_api') # Nahrazení get_data_from_api v hlavním modulu
    def test_process_data_success(self, mock_get_data_from_api):
        # Konfigurace mocku
        mock_get_data_from_api.return_value = {"data": "Mocked data"}

        # Volání testované funkce
        result = process_data("https://example.com/api")

        # Ověření výsledku
        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()

Tento příklad používá `unittest.mock.patch` k nahrazení funkce `get_data_from_api` mockem. Test konfiguruje mock tak, aby vracel specifickou hodnotu, a poté ověřuje, že funkce `process_data` vrací očekávaný výsledek.

Zde je stejný příklad s použitím `pytest-mock`:

# verze pro pytest
import pytest

def get_data_from_api(url):
    # V reálném scénáři by zde proběhlo volání API
    # Pro zjednodušení simulujeme volání API
    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"

Knihovna `pytest-mock` poskytuje `mocker` fixture, která zjednodušuje vytváření a konfiguraci mocků v rámci testů pytestu.

Java s Mockitem

Mockito je populární mocking framework pro Javu.

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() {
        // Vytvoření mocku DataFetcher
        DataFetcher mockDataFetcher = mock(DataFetcher.class);

        // Konfigurace mocku
        when(mockDataFetcher.fetchData("https://example.com/api")).thenReturn("API Data");

        // Vytvoření DataProcessor s mockem
        DataProcessor dataProcessor = new DataProcessor(mockDataFetcher);

        // Volání testované funkce
        String result = dataProcessor.processData("https://example.com/api");

        // Ověření výsledku
        assertEquals("Processed: API Data", result);

        // Ověření, že mock byl zavolán
        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");
    }
}

V tomto příkladu `Mockito.mock()` vytvoří mockovací objekt pro rozhraní `DataFetcher`. `when()` se používá ke konfiguraci návratové hodnoty mocku a `verify()` se používá k ověření, že mock byl volán s očekávanými argumenty.

Osvědčené postupy pro používání mockovacích funkcí

Alternativy k mockovacím funkcím

Ačkoli jsou mockovací funkce mocným nástrojem, nejsou vždy nejlepším řešením. V některých případech mohou být vhodnější jiné techniky:

Závěr

Mockovací funkce jsou nezbytným nástrojem pro psaní efektivních unit testů, které vám umožňují izolovat jednotky, řídit chování, simulovat chybové stavy a testovat asynchronní kód. Dodržováním osvědčených postupů a pochopením alternativ můžete využít mockovací funkce k budování robustnějšího, spolehlivějšího a udržitelnějšího softwaru. Nezapomeňte zvážit kompromisy a vybrat správnou testovací techniku pro každou situaci, abyste vytvořili komplexní a efektivní testovací strategii, bez ohledu na to, v jaké části světa vyvíjíte.