L'objectif de ce projet est de créer un projet TypeScript capable d'effectuer toutes les opérations suivantes :
jest
et ts-jest
pour les tests Je commence par initialiser ceci en tant que projet npm
.
$ yarn init .
Ensuite, j'installe typescript
, jest
, ts-jest
et @types/jest
comme dépendances :
$ yarn add -D typescript jest ts-jest @types/jest
Au moment d'écrire ces lignes, cela signifie [email protected]
, [email protected]
et [email protected]
.
Ensuite, nous initialisons cela en tant que projet TypeScript en utilisant :
$ npx tsc --init .
Je veux que mon code généré par TypeScript soit stocké dans ./lib
et je veux que les déclarations soient générées. Donc, je configure outDir
dans tsconfig.json
pour être ./lib
.
Mon .gitignore
est alors configuré pour être :
/node_modules
/lib
... alors que mon .npmignore
est juste :
/node_modules
Pour la même raison, je supprime la valeur par défaut files
dans tsconfig.json
et la remplace par :
"exclude" : [ " node_modules " , " lib " ]
Pour commencer, je crée un src/index.ts
qui contient une fonction simple :
export function sampleFunction ( x : string ) : string {
return x + x ;
}
J'ajoute également un simple test jest
. Je préfère conserver mes tests dans un emplacement complètement séparé, je mettrai donc tous mes tests dans __tests__
. Je crée donc le scénario de test suivant dans __tests__/base.spec.ts
:
import { sampleFunction } from "../src" ;
describe ( "This is a simple test" , ( ) => {
test ( "Check the sampleFunction function" , ( ) => {
expect ( sampleFunction ( "hello" ) ) . toEqual ( "hellohello" ) ;
} ) ;
} ) ;
À ce stade, j'aimerais exécuter ce test. Mais je dois d’abord créer un fichier jest.config.js
pour tous mes paramètres jest
. Cela doit prendre en compte le fait que j'utilise ts-jest
et le fait que mes tests sont stockés dans __tests__
. Le fichier résultant ressemble donc à ceci :
module . exports = {
transform : {
"^.+\.tsx?$" : "ts-jest" ,
} ,
testRegex : "(/__tests__/.*|(\.|/)(test|spec))\.(jsx?|tsx?)$" ,
moduleFileExtensions : [ "ts" , "tsx" , "js" , "jsx" , "json" , "node" ] ,
} ;
J'ajoute ensuite les scripts suivants à package.json
:
"scripts" : {
"compile" : " tsc " ,
"test" : " jest "
}
À ce stade, si je lance yarn test
, j'obtiens exactement ce que j'espérais :
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
Pour activer la couverture de code, je mets à jour mon fichier jest.config.js
vers :
module . exports = {
transform : {
"^.+\.tsx?$" : "ts-jest" ,
} ,
testRegex : "(/__tests__/.*|(\.|/)(test|spec))\.(jsx?|tsx?)$" ,
moduleFileExtensions : [ "ts" , "tsx" , "js" , "jsx" , "json" , "node" ] ,
collectCoverage : true ,
} ;
Je souhaiterai également mettre à jour mes fichiers .gitignore
et .npmignore
pour éviter le contrôle de version ou la publication du répertoire coverage
généré par jest
.
À ce stade, je vais commencer à introduire des sous-modules dans mon projet. Je vais donc ajouter un module src/core
et un module src/utils
juste pour rendre les choses un peu plus réalistes. Ensuite, j'exporterai le contenu des deux afin que src/index.ts
ressemble à ceci :
export * from "./core" ;
export * from "./utils" ;
Ceux-ci importent ensuite des fichiers spécifiques contenant différents types et fonctions. Dans un premier temps, je vais créer un ensemble de types très simples pour représenter des expressions extrêmement simples avec uniquement des littéraux et les opérations binaires +
, -
, *
et /
. Ensuite, je peux écrire quelques tests comme ceux-ci :
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 ) ;
} ) ;
} ) ;
Jusqu'ici, tout va bien. Mais notez que si j'exécute réellement ces tests, j'obtiens ces résultats :
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 | |
---------------|----------|----------|----------|----------|----------------|
Notez le manque de couverture de code. En ajoutant quelques cas de test supplémentaires ainsi que quelques /* istanbul ignore ... */
commentaires pour faire savoir istanbul
ce qu'il peut ignorer en toute sécurité, nous arrivons à :
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 | |
---------------|----------|----------|----------|----------|----------------|
Maintenant, si nous modifions un test pour le faire échouer, nous obtenons quelque chose comme ceci :
● 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 | |
---------------|----------|----------|----------|----------|----------------|
Notez que la piste de pile est correcte. Cela souligne le problème dans le code TypeScript .
Rappelons que nous avons ajouté un script compile
à notre package.json
. Nous pouvons compiler le code avec yarn compile
. Ce faisant, nous voyons que le répertoire lib
est rempli de deux sous-répertoires, src
et __tests__
.
Cependant, si nous regardons dans ces répertoires, nous constaterons qu’ils incluent uniquement le code Javascript généré. Ils n'incluent pas de définitions de type. Afin de générer des définitions de type (fichiers .d.ts
) afin que les autres utilisateurs de TypeScript puissent bénéficier de toutes les informations de type que nous avons ajoutées à notre code, nous devons définir le champ declaration
de notre fichier tsconfig.json
sur true
.
Notez également que pour que d'autres puissent utiliser ce package comme module NPM, vous devez définir le champ main
de package.json
sur lib/src/index.js
. De plus, pour que d'autres puissent accéder aux types de ce module, nous devons également définir le champ typings
dans package.json
sur lib/src/index.d.ts
. Autrement dit,
"main" : " lib/src/index.js " ,
"typings" : " lib/src/index.d.ts " ,
S'il est correctement configuré, nous pouvons alors lancer une session node
et importer notre nouveau package :
$ node
> var me = require( " . " )
undefined
> me
{ evaluate: [Function: evaluate],
assertNever: [Function: assertNever] }
>
Assurez-vous maintenant de mettre à jour votre jest.config.js
pour inclure le paramètre suivant, sinon jest
commencera à correspondre au code dans le répertoire lib/__tests__
:
testPathIgnorePatterns: ["/lib/", "/node_modules/"],
Enfin, nous arrivons au débogage. J'utilise Visual Studio Code, je vais donc vous montrer comment y faire fonctionner le débogage. Certaines de ces informations peuvent très bien être transposées dans d’autres IDE.
Dans VSCode, nous pouvons accéder à la barre latérale de débogage. Initialement, à côté du bouton « play » se trouveront les mots « No Configuration ». En cliquant dessus, un menu déroulant apparaît avec une option "Ajouter une configuration...".
Même si j'aime TypeScript, le débogage est vraiment son talon d'Achille. Ce n’est pas que vous ne pouvez pas déboguer, c’est simplement qu’il est difficile de travailler. Si vous sélectionnez "Ajouter une configuration..." puis "Node.js", vous verrez plusieurs préconfigurations dont une pour mocha
. Mais il n'y en a pas pour jest
. Vous devrez donc créer votre propre fichier .vscode/launch.json
. Heureusement, la page jest
vous suggère de créer un fichier .vscode/launch.json
qui ressemble à ceci :
{
"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 "
}
]
}
J'ai été agréablement surpris de constater que je pouvais non seulement exécuter mes tests et obtenir une couverture de code comme d'habitude, mais également définir des points d'arrêt dans les deux tests ( c'est-à-dire dans __tests__/base.spec.ts
) ainsi que dans le code ( par exemple, src/core/functions.ts
) et le débogueur les trouvera.
Notez que j'ai testé tout cela sur Node 8.x. J'ai vu des problèmes avec le débogage à l'aide de Node 6.x, donc si vous rencontrez des problèmes, vous pouvez envisager de mettre à niveau (ou laisser, si vous parvenez à le résoudre, soumettre un PR pour ce README expliquant le correctif).