Sinon: Idée de jalon(s) futur(s)

Créé le 13 sept. 2017  ·  31Commentaires  ·  Source: sinonjs/sinon

Fond

sinon.stub / sandbox.stub est devenu l'évier de cuisine de
comportement configurable avec des problèmes souvent difficiles à trouver et à résoudre sans régressions.

Je pense que la cause profonde des difficultés est qu'il stub a beaucoup trop de responsabilités.

De plus, stub a également une utilisation problématique due au fait que le comportement est défini après sa création et peut être redéfini plusieurs fois.

var myStub;

beforeEach(function(){
    myStub = sinon.stub().resolves('apple pie :)');
});

// several hundred lines of tests later
myStub = sinon.stub().rejects('no more pie :(');

// several hundred lines of tests later
// what behaviour does myStub currently have? Can you tell without 
// reading the entire file?
// can you safely change the behaviour without affecting tests further 
// down in the file?

Et puis il y a les scénarios les plus déroutants

var myStub = sinon.stub()
                .withArgs(42)
                .onThirdCall()
                .resolves('apple pie')
                .rejects('no more pie')

Qu'est-ce que ça fait même?

Au lieu de continuer à ajouter plus de responsabilités à stub , je propose que nous introduisions plutôt de nouveaux membres à sinon , qui ont une portée beaucoup plus restreinte .

Le plus important auquel je puisse penser serait un substitut immuable pour une fonction.

Nous pouvons alors déterminer plus tard ce que nous allons faire concernant les propriétés (en tant que membre distinct, nouveau et à responsabilité unique).

sinon.fake

