Почему модульный тест?

При написании кода, который имеет решающее значение для бизнеса, как разработчик может убедиться, что его код обрабатывает все пограничные случаи?

Как разработчик укрепляет доверие к своему коду?

Как убедиться, что кто-то другой рефакторинг функции не нарушит существующую функциональность? или не вносит никаких новых ошибок?

Ответ — юнит-тест.

Цель модульного теста

Модульный тест, как следует из названия, гарантирует, что конкретный модуль работает идеально. Независимо от крайних случаев, устройство не выйдет из строя.

Если мы рассмотрим большую функцию, состоящую из нескольких модулей, которые работают отлично. тогда изменения функции, содержащей ошибки, минимальны.

Сфера

Объем этой статьи охватывает только модульное тестирование NestJS (с использованием jest), но основные концепции и методы в основном всегда одни и те же.

Основной пример

начнем с очень простого примера, сложения. ниже приведен служебный файл, который обрабатывает сумму двух чисел:

import { Injectable } from '@nestjs/common';
@Injectable()
export class TestService {
  sum( a:number, b:number ): number{
    if(a <0 || b <0){
      throw new Error('it should be > 0');
    }
    return a + b;
  }
}

Каждый набор тестов для модуля должен охватывать как минимум следующие сценарии.

  • счастливый сценарий — модуль должен вернуть ожидаемый результат
  • негативный сценарий — если передан ввод, который не должен быть передан, модуль должен выдать правильную ошибку, а не неожиданную ошибку.

test.service.spec.ts

import { Test, TestingModule } from '@nestjs/testing';
import { TestService } from './test.service';
describe('TestService', () => {
  let service: TestService;
  let a,b;
  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [TestService],
    }).compile();
    a = 100;
    b = 200;
    service = module.get<TestService>(TestService);
  });
  it('should be defined', () => {
    expect(service).toBeDefined();
  });
  
  it("sum | it should give me right output", () => {
    let actualOutput = service.sum(a, b);
    let expectedOutput = 300;
    expect(actualOutput).toEqual(expectedOutput);
  });
  
  it("sum | it should return a number", () => {
    let actualOutput = service.sum(a, b);    
    expect(typeof actualOutput).toEqual("number");
  });
  it("sum | if i pass NaN then i will get NaN", () => {
    a = NaN;
    let actualOutput = service.sum(a, b);    
    expect(actualOutput).toBeNaN()
  });
  it("sum | if a| b < 0, i should get an error", () => {
    a = -1;
    try{
      service.sum(a, b);
      throw new Error('it should have failed, but didnt');
    }catch(err){
      expect(err.message).toEqual("it should be > 0");
    }
  });
});

Изучение тестовых случаев

если мы рассмотрим это,

описать(‘TestService’, () =› {

Определяет основной контекст, в зависимости от того, как вы его настроили, он может иметь несколько контекстов.

it("sum | it should give me right output", () => {
    let actualOutput = service.sum(a, b);
    let expectedOutput = 300;
    expect(actualOutput).toEqual(expectedOutput);
  });

каждый блок it имеет оператор утверждения, который в основном определяет поведение этого теста.

в it мы можем определить поведение теста. Хорошей практикой является следование приведенному ниже соглашению об именах.

‹название функции› | ‹ожидаемое поведение теста›

ожидать (фактический вывод). toEqual (ожидаемый вывод);

это оператор утверждения (https://nodejs.org/api/assert.html), мы можем использовать любой язык утверждений (https://jestjs.io/docs/expect)

Все тестовые случаи в примере говорят сами за себя, мы рассмотрим еще один тестовый пример

it("sum | if a| b < 0, i should get an error", () => {
    a = -1;
    try{
      service.sum(a, b);
      throw new Error('it should have failed, but didnt');
    }catch(err){
      expect(err.message).toEqual("it should be > 0");
    }
  });

в приведенном выше тестовом примере мы видим, что наша функция должна выдавать ошибку, и это не должна быть какая-либо ошибка, это также должна быть ожидаемая ошибка, поэтому мы используем блок catch для утверждения

expect(err.message).toEqual("должно быть › 0");

также, если нет ошибки, это также не ожидаемое поведение. поэтому мы выдаем ошибку, чтобы не пройти тестовый пример

выдать новую ошибку («должно было получиться, но не получилось»);

ЗАМЕТКА:

  • При создании сервиса Nest автоматически создает тестовый файл, соглашение заключается в том, что тестовые файлы должны заканчиваться на *.spec.ts.
  • при создании тестового модуля мы также должны импортировать любую зависимость, которую модуль имеет
const module: TestingModule = await Test.createTestingModule({
      providers: [TestService],
    }).compile();