Пишем юнит тесты на TypeScript'е (на примере котиков)

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Как писать модульные тесты в проекте с TypeScript'ом? В этой статье я постараюсь ответить на этот вопрос а также покажу как создать среду модульного тестирования под проекты использующие TypeScript.

Юнит тесты, что это?

Unit tests ( модульные тесты) - тесты применяемые в различных слоях приложения, тестирующие наименьшую делимую логику приложения: например модуль, класс или метод.

Суть юнит тестов, в том чтобы писать их к каждому новому классу или методу, проверяя, не привело ли очередное изменение кода к появлению ошибок (багов) в уже протестированных местах программы.

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

Настройка окружения

Итак теперь ближе к делу. Предположим у нас есть некий проект со следующей структурой:

project
| node_modules
| src
| package.json
| tsconfig.json

В ./src лежит некий модуль cat.module.ts который содержит простой класс Cat.

export class Cat {
  public name: string;
  public color: string;

  constructor(name: string, color: string) {
    this.name = name;
    this.color = color;
  }

  public move(distanceMeter: number) : string {
    return `${this.name} moved ${distanceMeter}m.`;
  }

  public say() : string {
    return `Cat ${this.name} says meow`;
  }
}

Как видно, наш класс содержит в себе конструктор который принимает в себя значения имени и цвета а также пару методов. Этот класс и будет являтся нашим объектом тестирования (SUT - system under test).

Создадим в корне проекта, папку test, в которой будем хранить наши тесты.

Далее установим необходимые npm пакеты:

npm install --save-dev ts-node mocha @testdeck/mocha nyc chai @types/chai

Краткое описание пакетов:
ts-node - пакет для исполнения TypeScript и REPL в среде node.js.

mocha - популярный, гибкий тестовый фреймворк, позволяет разрабатывать тесты любого уровня. Будем использовать его как основу наших тестов. Вместе с ним используем @testdeck/mocha - имплементацию декоратора testdeck для Мокки, чтобы писать наши тесты в ООП стиле.

nyc - современный CLI популярной утилиты Istanbul, которая расчитывает текущее покрытие тестами кода.

chai - популярная библиотека для проверки утверждений(assertions), подходит для многих тестовых фреймворков. Мы же будет ее использовать в паре с Моккой. Добавим так же @types/chai чтобы наш чаи мог свободно работать с типами typescript'а

После установки всех необходимых пакетов, создадим в нашей папке test, файл tsconfig.json, в который добавим конфиги TS которые будут вызыватся отдельно для наших тестов.

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "baseUrl": "./",
    "module": "commonjs",
    "experimentalDecorators": true,
    "strictPropertyInitialization": false,
    "isolatedModules": false,
    "strict": false,
    "noImplicitAny": false,
    "typeRoots" : [
      "../node_modules/@types"
    ]
  },
  "exclude": [
    "../node_modules"
  ],
  "include": [
    "./**/*.ts"
  ]
}

В строчке include мы указываем включать все файлы в папке test с расширешием .ts

Затем создадим в корне проекта, файл register.js в котором опишем иснтрукции для ts-node, откуда запускать и транспилировать наши тесты.

const tsNode = require('ts-node');
const testTSConfig = require('./test/tsconfig.json');

tsNode.register({
  files: true,
  transpileOnly: true,
  project: './test/tsconfig.json'
});

Далее создадим там в корне, файл .mocharc.json, со следующим содержимым:

{
  "require": "./register.js",
  "reporter": "list"
}

И файл .nyrc.json с конфигом нашей утилиты для анализа тестового покрытия.

{
  "extends": "@istanbuljs/nyc-config-typescript",
  "include": [
    "src/**/*.ts"
  ],
  "exclude": [
    "node_modules/"
  ],
  "extension": [
    ".ts"
  ],
  "reporter": [
    "text-summary",
    "html"
  ],
  "report-dir": "./coverage"
}

После добавления всех необходимых конфигов, дерево нашего проекта будет выглядить следующим образом:

