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:
- Modulių izoliavimas: Pašalinti išorines priklausomybes, siekiant sutelkti dėmesį į vienos funkcijos ar komponento elgseną.
- Elgsenos valdymas: Apibrėžti konkrečias grąžinamas reikšmes, sukelti klaidas ar vykdyti pasirinktinę logiką testavimo metu.
- Sąveikų stebėjimas: Stebėti, kiek kartų funkcija yra iškviečiama, kokius argumentus ji gauna ir kokia tvarka yra iškviečiama.
- Kraštutinių atvejų simuliavimas: Lengvai sukurti scenarijus, kuriuos sunku ar neįmanoma atkurti realioje aplinkoje (pvz., tinklo gedimai, duomenų bazės klaidos).
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ų:
- Lėti testai: Realių priklausomybių nustatymas ir vykdymas gali būti lėtas, o tai ženkliai pailgina testų vykdymo laiką.
- Nepatikimi testai: Išorinės priklausomybės gali būti nenuspėjamos ir linkusios į gedimus, dėl ko testai tampa nestabilūs.
- Sudėtingumas: Realių priklausomybių valdymas ir konfigūravimas gali pridėti nereikalingo sudėtingumo jūsų testavimo aplinkai.
- Kaina: Išorinių paslaugų naudojimas dažnai kainuoja, ypač atliekant išsamius testus.
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:
- Identifikuokite priklausomybes: Nustatykite, kurias išorines priklausomybes reikia imituoti.
- 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`.
- Konfigūruokite imituotą elgseną: Apibrėžkite imituotų funkcijų elgseną, pvz., jų grąžinamas reikšmes, klaidų sąlygas ir iškvietimų skaičių.
- Įdiekite imitacijas: Pakeiskite tikrąsias priklausomybes imituotais objektais jūsų testuojamame modulyje. Tai dažnai daroma naudojant priklausomybių injekciją.
- Vykdykite testą: Paleiskite testą ir stebėkite, kaip testuojamas modulis sąveikauja su imituotomis funkcijomis.
- 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ų.
- 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
- Imituokite saikingai: Imituokite tik tas priklausomybes, kurios yra tikrai išorinės arba sukuria didelį sudėtingumą. Venkite imituoti įgyvendinimo detales.
- Imitacijos turi būti paprastos: Imituotos funkcijos turėtų būti kuo paprastesnės, kad į jūsų testus neįvestumėte klaidų.
- Naudokite priklausomybių injekciją: Naudokite priklausomybių injekciją, kad būtų lengviau pakeisti tikrąsias priklausomybes imituotais objektais. Konstruktoriaus injekcija yra pageidautina, nes ji aiškiai parodo priklausomybes.
- Patikrinkite sąveikas: Visada patikrinkite, ar jūsų testuojamas modulis sąveikauja su imituotomis funkcijomis taip, kaip tikėtasi.
- Atkurkite pradinį funkcionalumą: Po kiekvieno testo atkurkite pradinį funkcionalumą, pašalindami imituotus objektus ir grįždami prie realių priklausomybių.
- Dokumentuokite imitacijas: Aiškiai dokumentuokite savo imituotas funkcijas, paaiškindami jų paskirtį ir elgseną.
- Venkite perteklinio specifikavimo: Netikrinkite kiekvienos sąveikos, sutelkite dėmesį į pagrindines sąveikas, kurios yra būtinos jūsų testuojamai elgsenai.
- Apsvarstykite integracinius testus: Nors moduliniai testai su imitacijomis yra svarbūs, nepamirškite juos papildyti integraciniais testais, kurie patikrina sąveikas tarp realių komponentų.
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:
- Stub'ai (Stubs): Stub'ai yra paprastesni nei imitacijos (mocks). Jie pateikia iš anksto nustatytus atsakymus į funkcijų iškvietimus, bet paprastai netikrina, kaip tie iškvietimai atliekami. Jie naudingi, kai jums tereikia valdyti įvestį į jūsų testuojamą modulį.
- Šnipai (Spies): Šnipai leidžia stebėti realios funkcijos elgseną, tuo pačiu leidžiant jai vykdyti savo pradinę logiką. Jie naudingi, kai norite patikrinti, ar funkcija yra iškviečiama su konkrečiais argumentais arba tam tikrą kartų skaičių, visiškai nepakeičiant jos funkcionalumo.
- Netikri objektai (Fakes): Netikri objektai yra veikiančios priklausomybės implementacijos, bet supaprastintos testavimo tikslams. Pavyzdžiui, atmintyje veikianti duomenų bazė yra netikro objekto pavyzdys.
- Integraciniai testai: Integraciniai testai patikrina sąveikas tarp kelių komponentų. Jie gali būti gera alternatyva moduliniams testams su imitacijomis, kai norite testuoti sistemos elgseną kaip visumą.
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.