Lietuvių

Įsisavinkite pažangius „Jest“ testavimo metodus, kad sukurtumėte patikimesnę ir lengviau prižiūrimą programinę įrangą. Susipažinkite su imitavimo, momentinių kopijų testavimo, pasirinktinių derintojų ir kitomis technikomis, skirtomis pasaulinėms kūrėjų komandoms.

Jest: pažangūs testavimo modeliai tvirtai programinei įrangai

Šiuolaikinėje sparčiai besivystančioje programinės įrangos kūrimo aplinkoje itin svarbu užtikrinti kodo bazės patikimumą ir stabilumą. Nors „Jest“ tapo „de facto“ standartu „JavaScript“ testavimui, peržengus paprastų vienetų testų ribas, atsiveria naujas pasitikėjimo jūsų programomis lygis. Šiame įraše gilinamasi į pažangius „Jest“ testavimo modelius, kurie yra būtini kuriant tvirtą programinę įrangą ir yra skirti pasaulinei programuotojų auditorijai.

Kodėl verta peržengti paprastų vienetų testų ribas?

Paprasti vienetų testai patikrina atskirus komponentus izoliuotai. Tačiau realios programos yra sudėtingos sistemos, kuriose komponentai sąveikauja. Pažangūs testavimo modeliai sprendžia šias problemas, leisdami mums:

Imitavimo (Mocking) ir šnipinėjimo (Spies) įvaldymas

Imitavimas (angl. mocking) yra labai svarbus norint išskirti testuojamą vienetą, pakeičiant jo priklausomybes kontroliuojamais pakaitalais. „Jest“ tam suteikia galingus įrankius:

jest.fn(): imitavimo ir šnipinėjimo pagrindas

jest.fn() sukuria imituojančią funkciją. Galite sekti jos iškvietimus, argumentus ir grąžinamas reikšmes. Tai yra sudėtingesnių imitavimo strategijų pagrindas.

Pavyzdys: funkcijų iškvietimų sekimas

// component.js
export const fetchData = () => {
  // Simuliuoja API užklausą
  return Promise.resolve({ data: 'some data' });
};

export const processData = async (fetcher) => {
  const result = await fetcher();
  return `Processed: ${result.data}`;
};

// component.test.js
import { processData } from './component';

test('turėtų teisingai apdoroti duomenis', async () => {
  const mockFetcher = jest.fn().mockResolvedValue({ data: 'mocked data' });
  const result = await processData(mockFetcher);
  expect(result).toBe('Processed: mocked data');
  expect(mockFetcher).toHaveBeenCalledTimes(1);
  expect(mockFetcher).toHaveBeenCalledWith();
});

jest.spyOn(): stebėjimas nepakeičiant

jest.spyOn() leidžia stebėti esamo objekto metodo iškvietimus, nebūtinai pakeičiant jo įgyvendinimą. Jei reikia, galite imituoti ir patį įgyvendinimą.

Pavyzdys: modulio metodo šnipinėjimas

// logger.js
export const logInfo = (message) => {
  console.log(`INFO: ${message}`);
};

// service.js
import { logInfo } from './logger';

export const performTask = (taskName) => {
  logInfo(`Starting task: ${taskName}`);
  // ... užduoties logika ...
  logInfo(`Task ${taskName} completed.`);
};

// service.test.js
import { performTask } from './service';
import * as logger from './logger';

test('turėtų registruoti užduoties pradžią ir pabaigą', () => {
  const logSpy = jest.spyOn(logger, 'logInfo');

  performTask('backup');

  expect(logSpy).toHaveBeenCalledTimes(2);
  expect(logSpy).toHaveBeenCalledWith('Starting task: backup');
  expect(logSpy).toHaveBeenCalledWith('Task backup completed.');

  logSpy.mockRestore(); // Svarbu atkurti pradinį įgyvendinimą
});

Modulių importavimo imitavimas

„Jest“ modulių imitavimo galimybės yra plačios. Galite imituoti ištisus modulius ar konkrečius eksportuojamus elementus.

Pavyzdys: išorinio API kliento imitavimas

// api.js
import axios from 'axios';

