Тест Node Native Test Runner

Это август 2023 года, версия 20.5.1 была выпущена пять дней назад, и теперь мы можем протестировать стабильную версию этой фантастической новой встроенной функции, сказочного TestRunner.

Просто краткое введение, тесты, что это такое, что они едят, где они живут?!?!

Мы используем модульные тесты, чтобы проверить предсказуемое поведение, чтобы увидеть, все ли после изменения нашей кодовой базы работает так, как мы ожидаем, это в основном способ защитить нас от самих себя, и когда у нас есть зависимости от внешних ресурсов, таких как API , Сервис, Взаимодействие с человеком (нажатие «ОК» или что-то маленькое), мы издеваемся над ними всеми, мы доверяем им, потому что у нас нет другого выбора.

Теперь с помощью быстрого и небольшого запроса ChatGPT мы можем перечислить несколько фреймворков для тестирования JavaScript:

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

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

export default function parseParams(baseObject, params = new URLSearchParams(window.location.search), arrSeparator = ',') {
  if(!baseObject || typeof(baseObject) !== 'object') throw new Error('baseObject property must be declared and a object');
  if(!params?.get) throw new Error('search params must be initialized like "new URLSearchParams(query)"');
  if(!arrSeparator) throw new Error('arr separator must not be empty');

  const obj = {};

  for(const key in baseObject) {    
    const param = params.get(key);

    if(param !== null)  
      obj[key] = parseByType(param, baseObject[key], arrSeparator);
    else 
      obj[key] = null;
  }

  return obj;
}

export function parseByType (variable, property, arrSeparator) {
  if (typeof(variable) !== 'string') throw new Error('variable and type must be strings');

  let result;

  switch(typeof(property)){
    case 'string':
      result = variable;
      break;
    case 'number':
      result = +variable;
      break;
    case 'object':
      if(Array.isArray(property))
        result = variable.split(arrSeparator);
      break;
    case 'boolean':
      result = stringToBoolean(variable);
      break;
    default:
      console.log(`type ${typeof(property)} not mapped`)
  }

  return result;
}


export function stringToBoolean (stringValue) {
  switch(stringValue?.toLowerCase()?.trim()){
      case "true": 
      case "yes": 
      case "1": 
        return true;

      case "false": 
      case "no": 
      case "0": 
      case null: 
      case undefined:
        return false;

      default: 
        return false;
  }
}

это простой urlQueryParser, опубликованный здесь, но простой он или нет, нам нужно его протестировать!

И еще один быстрый взгляд на то, как он будет тестироваться


describe('Main parseParams method', () => {
  test('should throw when there is no object passed', () => {
    assert.throws(() => {
      const result = parseParams('not an object', new URLSearchParams(''));
    },
    {
      message: 'baseObject property must be declared and a object'
    })
  });

  test('should throw when there is no URLSearchParams and is passed as null', () => {
    assert.throws(() => {
      const result = parseParams({ item1: 0}, null, null);
      
    }, 
    {
      message:  'search params must be initialized like "new URLSearchParams(query)"'
    });
  });

  test('should throw when the array separator is null', () => {
    assert.throws(() => {
      const result = parseParams({ item1: 0}, new URLSearchParams(''), null);
    }, 
    {
      message: 'arr separator must not be empty'
    });  
  });
});


describe('parseByType method', () => {
  test('should throw when the variable type is not string', () => {
    assert.throws(() => {
      parseByType(true, 'name', ',');
    },
    {
      message: 'variable and type must be strings'
    });
  });

  test('should test the parsed value from query string value', () => {
    const toBeParsed = [
      { value: 'toString', type: 'string', expected: 'toString' },
      { value: 'yEs', type: false, expected: true, },
      { value: '100', type: 0, expected: 100 },
      { value: 'var1,var2', type: [], expected: [ 'var1', 'var2' ] },
    ]
  
    for(const toParse of toBeParsed) {
      assert.deepEqual(
        parseByType(toParse.value, toParse.type, ','), 
        toParse.expected, 
        `type (${typeof(toParse.type)}) is failing`
      );
    }
  });
});


describe('stringToBoolean method', () => {
  test('test variations of true values', () => {
    const trueStrings = ['yes', 'YeS', '1', 'true', 'TrUe'];
  
    for(const string of trueStrings) {
      assert.strictEqual(stringToBoolean(string), true);
    }
  });
  
  test('test variations of false values', () => {
    const falseStrings = ['no', 'nO', '0', undefined, null];
  
    for(const string of falseStrings) {
      assert.strictEqual(stringToBoolean(string), false);
    }  
  });
});

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

У нас есть 3 тестовых движка (Mocha, Jest и Node Test Runner), давайте с ними разберемся и тоже настроим.

