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:
- Vienību izolēšana: Ārējo atkarību noņemšana, lai koncentrētos uz vienas funkcijas vai komponenta uzvedību.
- Uzvedības kontrolēšana: Specifisku atgriezto vērtību definēšana, kļūdu izmešana vai pielāgotas loģikas izpilde testēšanas laikā.
- Mijiedarbību novērošana: Izsekošana, cik reižu funkcija tiek izsaukta, kādus argumentus tā saņem un kādā secībā tā tiek izsaukta.
- Robežgadījumu simulēšana: Viegli izveidot scenārijus, kurus ir grūti vai neiespējami reproducēt reālā vidē (piemēram, tīkla kļūmes, datu bāzes kļūdas).
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:
- Lēni testi: Reālas atkarības var būt lēnas iestatīšanā un izpildē, ievērojami palielinot testu izpildes laiku.
- Neuzticami testi: Ārējās atkarības var būt neparedzamas un pakļautas kļūmēm, kas noved pie nestabiliem testiem.
- Sarežģītība: Reālu atkarību pārvaldīšana un konfigurēšana var pievienot nevajadzīgu sarežģītību jūsu testa iestatījumam.
- Izmaksas: Ārējo pakalpojumu izmantošana bieži rada izmaksas, īpaši plašas testēšanas gadījumā.
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:
- Identificējiet atkarības: Nosakiet, kuras ārējās atkarības jums ir nepieciešams aizstāt.
- 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).
- 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.
- 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).
- Izpildiet testu: Palaidiet testu un novērojiet, kā testējamā vienība mijiedarbojas ar aizstājējfunkcijām.
- 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.
- 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ā
- Aizstājiet taupīgi: Aizstājiet tikai tās atkarības, kas ir patiesi ārējas vai rada būtisku sarežģītību. Izvairieties no implementācijas detaļu aizstāšanas.
- Uzturiet aizstājējus vienkāršus: Aizstājējfunkcijām jābūt pēc iespējas vienkāršākām, lai izvairītos no kļūdu ieviešanas jūsu testos.
- Izmantojiet atkarību ievadi: Izmantojiet atkarību ievadi (dependency injection), lai būtu vieglāk aizstāt reālas atkarības ar aizstājējobjektiem. Priekšroka tiek dota konstruktora ievadei, jo tā padara atkarības skaidri redzamas.
- Pārbaudiet mijiedarbības: Vienmēr pārbaudiet, vai jūsu testējamā vienība mijiedarbojas ar aizstājējfunkcijām gaidītajā veidā.
- Atjaunojiet sākotnējo funkcionalitāti: Pēc katra testa atjaunojiet sākotnējo funkcionalitāti, noņemot aizstājējobjektus un atgriežoties pie reālajām atkarībām.
- Dokumentējiet aizstājējus: Skaidri dokumentējiet savas aizstājējfunkcijas, lai paskaidrotu to mērķi un uzvedību.
- Izvairieties no pārmērīgas specifikācijas: Nepārbaudiet katru mijiedarbību, koncentrējieties uz galvenajām mijiedarbībām, kas ir būtiskas jūsu testējamajai uzvedībai.
- Apsveriet integrācijas testus: Lai gan vienībtesti ar aizstājējiem ir svarīgi, atcerieties tos papildināt ar integrācijas testiem, kas pārbauda mijiedarbību starp reāliem komponentiem.
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:
- Aizvietotāji (Stubs): Aizvietotāji ir vienkāršāki par aizstājējiem (mocks). Tie nodrošina iepriekš definētas atbildes uz funkciju izsaukumiem, bet parasti nepārbauda, kā šie izsaukumi tiek veikti. Tie ir noderīgi, ja jums ir nepieciešams tikai kontrolēt ievadi jūsu testējamai vienībai.
- Spiegi (Spies): Spiegi ļauj jums novērot reālas funkcijas uzvedību, vienlaikus ļaujot tai izpildīt savu sākotnējo loģiku. Tie ir noderīgi, ja vēlaties pārbaudīt, vai funkcija tiek izsaukta ar konkrētiem argumentiem vai noteiktu reižu skaitu, pilnībā neaizstājot tās funkcionalitāti.
- Viltojumi (Fakes): Viltojumi ir funkcionējošas atkarības implementācijas, bet vienkāršotas testēšanas vajadzībām. Atmiņā esoša datu bāze (in-memory database) ir viltojuma piemērs.
- Integrācijas testi: Integrācijas testi pārbauda mijiedarbību starp vairākiem komponentiem. Tie var būt laba alternatīva vienībtestiem ar aizstājējiem, ja vēlaties pārbaudīt sistēmas uzvedību kopumā.
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.