export const getUser = async (userId) => {
  const response = await axios.get(`/api/users/${userId}`);
  return response.data;
};

// user-service.js
import { getUser } from './api';

export const getUserFullName = async (userId) => {
  const user = await getUser(userId);
  return `${user.firstName} ${user.lastName}`;
};

// user-service.test.js
import { getUserFullName } from './user-service';
import * as api from './api';

// Imituojamas visas api modulis
jest.mock('./api');

test('turėtų gauti pilną vardą naudojant imituotą API', async () => {
  // Imituojama konkreti funkcija iš imituoto modulio
  api.getUser.mockResolvedValue({ id: 1, firstName: 'Ada', lastName: 'Lovelace' });

  const fullName = await getUserFullName(1);

  expect(fullName).toBe('Ada Lovelace');
  expect(api.getUser).toHaveBeenCalledTimes(1);
  expect(api.getUser).toHaveBeenCalledWith(1);
});

Automatinis ir rankinis imitavimas

„Jest“ automatiškai imituoja Node.js modulius. ES moduliams ar pasirinktiniams moduliams gali prireikti jest.mock(). Norėdami daugiau kontrolės, galite sukurti __mocks__ katalogus.

Imituojantys įgyvendinimai

Galite pateikti pasirinktinius savo imitacijų įgyvendinimus.

Pavyzdys: imitavimas su pasirinktiniu įgyvendinimu

// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// calculator.js
import { add, subtract } from './math';

export const calculate = (operation, a, b) => {
  if (operation === 'add') {
    return add(a, b);
  } else if (operation === 'subtract') {
    return subtract(a, b);
  }
  return null;
};

// calculator.test.js
import { calculate } from './calculator';
import * as math from './math';

// Imituojamas visas math modulis
jest.mock('./math');

test('turėtų atlikti sudėtį naudojant imituotą math add', () => {
  // Pateikiamas imituojantis 'add' funkcijos įgyvendinimas
  math.add.mockImplementation((a, b) => a + b + 10); // Pridedama 10 prie rezultato
  math.subtract.mockReturnValue(5); // Atimtis taip pat imituojama

  const result = calculate('add', 5, 3);

  expect(math.add).toHaveBeenCalledWith(5, 3);
  expect(result).toBe(18); // 5 + 3 + 10

  const subResult = calculate('subtract', 10, 2);
  expect(math.subtract).toHaveBeenCalledWith(10, 2);
  expect(subResult).toBe(5);
});

Momentinių kopijų testavimas: vartotojo sąsajos ir konfigūracijos išsaugojimas

Momentinių kopijų testai (angl. snapshot tests) yra galinga funkcija, leidžianti fiksuoti jūsų komponentų ar konfigūracijų išvestį. Jie ypač naudingi testuojant vartotojo sąsają arba tikrinant sudėtingas duomenų struktūras.

Kaip veikia momentinių kopijų testavimas

Kai momentinės kopijos testas paleidžiamas pirmą kartą, „Jest“ sukuria .snap failą su serijiniu testuojamos reikšmės atvaizdu. Vėlesnių paleidimų metu „Jest“ palygina dabartinę išvestį su išsaugota momentine kopija. Jei jos skiriasi, testas nepavyksta, įspėdamas jus apie nenumatytus pakeitimus. Tai neįkainojama aptinkant regresijas vartotojo sąsajos komponentuose skirtinguose regionuose ar lokalėse.

Pavyzdys: „React“ komponento momentinės kopijos kūrimas

Tarkime, turite „React“ komponentą:

// UserProfile.js
import React from 'react';

const UserProfile = ({ name, email, isActive }) => (
  <div>
    <h2>{name}</h2>
    <p><strong>El. paštas:</strong> {email}</p>
    <p><strong>Būsena:</strong> {isActive ? 'Aktyvus' : 'Neaktyvus'}</p>
  </div>
);

export default UserProfile;

// UserProfile.test.js
import React from 'react';
import renderer from 'react-test-renderer'; // „React“ komponentų momentinėms kopijoms
import UserProfile from './UserProfile';