project
| node_modules
| src
| test
| --- tsconfig.json
| .mocharc.json
| .nyrc.json
| package.json
| register.js
| tsconfig.json

Теперь необходимо добавить скрипт запуска в package.json

"test": "nyc ./node_modules/.bin/_mocha 'tests/**/*.test.ts'"

С настройкой закончили, теперь можно написать первый тест

Пишем тесты

Создаем файл в папке ./test файл cat.unit.test.ts и пишем в нем следующий код:

import { Cat } from '../src/cat.module';
import { suite, test } from '@testdeck/mocha';
import * as _chai from 'chai';
import { expect } from 'chai';

_chai.should();
_chai.expect;

@suite class CatModuleTest {
  private SUT: Cat;
  private name: string;
  private color: string;

  before() {
    this.name = 'Tom';
    this.color = 'black';
    this.SUT = new Cat(this.name, this.color);
  }
}

Сейчас мы импортировали наш класс Cat из модуля cat.module.ts, добавили импорты необходимых тестовых библиотек, а также создали необходимые переменные чтобы инициализировать наш класс.

В секции before задали параметры необходимые для класс и создали инстанст класса Cat, на котором будут проходит тесты.

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

import { Cat } from '../src/cat.module';
import { suite, test } from '@testdeck/mocha';
import * as _chai from 'chai';
import { expect } from 'chai';

_chai.should();
_chai.expect;

@suite class CatModuleTest {
  private SUT: Cat;
  private name: string;
  private color: string;

  before() {
    this.name = 'Tom';
    this.color = 'black';
    this.SUT = new Cat(this.name, this.color);
  }

  @test 'Cat is created' () {
    this.SUT.name.should.to.not.be.undefined.and.have.property('name').equal('Tom');
  }
}

Запускаем тест командой npm test и получаем примерно такой результат в консоле:

Как видим покрытие у нас не полное, остались не протестированными строчки 11-15. Это как раз методы класса Cat move и say.

Дописываем еще два теста для этих методов и получаем в итоге такой файл с тестами:

import { Cat } from '../src/cat.module';
import { suite, test } from '@testdeck/mocha';
import * as _chai from 'chai';
import { expect } from 'chai';

_chai.should();
_chai.expect;

@suite class CatModuleTest {
  private SUT: Cat;
  private name: string;
  private color: string;

  before() {
    this.name = 'Tom';
    this.color = 'black';
    this.SUT = new Cat(this.name, this.color);
  }

  @test 'Cat is created' () {
    this.SUT.name.should.to.not.be.undefined.and.have.property('name').equal('Tom');
  }

  @test 'Cat move 10m' () {
    let catMove = this.SUT.move(10);
    expect(catMove).to.be.equal('Tom moved 10m.');
  }

  @test 'Cat say meow' () {
    expect(this.SUT.say()).to.be.equal('Cat Tom says meow');
  }
}

Снова запускаем наши тесты и видим что теперь класс Cat имеет полное тестовое покрытие.

Итог

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

PS: Написано по мотивам статьи How setting up unit test with TypeScript.

Источник: https://habr.com/ru/post/553234/


Интересные статьи

Интересные статьи

В данной статье мы рассмотрим уязвимости IoT систем и 3 сценария атаки на устройства данного типа. Но для начала стоит разобраться в терминах: что такое IoT? Что плохого ...
tl;dr: собираю образ Kali Linux для ARM-компьютера, в программе debootstrap, linux и u-boot. Если вы покупали какой-нибудь не очень популярный одноплатник, то могли столкнуться с о...
Персональные ДНК тесты открывают новые возможности для исследования своего организма. В этой статье я бы хотел привести пример, как самостоятельно найти информацию для интерпретации ...
Когда в Sports.ru понадобился свой WYSIWYG-редактор, мы решили сделать его на основе библиотеки ProseMirror. Одной из ключевых особенностей этого инструмента является модульность и широкие возмож...
С версии 12.0 в Bitrix Framework доступно создание резервных копий в автоматическом режиме. Задание параметров автоматического резервного копирования производится в Административной части на странице ...