Un fake (la valeur de retour de l'appel de sinon.fake ) est un Function pur et immuable . Il fait une chose, et une seule chose. Il a le même comportement à chaque appel. Contrairement à stub , son comportement ne peut pas être redéfini. Si vous avez besoin d'un comportement différent, créez un nouveau fake .

Responsabilité unique

Un faux peut avoir l'une de ces responsabilités

  • résoudre un Promise en une valeur
  • rejeter un Promise vers un Error
  • retourner une valeur
  • jeter un Error
  • rendement(s) valeur(s) à un rappel

Si vous voulez/avez besoin d'effets secondaires et que vous voulez toujours l'interface d'espionnage, utilisez la fonction réelle, utilisez un stub ou créez une fonction personnalisée

sinon.replace(myObject, myMethod, sandbox.spy(function(args) {
    someFunctionWithSideEffects(args);
});

Lance généreusement les erreurs

Sera généreux avec les erreurs de lancement lorsque l'utilisateur essaie de les créer/utiliser de manière non prise en charge.

// will throw TypeError when `config` argument has more than one property
const fake = sinon.fake({
    resolves: true, 
    returns: true
});

Utilise l'API d'espionnage

Sauf pour .withArgs , car cela viole l'immuabilité

Idées d'utilisation

// will return a Promise that resolves to 'apple pie'
var fake = sinon.fake({resolves: 'apple pie'})

// will return a Promise that rejects with the provided Error, or 
// creates a generic Error using the input as message
var fake = sinon.fake({rejects: new TypeError('no more pie')});
var fake = sinon.fake({rejects: 'no more pie'});

// returns the value passed
var fake = sinon.fake({returns: 'apple pie'});

// throws the provided Error, or creates a generic Error using the 
// input as message
var fake = sinon.fake({throws: new RangeError('no more pie')});
var fake = sinon.fake({throws: 'no more pie'});

// replace a method with a fake
var fake = sinon.replace(myObject, 'methodName', sandbox.fake({
    returns: 'apple pie'
}))
// .. or use the helper method, which will use `sandbox.replace` and `
// sinon.fake`
var fake = sinon.setFake(global, 'methodName', {
    returns: 'apple pie'
});

// create an async fake
var asyncFake = sinon.asyncFake({
    returns: 'apple pie'
});

Synonymes

Je ne sais pas si fake est le meilleur nom à utiliser ici, mais je pense que nous devrions essayer de nous en tenir à la convention d'utilisation des noms et de ne pas nous égarer dans les adjectifs ou les verbes.

Modifications de l'API proposées

Utiliser un bac à sable par défaut

C'est quelque chose que j'envisage depuis un certain temps, pourquoi ne pas créer un bac à sable par défaut ? Si les utilisateurs ont besoin de bacs à sable séparés, ils peuvent toujours en créer.

Nous devrions créer un bac à sable par défaut utilisé pour toutes les méthodes exposées via sinon.* .
Cela signifie que sinon.stub deviendra le même que sandbox.stub , ce qui supprimera la limitation de pouvoir stub des propriétés en utilisant sinon.stub .

sandbox.replace

Créez sandbox.replace et utilisez-le pour toutes les opérations qui remplacent n'importe quoi n'importe où. Exposez ceci comme sinon.replace et utilisez le bac à sable par défaut lorsqu'il est utilisé de cette façon.

Cela devrait probablement avoir une validation d'entrée sérieuse, donc il ne remplacera que les fonctions par des fonctions, les accesseurs par des accesseurs, etc.

Feature Request Improvement Needs investigation pinned

Commentaire le plus utile

OK, je pense que nous avons une compréhension similaire 👍

Juste pour réitérer, au cas où nous aurions raté quelque chose et pour que les autres contributeurs aient la même compréhension.

TL;DR

  • tous les remplacements seront effectués par un nouvel utilitaire : sandbox.replace (il vit actuellement dans stub )
  • sinon aura un bac à sable par défaut, permettant sinon.reset et sinon.restore (devrions-nous simplement les fusionner ?)
  • sinon.fake - un remplacement immuable et programmable pour les fonctions qui enregistrent tous les appels
  • sinon.spy
  • sinon.stub
// effectively a spy that has no target
const fake = sinon.fake()

// spy on a function
const fake = sinon.fake(console.log);
const fake = sinon.fake(function() { return 'apple pie'; });

// a shorthand construction of fake with behaviour
const fake = sinon.fake({
    returns: 'apple pie'
});

// replacing an existing function with a fake
var fakeLog = sinon.fake();
sandbox.replace(console, 'log', fakeLog);

À ce stade, la proposition ne traite que de Function . Nous devons réfléchir à ce qu'il faut faire des propriétés non fonctionnelles et des accesseurs. À tout le moins, nous devrions voir si nous pouvons limiter sandbox.replace pour n'autoriser que les remplacements sains.

Tous les 31 commentaires

Ping @sinonjs/core

Bonnes suggestions, Morgane. Merci d'avoir soulevé cette question. Je pense aussi que l'API stub est une confusion et j'aime toutes vos suggestions. Voici quelques réflexions :

sinon.faux

Je suis d'accord que l'immuabilité est la clé ici. Cependant, nous pourrions autoriser certains cas d'utilisation "sains" qui sont actuellement possibles avec les stubs.

Par exemple, il pourrait s'agir d'un cas d'utilisation valide pour céder et renvoyer :

sinon.fake({
  yields: [null, 42],
  returns: true
})

Nous pouvons vérifier ce qui a du sens et ce qui ne l'est pas.

De plus, si nous prenons en charge callsThrough: true en tant que configuration (qui n'est pas valide en combinaison avec l'une des propriétés de comportement), les nouveaux faux pourraient également être utilisés à la place de l'API "espion". Ce serait plus explicite que d'apprendre ce que "espion" et "stub" signifient dans Sinon-speak 😄

Utiliser un bac à sable par défaut

Bien que j'aime cette idée, cela signifie qu'appeler sinon.restore() après un test pourrait annuler certains restes d'autres tests et conduire à des résultats surprenants - ou à des tests défaillants qui fonctionnaient auparavant. La chose brillante que cela permettrait est de réinitialiser le bac à sable global dans beforeEach pour améliorer l'isolation des tests. 👍

bac à sable.remplacer

J'aime beaucoup ça. Je comprends cela comme un utilitaire "laissez-moi simplement coller ce truc là", n'est-ce pas ?

De plus, si nous prenons en charge callsThrough: true en tant que configuration (qui n'est pas valide en combinaison avec l'une des propriétés behaviors), les nouveaux fakes pourraient également être utilisés à la place de l'API "spy". Ce serait plus explicite que d'apprendre ce que "espion" et "stub" signifient dans Sinon-speak 😄

Cela signifierait-il que nous n'aurions pas du tout besoin spy ou stub ?

bac à sable.remplacer

J'aime beaucoup ça. Je comprends cela comme un utilitaire "laissez-moi simplement coller ce truc là", n'est-ce pas ?

Ouais, c'était l'idée. Au lieu de surcharger la même méthode ( sinon.stub ) pour faire beaucoup, beaucoup de choses, ayez des méthodes explicites qui ne font qu'une seule chose

Comme vous l'avez souligné, l'API fake ne prendra probablement pas en charge tout ce qui est actuellement possible avec les espions et les stubs. Mais oui, je pense que l'API fake est une opportunité d'unifier les fonctionnalités stub et spy .

Bien que j'aime cette idée, cela signifie qu'appeler sinon.restore() après un test pourrait annuler certains restes d'autres tests et conduire à des résultats surprenants - ou à des tests défaillants qui fonctionnaient auparavant. La chose brillante que cela permettrait est de réinitialiser le bac à sable global dans beforeEach pour améliorer l'isolation des tests. 👍

Il s'agit certainement d'un changement radical et ne doit pas être introduit à la légère.

Lors de la création d'un fake , si vous ne lui transmettez pas une configuration de comportement, cela équivaudrait à un spy .

// ~spy, records all calls, has no behaviour
const fake = sinon.fake();

// ~stub, records all calls, returns 'apple pie'
const fake = sinon.fake({
    returns: 'apple pie'
});

Comment créeriez-vous un stub qui ne fait rien alors?

Comment créeriez-vous un stub qui ne fait rien alors?

Je ne suis pas sûr de bien comprendre votre question... mais voilà

// a fake that has no behaviour
const fake = sinon.fake();

// put it in place of an existing method
sandbox.replace(myObject, 'someMethod', fake);

Ah, je pense que je comprends ce que vous voulez dire maintenant : un fake est toujours un stub . Lorsque vous avez dit ~spy, records all calls , j'ai compris "appels à la fonction d'origine". Cependant, le fake n'a aucune connaissance de la fonction qu'il remplace - c'est ce que fait le sandbox.replace .

Donc, dans cet esprit, voici une autre proposition sur la façon dont nous pourrions intégrer la fonctionnalité actuelle spy (comme dans l'appel) dans les nouvelles contrefaçons :

const fake = sinon.fake(function () {
   // Any custom function
});

La fonction donnée serait appelée par le faux. Cette API rend impossible son mélange avec d'autres comportements. En fait, un objet de configuration créerait une fonction qui implémenterait le comportement spécifié, puis le transmettrait au faux.

L'implémentation sandbox.spy(object, method) pourrait alors devenir ceci :

const original = object[method];
const fake = sinon.fake(original);
sandbox.replace(object, method, fake);

Fondamentalement, un one-liner 🤓

Ouais. Une fois que vous avez simplifié les choses, vous pouvez commencer à remixer pour le plaisir 🎉 et le profit 💰

Cependant, si nous voulons simplement utiliser fake et ne plus utiliser spy et stub , nous devrions probablement laisser ces deux seuls.

Je pense à la "prochaine" API ici. Vous auriez besoin sandbox.spy pour avoir la logique de remplacement quelque part. Si je comprends bien, cela devrait être rétrocompatible. L'implémentation stub pourrait alors être obsolète.

Vous auriez besoin de sandbox.spy pour avoir la logique de remplacement quelque part. Si je comprends bien, cela devrait être rétrocompatible. L'implémentation du stub pourrait alors être obsolète.

Je ne suis pas sûr de suivre. Pourriez-vous élaborer?

Sûr. Si je comprends bien votre proposition, vous voulez un remplacement pour l'API stub trop compliquée. La façon dont les stubs sont actuellement implémentés consiste à créer un spy avec une fonction implémentant le comportement. Ce que je suggère, c'est de faire la même chose avec l'API fake et de créer en interne un spy , mais nous ne renverrions plus de comportement, car nous voulons nous débarrasser du chaînage . Nous rendrions juste l'espion. Cela fait de l'implémentation fake une alternative à stub , la fonction renvoyée étant compatible avec toutes les API Sinon actuelles. Est-ce logique ou ai-je raté quelque chose?

OK, je pense que nous avons une compréhension similaire 👍

Juste pour réitérer, au cas où nous aurions raté quelque chose et pour que les autres contributeurs aient la même compréhension.

TL;DR

  • tous les remplacements seront effectués par un nouvel utilitaire : sandbox.replace (il vit actuellement dans stub )
  • sinon aura un bac à sable par défaut, permettant sinon.reset et sinon.restore (devrions-nous simplement les fusionner ?)
  • sinon.fake - un remplacement immuable et programmable pour les fonctions qui enregistrent tous les appels
  • sinon.spy
  • sinon.stub
// effectively a spy that has no target
const fake = sinon.fake()

// spy on a function
const fake = sinon.fake(console.log);
const fake = sinon.fake(function() { return 'apple pie'; });

// a shorthand construction of fake with behaviour
const fake = sinon.fake({
    returns: 'apple pie'
});

// replacing an existing function with a fake
var fakeLog = sinon.fake();
sandbox.replace(console, 'log', fakeLog);

À ce stade, la proposition ne traite que de Function . Nous devons réfléchir à ce qu'il faut faire des propriétés non fonctionnelles et des accesseurs. À tout le moins, nous devrions voir si nous pouvons limiter sandbox.replace pour n'autoriser que les remplacements sains.

Cela signifie-t-il que sinon.stub() et sinon.spy() seront tous deux obsolètes à l'avenir au profit de sinon.fake() , ou simplement refaits en interne ? Si tel est le cas, nous nous dirigeons essentiellement vers la pensée de TestDouble . Pas nécessairement une mauvaise chose, à mon humble avis, mais cela vaut peut-être la peine de considérer que si beaucoup de gens trouvent qu'ils devront remplacer tous leurs appels d'API Sinon de toute façon pour sinon.fake() , ils pourraient tout aussi bien utiliser une autre bibliothèque (bien que cela signifierait qu'ils perdraient toutes leurs connaissances existantes de l'API de Sinon).

Cela signifie-t-il que sinon.stub() et sinon.spy() seront tous deux obsolètes à l'avenir au profit de sinon.fake(), ou simplement refaits en interne ? Si tel est le cas, nous nous dirigeons essentiellement vers la pensée de TestDouble.

Je suppose que cela se chevauche un peu. Ma principale motivation pour cette proposition est d'avoir de fausses fonctions avec un comportement immuable.

Pas nécessairement une mauvaise chose, à mon humble avis, mais cela peut valoir la peine de considérer que si beaucoup de gens trouvent qu'ils auront besoin de remplacer tous leurs appels d'API Sinon de toute façon pour sinon.fake(), ils pourraient tout aussi bien utiliser une autre bibliothèque (bien que cela signifierait qu'ils perdraient toutes leurs connaissances existantes de l'API de Sinon).

Si les gens trouvent qu'une autre bibliothèque répond mieux à leurs besoins, alors je suis heureux que nous les ayons aidés à apprendre cela :)

Mais allons-nous conserver les méthodes d'espionnage et de stub ou les déconseiller, avec quelques réductions possibles de fonctionnalités ? Ce n'était pas clair pour moi.

Mais allons-nous conserver les méthodes d'espionnage et de stub ou les déconseiller, avec quelques réductions possibles de fonctionnalités ?

Une fois que fake semble stable, je déconseillerais spy et stub , puis je lui donnerais environ un an pour donner aux gens le temps de se mettre à jour.

Je pense que nous devrions faire de notre mieux pour fournir des codemods et une excellente documentation, pour aider les gens à déplacer leur code

Je travaille sur une branche pour les premières parties de ceci (bac à sable par défaut). J'ai refactorisé le code pour que sandbox et collection ne fassent plus qu'un. J'ai réussi à faire fonctionner le bac à sable par défaut.

Je vais ranger les commits au cours des prochains jours, puis créer une branche sur ce référentiel pour l'API mise à jour.

C'est une excellente idée, très bien écrit d'ailleurs.

J'ajouterais également des avis de dépréciation aux stubs et aux espions.

Je pensais aussi peut-être changer le passage d'un objet avec des clés en passant des fonctions.

Cela ajouterait les avantages suivants :

  • Cela nous permettrait d'ajouter un type à ces fonctions pour les utilisateurs qui souhaitent utiliser typescript ou d'autres types de vérificateurs statiques
  • Les utilisateurs obtiendraient des erreurs lorsqu'ils essaieraient d'invoquer des fonctions pour des comportements qui n'existent pas
  • Nous pourrions documenter ces fonctions séparément et améliorer encore la documentation
  • Nous pourrions fournir des erreurs utiles lors du passage d'arguments qui n'ont pas de sens pour ces comportements et leur permettre d'avoir plusieurs arguments facultatifs/plus d'un
  • Cela rendrait également les choses plus composables (même si je ne vois pas beaucoup de cas pour cela dans ce cas) et permettrait aux gens de réutiliser les comportements créés
  • IMO ce serait aussi plus simple que d'avoir un objet avec un comportement

Par conséquent, l'API ressemblerait plutôt à ceci :

// It would be cool to allow users to import these using destructuring to make code more concise
import { resolves, rejects, returns } from 'sinon/behaviors'; 

var fake = sinon.fake(resolves('apple pie'))

var fake = sinon.fake(rejects(new TypeError('no more pie')));
var fake = sinon.fake(rejects('no more pie'));

var fake = sinon.fake(returns('apple pie'));

var fake = sinon.fake(throws(new RangeError('no more pie'));
var fake = sinon.fake(throws('no more pie'));

Lorsqu'il s'agit de mettre cela en œuvre, il pourrait s'agir simplement de renvoyer des objets très simples comme ceux que vous proposez. Ensuite, si nous avons plus d'un comportement, nous pouvons simplement les fusionner.

De plus, lorsqu'il s'agit de mélanger des choses comme onThirdCall et withArgs , je pense que ce qui se passe dans ces cas devrait être documenté.

Désolé d'avoir revu ça si tard. Les derniers mois ont été très chargés.

@lucasfcosta consultez le PR #1586

Ce problème a été automatiquement marqué comme obsolète, car il n'a pas eu d'activité récente. Il sera fermé s'il n'y a plus d'activité. Merci pour vos contributions.

La version précédente 5.0.0 pose des problèmes avec les dernières versions préliminaires 5.0.0-next.* dans package.json, car 5.0.0 est supérieure à toute version préliminaire.

Puisque la version 5.0.0 est disponible, je pense que les numéros de pré-version next doivent peut-être être augmentés à 5.0.1-next.1 ?

J'ai remarqué cela parce qu'un autre package que j'utilisais recevait un msg obsolète et son package.json dépend de "sinon": "^5.0.0-next.4"

npm WARN deprecated [email protected]: this version has been deprecated

Je n'étais pas sûr que cela valait la peine d'ouvrir un nouveau numéro pour un problème de pré-version, donc un commentaire ici semblait le plus sûr.

Une autre solution serait de publier la prochaine version majeure. Qu'en pensez-vous @sinonjs/core ?

@mroderick Je ne peux plus dire quels sont tous les changements pour la v5. D'après mes derniers tests, cela a bien fonctionné et j'ai hâte d'utiliser les nouvelles contrefaçons. C'est un nouveau major, alors hé, expédiez-le 😄

Il y a juste un autre PR #1764 que j'aimerais fusionner, avant que nous ne publiions la prochaine version majeure.

J'ai publié [email protected] , j'espère que cela facilitera la vie des gens entre-temps.

Merci, j'ai testé (toujours bon de vérifier) ​​les dépendances dans package.json et "sinon": "^5.0.1" donne une erreur comme il se doit car il n'y a pas de correspondance trouvée (pas encore de version), et "sinon": "^5.0.1-next.1" fonctionne correctement pour obtenir cette version.

Cela n'a jamais été un gros problème, j'ai juste pensé que cela valait la peine de vous en informer, en particulier lorsque j'ai vu que la v5 était en développement depuis un certain temps, donc je ne savais pas combien de temps avant sa sortie. Je pense que sortir dans un futur proche semble être une bonne idée.

fake a été introduit avec #1768, qui est devenu [email protected]

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

stephanwlee picture stephanwlee  ·  3Commentaires

fearphage picture fearphage  ·  4Commentaires

NathanHazout picture NathanHazout  ·  3Commentaires

stevenmusumeche picture stevenmusumeche  ·  3Commentaires

fearphage picture fearphage  ·  3Commentaires