test('teisingai atvaizduoja UserProfile', () => {
  const user = {
    name: 'Jane Doe',
    email: 'jane.doe@example.com',
    isActive: true,
  };
  const component = renderer.create(
    <UserProfile {...user} />
  );
  const tree = component.toJSON();
  expect(tree).toMatchSnapshot();
});

test('teisingai atvaizduoja neaktyvų UserProfile', () => {
  const user = {
    name: 'John Smith',
    email: 'john.smith@example.com',
    isActive: false,
  };
  const component = renderer.create(
    <UserProfile {...user} />
  );
  const tree = component.toJSON();
  expect(tree).toMatchSnapshot('inactive user profile'); // Pavadinta momentinė kopija
});

Paleidus testus, „Jest“ sukurs UserProfile.test.js.snap failą. Atnaujinę komponentą, turėsite peržiūrėti pakeitimus ir galbūt atnaujinti momentinę kopiją, paleisdami „Jest“ su --updateSnapshot arba -u vėliavėle.

Geriausios momentinių kopijų testavimo praktikos

Pasirinktiniai derintojai (Custom Matchers): testų skaitomumo gerinimas

„Jest“ integruoti derintojai yra platūs, tačiau kartais reikia patikrinti specifines sąlygas, kurių jie neapima. Pasirinktiniai derintojai leidžia jums sukurti savo patvirtinimo logiką, todėl jūsų testai tampa išraiškingesni ir lengviau skaitomi.

Pasirinktinių derintojų kūrimas

Galite išplėsti „Jest“ expect objektą savo derintojais.

Pavyzdys: galiojančio el. pašto formato tikrinimas

Savo „Jest“ sąrankos faile (pvz., jest.setup.js, sukonfigūruotame jest.config.js):

// jest.setup.js

expect.extend({
  toBeValidEmail(received) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    const pass = emailRegex.test(received);

    if (pass) {
      return {
        message: () => `expected ${received} not to be a valid email`,
        pass: true,
      };
    } else {
      return {
        message: () => `expected ${received} to be a valid email`,
        pass: false,
      };
    }
  },
});

// Jūsų jest.config.js
// module.exports = { setupFilesAfterEnv: ['/jest.setup.js'] };

Savo testo faile:

// validation.test.js

test('turėtų patvirtinti el. pašto formatus', () => {
  expect('test@example.com').toBeValidEmail();
  expect('invalid-email').not.toBeValidEmail();
  expect('another.test@sub.domain.co.uk').toBeValidEmail();
});

Pasirinktinių derintojų privalumai

Asinchroninių operacijų testavimas

„JavaScript“ yra labai asinchroniška. „Jest“ suteikia puikų palaikymą testuojant pažadus (promises) ir async/await.

async/await naudojimas

Tai yra moderniausias ir geriausiai skaitomas būdas testuoti asinchroninį kodą.

Pavyzdys: asinchroninės funkcijos testavimas

// dataService.js
export const fetchUserData = async (userId) => {
  // Simuliuojamas duomenų gavimas po delsos
  await new Promise(resolve => setTimeout(resolve, 50));
  if (userId === 1) {
    return { id: 1, name: 'Alice' };
  } else {
    throw new Error('User not found');
  }
};

// dataService.test.js
import { fetchUserData } from './dataService';

test('teisingai gauna vartotojo duomenis', async () => {
  const user = await fetchUserData(1);
  expect(user).toEqual({ id: 1, name: 'Alice' });
});

test('meta klaidą neegzistuojančiam vartotojui', async () => {
  await expect(fetchUserData(2)).rejects.toThrow('User not found');
});

.resolves ir .rejects naudojimas

Šie derintojai supaprastina pažadų įvykdymo ir atmetimo testavimą.

Pavyzdys: .resolves/.rejects naudojimas

// dataService.test.js (tęsinys)

test('gauna vartotojo duomenis su .resolves', () => {
  return expect(fetchUserData(1)).resolves.toEqual({ id: 1, name: 'Alice' });
});

test('meta klaidą neegzistuojančiam vartotojui su .rejects', () => {
  return expect(fetchUserData(2)).rejects.toThrow('User not found');
});