Мокко

Это просто, не нужно вообще беспокоиться о ECMAScript или CommonJS, простое прямое npm i -D mocha сделает свое дело.

В верхней части файла давайте напрямую импортируем все необходимые нам инструменты тестирования.

import { describe, test } from 'mocha';
import * as assert from 'node:assert';

на Mocha мы можем использовать нативную node:assert или даже несколько других библиотек, таких как should.js expect.js chai , вы можете найти список здесь mocha docs.

чтобы он был небольшим, я буду использовать нативный, который доступен с бета-версии Node.js, как вы можете найти здесь.

Теперь package.json

{
  "name": "nodejs-unity-tests-benchmark",
  "version": "0.1.0",
  "license": "ISC",
  "engines": {
    "node": "20.5.1"
  },
  "description": "",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test:mocha": "mocha index.mocha.spec.js"
  },
  "dependencies": {
    "mocha": "^10.2.0"
  }
}

Все готово, посмотрим, как будет выглядеть результат

7 мс, это выглядит много, мы углубимся в это позже в нашем обзоре результатов.

Шутка

Этот требует немного большей сложности, он основан на CommonJS, поэтому нам нужно его немного настроить.

для модулей ES нам нужно следовать этому документу, так как я использую Windows, я сделаю больший путь с npm i -D jest cross-env

Снова импортируйте правильный набор тестов

import { describe, test, expect } from '@jest/globals'

И настройте package.json

{
  "name": "nodejs-unity-tests-benchmark",
  "version": "0.1.0",
  "license": "ISC",
  "engines": {
    "node": "20.5.1"
  },
  "description": "",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test:mocha": "mocha index.mocha.spec.js",
    "test:jest": "cross-env NODE_OPTIONS=--experimental-vm-modules npx jest index.jest.spec.js"
  },
  "dependencies": {
    "cross-env": "^7.0.3",
    "jest": "^29.6.2",
    "mocha": "^10.2.0"
  }
}

Теперь предварительный просмотр результата

Node.js

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

import { describe, test } from 'node:test';
import * as assert from 'node:assert';

на Package.json

{
  "name": "nodejs-unity-tests-benchmark",
  "version": "0.1.0",
  "license": "ISC",
  "engines": {
    "node": "20.5.1"
  },
  "description": "",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test:node": "node index.node.spec.js",
    "test:mocha": "mocha index.mocha.spec.js",
    "test:jest": "cross-env NODE_OPTIONS=--experimental-vm-modules npx jest index.jest.spec.js"
  },
  "dependencies": {
    "cross-env": "^7.0.3",
    "jest": "^29.6.2",
    "mocha": "^10.2.0"
  }
}

результат

Обзор результатов

Удивительно, теперь у нас есть с чем работать, запуская их все по 10 раз, я закончил с этим

Совершенно несправедливо, Mocha показывал результаты в 20 раз больше, давайте немного изменим Mocha и запустим все это параллельно, "test:mocha": "mocha index.mocha.spec.js --parallel" , теперь результаты будут более удовлетворительными:

Удивительно, теперь мы можем видеть вещи!

Подведем итоги!

1 – Node.js показывает среднее время 0,1388 мс

2 – Мокко с 0,1694 мс

3 – Jest почти в два раза быстрее двух других, 0,2514 мс

Имея это в виду, первый приз, очевидно, достается Node.js за производительность, но если подумать, это было несправедливо в любом случае, поскольку Node.js поставляется с этим прямо из коробки, нам не нужно ничего устанавливать. , это просто действие его использования на самом деле. Очевидно, что это был довольно плохой тест без какой-либо сложности, без дополнительных функций, таких как шпионы или моки, без асинхронности или промисов, и даже не был показан простой отчет о покрытии кода.

За и против

Минусы:

  • Mocha: требуется простая конфигурация o --parallel, чтобы работать значительно быстрее, и это нормально, так как вам нужно сделать это только один раз;
  • Шутка: он изначально совместим с CommonJS, поэтому нам нужно настроить переменные среды узла, а для Windows нам нужно загрузить библиотеку cross-env, на самом деле не очень хороший опыт;
  • Нод: Честно говоря, я не нашел ничего плохого.

Плюсы:

  • Mocha: у него уже есть инструменты покрытия кода, можно фильтровать тесты по описаниям тестов с флагом --grep, здесь, для Node.js не нашел
  • Шутка: у нас он изначально настроен для многих фреймворков, если вы используете Next.js, (устарело) Создать приложение React, возможно, вы найдете его и в других фреймворках.
  • Node: встроенная функция без зависимостей, быстрее, без настройки.

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