Целью этого проекта является создание проекта TypeScript, который может выполнять все следующие действия:
jest
и ts-jest
для тестирования Я начинаю с того, что инициализирую это как проект npm
.
$ yarn init .
Затем я устанавливаю typescript
, jest
, ts-jest
и @types/jest
в качестве зависимостей:
$ yarn add -D typescript jest ts-jest @types/jest
На момент написания этой статьи это означает [email protected]
, [email protected]
и [email protected]
.
Далее мы инициализируем это как проект TypeScript, используя:
$ npx tsc --init .
Я хочу, чтобы мой код, сгенерированный TypeScript, хранился в ./lib
, и я хочу, чтобы были сгенерированы объявления. Итак, я настраиваю outDir
в tsconfig.json
как ./lib
.
Мой .gitignore
затем настроен следующим образом:
/node_modules
/lib
... в то время как мой .npmignore
просто:
/node_modules
По той же причине я удаляю значение по умолчанию для files
в tsconfig.json
и заменяю его следующим:
"exclude" : [ " node_modules " , " lib " ]
Для начала я создаю файл src/index.ts
, содержащий простую функцию:
export function sampleFunction ( x : string ) : string {
return x + x ;
}
Я также добавляю простой jest
тест. Я предпочитаю хранить свои тесты в совершенно отдельном месте, поэтому помещаю все свои тесты в __tests__
. Поэтому я создаю следующий тестовый пример в __tests__/base.spec.ts
:
import { sampleFunction } from "../src" ;
describe ( "This is a simple test" , ( ) => {
test ( "Check the sampleFunction function" , ( ) => {
expect ( sampleFunction ( "hello" ) ) . toEqual ( "hellohello" ) ;
} ) ;
} ) ;
На данный момент я хотел бы запустить этот тест. Но сначала мне нужно создать файл jest.config.js
для всех моих настроек jest
. При этом необходимо учитывать тот факт, что я использую ts-jest
и мои тесты хранятся в __tests__
. Итоговый файл выглядит так:
module . exports = {
transform : {
"^.+\.tsx?$" : "ts-jest" ,
} ,
testRegex : "(/__tests__/.*|(\.|/)(test|spec))\.(jsx?|tsx?)$" ,
moduleFileExtensions : [ "ts" , "tsx" , "js" , "jsx" , "json" , "node" ] ,
} ;
Затем я добавляю в package.json
следующие скрипты:
"scripts" : {
"compile" : " tsc " ,
"test" : " jest "
}
На этом этапе, если я запущу yarn test
, я получу именно то, на что надеялся:
PASS __tests__/base.spec.ts
This is a simple test
✓ Check the sampleFunction function (3ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Чтобы включить покрытие кода, я обновляю свой файл jest.config.js
до следующего содержания:
module . exports = {
transform : {
"^.+\.tsx?$" : "ts-jest" ,
} ,
testRegex : "(/__tests__/.*|(\.|/)(test|spec))\.(jsx?|tsx?)$" ,
moduleFileExtensions : [ "ts" , "tsx" , "js" , "jsx" , "json" , "node" ] ,
collectCoverage : true ,
} ;
Я также хочу обновить файлы .gitignore
и .npmignore
чтобы избежать контроля версий или публикации каталога coverage
созданного jest
.
На этом этапе я собираюсь начать вводить подмодули в свой проект. Поэтому я добавлю модули src/core
и src/utils
, чтобы сделать ситуацию немного более реалистичной. Затем я экспортирую содержимое обоих файлов, чтобы src/index.ts
выглядел так:
export * from "./core" ;
export * from "./utils" ;
Затем они импортируют определенные файлы, содержащие различные типы и функции. Вначале я создам очень простой набор типов для представления чрезвычайно простых выражений, состоящих только из литералов и двоичных операций +
, -
, *
и /
. Затем я могу написать несколько таких тестов:
import { evaluate , Expression } from "../src" ;
describe ( "Simple expression tests" , ( ) => {
test ( "Check literal value" , ( ) => {
expect ( evaluate ( { type : "literal" , value : 5 } ) ) . toBeCloseTo ( 5 ) ;
} ) ;
test ( "Check addition" , ( ) => {
let expr : Expression = {
type : "binary" ,
operator : "+" ,
left : {
type : "literal" ,
value : 5 ,
} ,
right : {
type : "literal" ,
value : 10 ,
} ,
} ;
expect ( evaluate ( expr ) ) . toBeCloseTo ( 15 ) ;
} ) ;
} ) ;
Все идет нормально. Но учтите, что если я действительно запущу эти тесты, я получу следующие результаты:
PASS __tests__/base.spec.ts
Simple expression tests
✓ Check literal value (4ms)
✓ Check addition
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.048s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files | 66.67 | 37.5 | 50 | 66.67 | |
src | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/core | 61.54 | 37.5 | 100 | 61.54 | |
functions.ts | 54.55 | 37.5 | 100 | 54.55 | 14,16,18,20,25 |
index.ts | 100 | 100 | 100 | 100 | |
src/utils | 66.67 | 100 | 0 | 66.67 | |
checks.ts | 50 | 100 | 0 | 50 | 2 |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|----------------|
Обратите внимание на отсутствие покрытия кода. Добавив еще несколько тестовых примеров вместе с комментариями /* istanbul ignore ... */
чтобы сообщить istanbul
, что можно безопасно игнорировать, мы получим:
PASS __tests__/base.spec.ts
Simple expression tests
✓ Check literal value (3ms)
✓ Check addition
✓ Check subtraction
✓ Check multiplication (1ms)
✓ Check division
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 1.353s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files | 100 | 100 | 100 | 100 | |
src | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/core | 100 | 100 | 100 | 100 | |
functions.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/utils | 100 | 100 | 100 | 100 | |
checks.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|----------------|
Теперь, если мы изменим тест так, чтобы он провалился, мы получим что-то вроде этого:
● Simple expression tests › Check division
expect(received).toBeCloseTo(expected, precision)
Expected value to be close to (with 2-digit precision):
1
Received:
2
19 | test("Check division", () => {
20 | let expr = bin("/", 10, 5);
> 21 | expect(evaluate(expr)).toBeCloseTo(1);
22 | });
23 | });
24 |
at Object.<anonymous> (__tests__/base.spec.ts:21:32)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 4 passed, 5 total
Snapshots: 0 total
Time: 1.535s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files | 100 | 100 | 100 | 100 | |
src | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/core | 100 | 100 | 100 | 100 | |
functions.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/utils | 100 | 100 | 100 | 100 | |
checks.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|----------------|
Обратите внимание, что дорожка стека правильная. Это указывает на проблему в коде TypeScript .
Напомним, что мы добавили скрипт compile
в наш package.json
. Мы можем скомпилировать код с помощью yarn compile
. При этом мы видим, что каталог lib
заполнен двумя подкаталогами: src
и __tests__
.
Однако, если мы заглянем в эти каталоги, мы обнаружим, что они содержат только сгенерированный код Javascript. Они не включают определения типов. Чтобы генерировать определения типов (файлы .d.ts
), чтобы другие пользователи TypeScript могли извлечь выгоду из всей информации о типах, которую мы добавили в наш код, мы должны установить для поля declaration
в нашем файле tsconfig.json
значение true
.
Также обратите внимание: чтобы другие могли использовать этот пакет в качестве модуля NPM, вам необходимо установить для main
поля в package.json
значение lib/src/index.js
. Кроме того, чтобы другие могли получить доступ к типам в этом модуле, нам также необходимо установить для поля typings
в package.json
значение lib/src/index.d.ts
. Другими словами,
"main" : " lib/src/index.js " ,
"typings" : " lib/src/index.d.ts " ,
При правильной настройке мы можем запустить сеанс node
и импортировать наш новый пакет:
$ node
> var me = require( " . " )
undefined
> me
{ evaluate: [Function: evaluate],
assertNever: [Function: assertNever] }
>
Теперь обязательно обновите jest.config.js
, включив в него следующий параметр, иначе jest
начнет сопоставлять код в каталоге lib/__tests__
:
testPathIgnorePatterns: ["/lib/", "/node_modules/"],
Наконец мы подошли к отладке. Я использую Visual Studio Code, поэтому покажу, как заставить его работать с отладкой. Некоторая часть этой информации вполне может быть переведена в другие IDE.
В VSCode мы можем перейти на боковую панель отладки. Изначально рядом с кнопкой «играть» будет надпись «Нет конфигурации». При нажатии на нее открывается раскрывающееся меню с опцией «Добавить конфигурацию...».
Как бы я ни любил TypeScript, на самом деле отладка — это его ахиллесова пята. Дело не в том, что вы не умеете отлаживать, а в том, что просто сложно начать работать. Если вы выберете «Добавить конфигурацию...», а затем «Node.js», вы увидите несколько предварительных конфигураций, включая одну для mocha
. Но jest
нет. Поэтому вам придется создать собственный файл .vscode/launch.json
. К счастью, страница jest
предлагает создать файл .vscode/launch.json
, который выглядит следующим образом:
{
"version" : " 0.2.0 " ,
"configurations" : [
{
"name" : " Debug Jest Tests " ,
"type" : " node " ,
"request" : " launch " ,
"runtimeArgs" : [ " --inspect-brk " , " ${workspaceRoot}/node_modules/.bin/jest " , " --runInBand " ],
"console" : " integratedTerminal " ,
"internalConsoleOptions" : " neverOpen "
}
]
}
Я был приятно удивлен, обнаружив, что могу не только запускать тесты и получать покрытие кода, как обычно, но также устанавливать точки останова как в тестах ( т. е. в __tests__/base.spec.ts
), так и в коде ( например, src/core/functions.ts
), и отладчик их найдет.
Обратите внимание, что я тестировал все это на Node 8.x. Я видел проблемы с отладкой с использованием Node 6.x, поэтому, если у вас возникли проблемы, вы можете рассмотреть возможность обновления (или позволить, если вам удастся это исправить, отправить PR для этого README с объяснением исправления).