Laikmačių valdymas

Funkcijoms, kurios naudoja setTimeout arba setInterval, „Jest“ suteikia laikmačio valdymą.

Pavyzdys: laikmačių valdymas

// delayedGreeter.js
export const greetAfterDelay = (name, callback) => {
  setTimeout(() => {
    callback(`Hello, ${name}!`);
  }, 1000);
};

// delayedGreeter.test.js
import { greetAfterDelay } from './delayedGreeter';

jest.useFakeTimers(); // Įgalinti netikrus laikmačius

test('pasveikina po delsos', () => {
  const mockCallback = jest.fn();
  greetAfterDelay('World', mockCallback);

  // Pasukti laikmačius 1000ms į priekį
  jest.advanceTimersByTime(1000);

  expect(mockCallback).toHaveBeenCalledTimes(1);
  expect(mockCallback).toHaveBeenCalledWith('Hello, World!');
});

// Atkurti tikrus laikmačius, jei reikia kitur
jest.useRealTimers();

Testų organizavimas ir struktūra

Augant jūsų testų rinkiniui, organizavimas tampa itin svarbus siekiant palaikomumo.

„Describe“ ir „it“ blokai

Naudokite describe susijusiems testams grupuoti ir it (arba test) atskiriems testavimo atvejams. Ši struktūra atspindi programos moduliškumą.

Pavyzdys: struktūrizuoti testai

describe('Vartotojo autentifikavimo paslauga', () => {
  let authService;

  beforeEach(() => {
    // Paruošti imitacijas ar paslaugų egzempliorius prieš kiekvieną testą
    authService = require('./authService');
    jest.spyOn(authService, 'login').mockImplementation(() => Promise.resolve({ token: 'fake_token' }));
  });

  afterEach(() => {
    // Išvalyti imitacijas
    jest.restoreAllMocks();
  });

  describe('prisijungimo funkcionalumas', () => {
    it('turėtų sėkmingai prijungti vartotoją su galiojančiais prisijungimo duomenimis', async () => {
      const result = await authService.login('user@example.com', 'password123');
      expect(result.token).toBeDefined();
      // ... daugiau patvirtinimų ...
    });

    it('turėtų nepavykti prisijungti su negaliojančiais prisijungimo duomenimis', async () => {
      jest.spyOn(authService, 'login').mockRejectedValue(new Error('Invalid credentials'));
      await expect(authService.login('user@example.com', 'wrong_password')).rejects.toThrow('Invalid credentials');
    });
  });

  describe('atsijungimo funkcionalumas', () => {
    it('turėtų išvalyti vartotojo sesiją', async () => {
      // Testuoti atsijungimo logiką...
    });
  });
});

Sąrankos (Setup) ir išmontavimo (Teardown) kabliai

Šie kabliai yra būtini norint paruošti imituojamus duomenis, duomenų bazių ryšius ar išvalyti resursus tarp testų.

Testavimas pasaulinei auditorijai

Kuriant programas pasaulinei auditorijai, testavimo aspektai išsiplečia:

Tarptautinimas (i18n) ir lokalizavimas (l10n)

Užtikrinkite, kad jūsų vartotojo sąsaja ir pranešimai teisingai prisitaikytų prie skirtingų kalbų ir regioninių formatų.

Pavyzdys: lokalizuoto datos formato testavimas

// dateUtils.js
export const formatLocalizedDate = (date, locale) => {
  return new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'numeric', day: 'numeric' }).format(date);
};

// dateUtils.test.js
import { formatLocalizedDate } from './dateUtils';

test('teisingai formatuoja datą JAV lokalei', () => {
  const date = new Date(2023, 10, 15); // 2023 m. lapkričio 15 d.
  expect(formatLocalizedDate(date, 'en-US')).toBe('11/15/2023');
});

test('teisingai formatuoja datą Vokietijos lokalei', () => {
  const date = new Date(2023, 10, 15);
  expect(formatLocalizedDate(date, 'de-DE')).toBe('15.11.2023');
});

Laiko juostų išmanymas

