web-dev-qa-db-fra.com

tests unitaires de fonctions privées avec mocha et node.js

J'utilise mocha afin de tester à l'unité une application écrite pour node.js

Je me demande s'il est possible de regrouper des fonctions de test qui n'ont pas été exportées dans un module.

Exemple:

J'ai beaucoup de fonctions définies comme ceci dans foobar.js

function private_foobar1(){
    ...
}

function private_foobar2(){
    ...
}

et quelques fonctions exportées en public:

exports.public_foobar3 = function(){
    ...
}

Le scénario de test est structuré comme suit:

describe("private_foobar1", function() {
    it("should do stuff", function(done) {
        var stuff = foobar.private_foobar1(filter);
        should(stuff).be.ok;
        should(stuff).....

Évidemment, cela ne fonctionne pas, puisque private_foobar1 n'est pas exporté.

Quelle est la bonne façon de tester des méthodes privées? Mocha a-t-il des méthodes intégrées pour le faire?

120
fstab

Si la fonction n'est pas exportée par le module, elle ne peut pas être appelée par le code de test en dehors du module. Cela est dû au fonctionnement de JavaScript et Mocha ne peut à lui seul contourner ce problème.

Dans les rares cas où j'ai déterminé que le test d'une fonction privée est la bonne chose à faire, ce que j'ai fait est de définir une variable d'environnement que mon module vérifie pour déterminer si elle est exécutée dans une configuration de test ou non. S'il fonctionne dans la configuration de test, il exporte des fonctions supplémentaires que je peux ensuite appeler pendant les tests.

Le mot "environnement" est utilisé vaguement ici. Cela pourrait signifier vérifier process.env ou quelque chose d’autre qui peut communiquer avec le module "vous êtes en train d’être testé". Les cas où j'ai dû faire cela se trouvaient dans un environnement RequireJS et j'ai utilisé module.config dans ce but.

58
Louis

Découvrez le module rewire . Il vous permet d'obtenir (et de manipuler) des variables et des fonctions privées dans un module.

Donc, dans votre cas, l'utilisation serait quelque chose comme:

var rewire = require('rewire'),
    foobar = rewire('./foobar'); // Bring your module in with rewire

describe("private_foobar1", function() {

    // Use the special '__get__' accessor to get your private function.
    var private_foobar1 = foobar.__get__('private_foobar1');

    it("should do stuff", function(done) {
        var stuff = private_foobar1(filter);
        should(stuff).be.ok;
        should(stuff).....
175
barwin

Voici un très bon workflow pour tester vos méthodes privées expliqué par Philip Walton, un ingénieur de Google sur son blog.

Principe

  • Écrivez votre code normalement
  • Liez vos méthodes privées à l’objet dans un bloc de code séparé, marquez-le par un _ par exemple
  • Entourez ce bloc de code par les commentaires de début et de fin

Ensuite, utilisez une tâche de construction ou votre propre système de construction (par exemple, grunt-strip-code) pour supprimer ce bloc pour les générations de production.

Vos versions de test ont accès à votre API privée, contrairement à vos versions de production.

Fragment

Écrivez votre code comme ceci:

var myModule = (function() {

  function foo() {
    // private function `foo` inside closure
    return "foo"
  }

  var api = {
    bar: function() {
      // public function `bar` returned from closure
      return "bar"
    }
  }

  /* test-code */
  api._foo = foo
  /* end-test-code */

  return api
}())

Et vos tâches comme ça

grunt.registerTask("test", [
  "concat",
  "jshint",
  "jasmine"
])
grunt.registerTask("deploy", [
  "concat",
  "strip-code",
  "jshint",
  "uglify"
])

Plus profond

Dans un article ultérieur , il explique le "pourquoi" de "tester les méthodes privées"

21
Rémi Becheras

Si vous préférez rester simple, exportez également les membres privés, mais clairement séparés de l'API publique avec une convention, par exemple. préfixez-les avec un _ ou les imbriquer sous un seul objet privé.

var privateWorker = function() {
    return 1
}

var doSomething = function() {
    return privateWorker()
}

module.exports = {
    doSomething: doSomething,
    _privateWorker: privateWorker
}
20
famousgarkin

J'ai créé un paquet npm à cet effet qui pourrait vous être utile: require-from

Fondamentalement, vous exposez des méthodes non publiques en:

module.testExports = {
    private_foobar1: private_foobar1,
    private_foobar2: private_foobar2,
    ...
}

note: testExports peut être n’importe quel nom valide, sauf exports bien sûr.

Et à partir d'un autre module:

var requireFrom = require('require-from');
var private_foobar1 = requireFrom('testExports', './path-to-module').private_foobar1;
5
DEADB17

J'ai ajouté une fonction supplémentaire que je nomme Internal () et renvoie toutes les fonctions privées à partir de là. Cette fonction Internal () est ensuite exportée. Exemple:

function Internal () {
  return { Private_Function1, Private_Function2, Private_Function2}
}

// Exports --------------------------
module.exports = { PublicFunction1, PublicFunction2, Internal }

Vous pouvez appeler les fonctions internes comme ceci:

let test = require('.....')
test.Internal().Private_Function1()

J'aime mieux cette solution parce que:

  • une seule fonction Internal () est toujours exportée. Cette fonction Internal () est toujours utilisée pour tester les fonctions privées.
  • C'est simple à mettre en place
  • Faible impact sur le code de production (une seule fonction supplémentaire)
4

J'ai suivi @ barwin répondre et vérifier comment les tests unitaires peuvent être effectués avec rewire module. Je peux confirmer que cette solution fonctionne simplement.

Le module devrait être requis en deux parties: une publique et une privée. Pour les fonctions publiques, vous pouvez le faire de manière standard:

const { public_foobar3 } = require('./foobar');

Pour une portée privée:

const privateFoobar = require('rewire')('./foobar');
const private_foobar1 = privateFoobar .__get__('private_foobar1');
const private_foobar2 = privateFoobar .__get__('private_foobar2');

Pour en savoir plus sur le sujet, j'ai créé un exemple concret de test de module complet, qui inclut une portée privée et publique.

Pour plus d'informations, je vous encourage à consulter l'article ( https://medium.com/@macsikora/how-to-test-private-functions-of-es6-module-fb8c1345b25f ) décrivant en détail le sujet, il comprend des exemples de code.

1
Maciej Sikora

Je sais que ce n'est pas nécessairement la réponse que vous recherchez, mais ce que j'ai trouvé, c'est que la plupart du temps, si une fonction privée vaut la peine d'être testée, elle mérite d'être classée dans son propre fichier.

Par exemple. au lieu d'avoir des méthodes privées dans le même fichier que les méthodes publiques, comme ceci ...

src/thing/PublicInterface.js


function helper1 (x) {
    return 2 * x;
}

function helper2 (x) {
    return 3 * x;
}

export function publicMethod1(x) {
    return helper1(x);
}

export function publicMethod2(x) {
    return helper1(x) + helper2(x);
}

... vous le divisez comme ceci:

src/thing/PublicInterface.js

import {helper1} from './internal/helper1.js';
import {helper2} from './internal/helper2.js';

export function publicMethod1(x) {
    return helper1(x);
}

export function publicMethod2(x) {
    return helper1(x) + helper2(x);
}

src/thing/internal/helper1.js

export function helper1 (x) {
    return 2 * x;
}

src/thing/internal/helper2.js

export function helper2 (x) {
    return 3 * x;
}

De cette façon, vous pouvez facilement tester helper1 et helper2 _ tel quel, sans utiliser Rewire et d’autres méthodes "magiques" (qui, j’ai trouvé, ont leurs propres problèmes au débogage ou lorsque vous essayez de passer à TypeScript, sans parler de la moindre compréhensibilité pour les nouveaux collègues). Et être dans un sous-dossier appelé internal, ou quelque chose du genre, aidera à éviter leur utilisation accidentelle dans des endroits non souhaités.


P.S .: Un autre problème courant avec les méthodes "privées" est que si vous voulez tester publicMethod1 et publicMethod2 et moquez les assistants, encore une fois, vous avez normalement besoin de quelque chose comme Rewire pour le faire. Cependant, s’ils se trouvent dans des fichiers séparés, vous pouvez utiliser Proxyquire pour le faire, ce qui, contrairement à Rewire, ne nécessite aucune modification de votre processus de construction, est facile à lire et à déboguer, et fonctionne bien même avec TypeScript.

0
Dániel Kis-Nagy

Pour rendre les méthodes privées disponibles pour les tests, je fais ceci:

const _myPrivateMethod: () => {};

const methods = {
    myPublicMethod1: () => {},
    myPublicMethod2: () => {},
}

if (process.env.NODE_ENV === 'test') {
    methods._myPrivateMethod = _myPrivateMethod;
}

module.exports = methods;
0
MFB