Testuokite, kaip jūsų programa tvarko skirtingas laiko juostas, ypač funkcijoms, tokioms kaip planavimas ar realaus laiko atnaujinimai. Sistemos laikrodžio imitavimas arba bibliotekų, kurios abstrahuoja laiko juostas, naudojimas gali būti naudingas.

Kultūriniai duomenų niuansai

Apsvarstykite, kaip skaičiai, valiutos ir kitos duomenų reprezentacijos gali būti suvokiamos ar tikimasi skirtingai įvairiose kultūrose. Čia ypač naudingi gali būti pasirinktiniai derintojai.

Pažangios technikos ir strategijos

Testais pagrįstas kūrimas (TDD) ir elgsena pagrįstas kūrimas (BDD)

„Jest“ puikiai dera su TDD („Red-Green-Refactor“) ir BDD („Given-When-Then“) metodologijomis. Rašykite testus, kurie apibūdina norimą elgseną, prieš rašydami įgyvendinimo kodą. Tai užtikrina, kad kodas nuo pat pradžių rašomas atsižvelgiant į testuojamumą.

Integracinis testavimas su „Jest“

Nors „Jest“ puikiai tinka vienetų testams, jį taip pat galima naudoti integraciniams testams. Gali padėti mažesnio skaičiaus priklausomybių imitavimas arba tokių įrankių kaip „Jest“ runInBand parinkties naudojimas.

Pavyzdys: API sąveikos testavimas (supaprastintas)

// apiService.js
import axios from 'axios';

const API_BASE_URL = 'https://api.example.com';

export const createProduct = async (productData) => {
  const response = await axios.post(`${API_BASE_URL}/products`, productData);
  return response.data;
};

// apiService.test.js (Integracinis testas)
import axios from 'axios';
import { createProduct } from './apiService';

// Imituojamas axios integraciniams testams, kad būtų galima kontroliuoti tinklo lygmenį
jest.mock('axios');

test('sukuriamas produktas per API', async () => {
  const mockProduct = { id: 1, name: 'Gadget' };
  const responseData = { success: true, product: mockProduct };

  axios.post.mockResolvedValue({
    data: responseData,
    status: 201,
    headers: { 'content-type': 'application/json' },
  });

  const newProductData = { name: 'Gadget', price: 99.99 };
  const result = await createProduct(newProductData);

  expect(axios.post).toHaveBeenCalledWith(`${process.env.API_BASE_URL || 'https://api.example.com'}/products`, newProductData);
  expect(result).toEqual(responseData);
});

Lygiagretumas ir konfigūravimas

„Jest“ gali paleisti testus lygiagrečiai, kad pagreitintų vykdymą. Sukonfigūruokite tai savo jest.config.js faile. Pavyzdžiui, nustatymas maxWorkers kontroliuoja lygiagrečių procesų skaičių.

Aprėpties ataskaitos

Naudokite „Jest“ integruotą aprėpties ataskaitų teikimą, kad nustatytumėte kodo bazės dalis, kurios nėra testuojamos. Paleiskite testus su --coverage, kad sugeneruotumėte išsamias ataskaitas.

jest --coverage

Aprėpties ataskaitų peržiūra padeda užtikrinti, kad jūsų pažangūs testavimo modeliai veiksmingai apimtų kritinę logiką, įskaitant internacionalizavimo ir lokalizavimo kodo kelius.

Išvada

Pažangių „Jest“ testavimo modelių įvaldymas yra svarbus žingsnis kuriant patikimą, lengvai prižiūrimą ir aukštos kokybės programinę įrangą pasaulinei auditorijai. Efektyviai naudodami imitavimą, momentinių kopijų testavimą, pasirinktinius derintojus ir asinchroninio testavimo technikas, galite sustiprinti savo testų rinkinio tvirtumą ir įgyti didesnį pasitikėjimą savo programos elgsena įvairiuose scenarijuose ir regionuose. Šių modelių taikymas įgalina kūrėjų komandas visame pasaulyje teikti išskirtines vartotojų patirtis.

Pradėkite taikyti šias pažangias technikas savo darbo eigoje jau šiandien, kad pakeltumėte savo „JavaScript“ testavimo praktikas į aukštesnį lygį.