Sinon: Idea para hitos futuros

Creado en 13 sept. 2017  ·  31Comentarios  ·  Fuente: sinonjs/sinon

Fondo

sinon.stub / sandbox.stub se ha convertido en el fregadero de la cocina de
Comportamiento configurable con problemas que a menudo son difíciles de encontrar y solucionar sin regresiones.

Creo que la causa fundamental de las dificultades es que stub tiene demasiadas responsabilidades.

Además, stub también tiene un uso problemático causado por el hecho de que el comportamiento se establece después de que se haya creado y se puede redefinir muchas veces.

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?

Y luego están los escenarios más confusos

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

¿Qué hace eso?

En lugar de continuar agregando más responsabilidades a stub , propongo que en su lugar presentemos nuevos miembros a sinon , que tienen un alcance mucho más limitado.

Lo más importante que se me ocurre sería un sustituto inmutable de una función.

Luego, podemos averiguar qué vamos a hacer con las propiedades (como un miembro de responsabilidad único, nuevo e independiente).

sinon.fake

Un fake (el valor de retorno de llamar a sinon.fake ) es un Function puro e inmutable . Hace una cosa, y sólo una cosa. Tiene el mismo comportamiento en todas y cada una de las llamadas. A diferencia stub , su comportamiento no se puede redefinir. Si necesita un comportamiento diferente, cree un nuevo fake .

Responsabilidad única

Un falso puede tener una de estas responsabilidades

  • resolver un Promise a un valor
  • rechazar un Promise a un Error
  • devolver un valor
  • tirar un Error
  • producir valores a una devolución de llamada

Si desea/necesita efectos secundarios, y aún desea la interfaz de espionaje, entonces use la función real, use un stub o cree una función personalizada

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

Lanza errores generosamente

Será generoso al arrojar errores cuando el usuario intente crearlos/usarlos de formas no admitidas.

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

Utiliza la API de espionaje

Excepto por .withArgs , ya que viola la inmutabilidad

ideas de uso

// 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'
});

Sinónimos

No sé si fake es el mejor sustantivo para usar aquí, pero creo que deberíamos tratar de apegarnos a la convención de usar sustantivos y no perdernos en adjetivos o verbos.

Cambios de API propuestos

Usar una zona de pruebas predeterminada

Esto es algo que he estado considerando durante un tiempo, ¿por qué no creamos una zona de pruebas predeterminada? Si las personas necesitan sandboxes separados, aún pueden crearlos.

Deberíamos crear una zona de pruebas predeterminada que se use para todos los métodos que se exponen a través sinon.* .
Esto significa que sinon.stub se convertirá en lo mismo que sandbox.stub , lo que eliminará la limitación de poder agregar propiedades usando sinon.stub .

sandbox.replace

Cree sandbox.replace y utilícelo para todas las operaciones que reemplazan cualquier cosa en cualquier lugar. Exponga esto como sinon.replace y use el sandbox predeterminado cuando se use de esta manera.

Esto probablemente debería tener una validación de entrada seria, por lo que solo reemplazará funciones con funciones, accesores con accesores, etc.

Feature Request Improvement Needs investigation pinned

Comentario más útil

OK, creo que tenemos un entendimiento similar 👍

Solo para reiterar, en caso de que nos hayamos perdido algo y para que otros colaboradores tengan el mismo entendimiento.

TL;RD

  • todos los reemplazos serán realizados por una nueva utilidad: sandbox.replace (actualmente vive en stub )
  • sinon tendrá una zona de pruebas predeterminada, lo que permitirá sinon.reset y sinon.restore (¿deberíamos fusionarlos?)
  • sinon.fake — un reemplazo inmutable y programable para funciones que registra todas las llamadas
  • 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);

En este estado, la propuesta solo se ocupa de Function . Necesitamos considerar qué hacer con las propiedades y accesores que no son funciones. Como mínimo, deberíamos ver si podemos limitar sandbox.replace para permitir solo reemplazos sensatos.

Todos 31 comentarios

Haga ping a @sinonjs/core

Buenas sugerencias, Morgan. Gracias por mencionar esto. También creo que la API stub es una confusión y me gustan todas sus sugerencias. Aquí hay algunos pensamientos:

sinon.falso

Estoy de acuerdo en que la inmutabilidad es clave aquí. Sin embargo, podríamos permitir algunos casos de uso "sanos" que actualmente son posibles con stubs.

Por ejemplo, podría ser un caso de uso válido para producir y devolver:

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

Podemos comprobar qué tiene sentido y qué no.

Además, si admitimos callsThrough: true como configuración (lo que no es válido en combinación con cualquiera de las propiedades de comportamiento), las nuevas falsificaciones también podrían usarse en lugar de la API "espía". Esto sería más autoexplicativo que aprender qué significa "espía" y "talón" en Sinon-speak 😄

Usar una zona de pruebas predeterminada

Si bien me gusta esta idea, significa que llamar a sinon.restore() después de una prueba podría revertir algunos restos de otras pruebas y generar resultados sorprendentes, o pruebas fallidas que funcionaron antes. Lo brillante que esto permitiría es restablecer el sandbox global en beforeEach para mejorar el aislamiento de la prueba. 👍

caja de arena.reemplazar

Me gusta esto muchisimo. Entiendo esto como una utilidad de "solo déjame pegar esto allí", ¿verdad?

Además, si admitimos llamadas a través: verdadero como una configuración (que no es válida en combinación con cualquiera de las propiedades de comportamiento), las nuevas falsificaciones también podrían usarse en lugar de la API "espía". Esto sería más autoexplicativo que aprender qué significa "espía" y "talón" en Sinon-speak 😄

¿Significaría eso que no necesitaríamos spy o stub en absoluto?

caja de arena.reemplazar

Me gusta esto muchisimo. Entiendo esto como una utilidad de "solo déjame pegar esto allí", ¿verdad?

Sí, esa era la idea. En lugar de sobrecargar el mismo método ( sinon.stub ) para hacer muchas, muchas cosas, tenga métodos explícitos que hagan una sola cosa

Como resaltó, la API fake probablemente no admitirá todo lo que actualmente es posible con espías y stubs. Pero sí, creo que la API fake es una oportunidad para unificar la funcionalidad stub y spy .

Si bien me gusta esta idea, significa que llamar a sinon.restore() después de una prueba podría revertir algunos restos de otras pruebas y generar resultados sorprendentes, o pruebas fallidas que funcionaron antes. Lo brillante que esto permitiría es restablecer el sandbox global en beforeEach para mejorar el aislamiento de la prueba. 👍

Sin duda, es un cambio radical y no debe introducirse a la ligera.

Al crear un fake , si no le pasa una configuración de comportamiento, sería equivalente a 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'
});

¿Cómo crearías un stub que no hace nada entonces?

¿Cómo crearías un stub que no hace nada entonces?

No estoy seguro de entender completamente tu pregunta... pero aquí va

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

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

Ah, creo que entiendo lo que quieres decir ahora: un fake es siempre un stub . Cuando dijiste ~spy, records all calls entendí "llamadas a la función original". Sin embargo, fake no tiene conocimiento sobre la función que está reemplazando; eso es lo que hace sandbox.replace .

Entonces, con eso en mente, aquí hay otra propuesta de cómo podríamos incorporar la funcionalidad actual spy (como llamar) en las nuevas falsificaciones:

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

La función dada sería llamada por el fake. Esta API hace que sea imposible mezclarla con otros comportamientos. De hecho, un objeto de configuración crearía una función que implementa el comportamiento especificado y luego lo pasaría al falso.

La implementación sandbox.spy(object, method) podría convertirse en esto:

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

Básicamente una sola línea 🤓

Sí. Una vez que simplificas las cosas, puedes comenzar a mezclar por diversión 🎉 y obtener ganancias 💰

Sin embargo, si queremos optar por usar fake y dejar de usar spy y stub , entonces probablemente deberíamos dejar esos dos solos.

Estoy pensando en la "próxima" API aquí. Necesitaría sandbox.spy para tener la lógica de reemplazo en alguna parte. Según tengo entendido, eso debería ser compatible con versiones anteriores. La implementación stub podría quedar obsoleta.

Necesitaría sandbox.spy para tener la lógica de reemplazo en alguna parte. Según tengo entendido, eso debería ser compatible con versiones anteriores. La implementación de stub podría quedar obsoleta.

No estoy seguro de seguir. ¿Podría elaborar?

Seguro. Según entiendo su propuesta, desea un reemplazo para la API stub demasiado complicada. La forma en que se implementan actualmente los stubs es mediante la creación de un spy con una función que implementa el comportamiento. Lo que sugiero es hacer lo mismo con la API fake e internamente crear un spy , pero ya no devolveríamos un comportamiento, porque queremos deshacernos del encadenamiento. . Simplemente devolveríamos al espía. Esto hace que la implementación fake sea ​​una alternativa a stub y la función devuelta sea compatible con todas las API actuales de Sinon. ¿Tiene sentido o me estoy perdiendo algo?

OK, creo que tenemos un entendimiento similar 👍

Solo para reiterar, en caso de que nos hayamos perdido algo y para que otros colaboradores tengan el mismo entendimiento.

TL;RD

  • todos los reemplazos serán realizados por una nueva utilidad: sandbox.replace (actualmente vive en stub )
  • sinon tendrá una zona de pruebas predeterminada, lo que permitirá sinon.reset y sinon.restore (¿deberíamos fusionarlos?)
  • sinon.fake — un reemplazo inmutable y programable para funciones que registra todas las llamadas
  • 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);

En este estado, la propuesta solo se ocupa de Function . Necesitamos considerar qué hacer con las propiedades y accesores que no son funciones. Como mínimo, deberíamos ver si podemos limitar sandbox.replace para permitir solo reemplazos sensatos.

¿Significa esto que sinon.stub() y sinon.spy() quedarán obsoletos en el futuro a favor de sinon.fake() , o simplemente se reharán internamente? Si es así, nos estamos moviendo esencialmente hacia el pensamiento de TestDouble . No es necesariamente algo malo, en mi humilde opinión, pero podría valer la pena considerar que si muchas personas descubren que necesitarán reemplazar todas sus llamadas a la API de Sinon de todos modos por sinon.fake() , también podrían usar otra biblioteca (aunque eso significaría que perderían todo su conocimiento existente de la API de Sinon).

¿Significa esto que sinon.stub() y sinon.spy() quedarán obsoletos en el futuro a favor de sinon.fake(), o simplemente se reharán internamente? Si es así, nos estamos moviendo esencialmente hacia el pensamiento de TestDouble.

Supongo que se superpone un poco. Mi principal motivación para esta propuesta es tener funciones falsas con comportamiento inmutable.

No es necesariamente algo malo, en mi humilde opinión, pero podría valer la pena considerar que si muchas personas descubren que necesitarán reemplazar todas sus llamadas a la API de Sinon de todos modos por sinon.fake(), también podrían usar otra biblioteca (aunque eso significaría que perderían todo su conocimiento existente de la API de Sinon).

Si las personas encuentran que otra biblioteca satisface mejor sus necesidades, entonces me alegra que les hayamos ayudado a aprender eso :)

Pero, ¿mantendremos los métodos spy y stub o los descartaremos, con algunas posibles reducciones en la funcionalidad? Eso no estaba claro para mí.

Pero, ¿mantendremos los métodos spy y stub o los descartaremos, con algunas posibles reducciones en la funcionalidad?

Una vez que fake se vea estable, dejaría de usar spy y stub , y luego le daría como un año para que las personas tengan tiempo de actualizar.

Creo que deberíamos hacer todo lo posible para proporcionar codemods y excelente documentación, para ayudar a las personas a mover su código.

Estoy trabajando en una rama para las primeras partes de esto (sandbox predeterminado). He refactorizado el código para que sandbox y collection ahora sean uno. He conseguido que funcione el sandbox predeterminado.

Ordenaré las confirmaciones en los próximos días y luego crearé una rama en este repositorio para la API actualizada.

Esta es una gran idea, muy bien escrita por cierto.

También agregaría avisos de obsolescencia a stubs y spys.

También estaba pensando en tal vez cambiar el paso de un objeto con claves al pasar funciones.

Esto agregaría los siguientes beneficios:

  • Esto nos permitiría agregar un type a esas funciones para el usuario que quiera usar typescript u otro tipo de fichas estáticas
  • Los usuarios obtendrían errores al intentar invocar funciones para comportamientos que no existen
  • Podríamos documentar esas funciones por separado y hacer que los documentos sean aún mejores
  • Podríamos proporcionar errores útiles al pasar argumentos que no tienen sentido para esos comportamientos y permitirles tener más de un argumento opcional
  • También haría las cosas más componibles (aunque no veo muchos casos para esto en este caso) y permitiría a las personas reutilizar comportamientos creados.
  • En mi opinión, esto también sería más simple que tener un objeto con comportamiento

Por lo tanto, la API se vería así:

// 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'));

Cuando se trata de implementar esto, podría ser solo una cuestión de devolver objetos muy simples como los que está proponiendo. Luego, si tenemos más de un comportamiento, podemos fusionarlos.

Además, cuando se trata de mezclar cosas como onThirdCall y withArgs , creo que lo que sucede en esos casos debería documentarse.

Lo siento por revisar esto tan tarde. Los últimos meses han sido muy ocupados.

@lucasfcosta mira el PR #1586

Este problema se ha marcado automáticamente como obsoleto porque no ha tenido actividad reciente. Se cerrará si no se produce más actividad. Gracias por sus aportaciones.

La versión anterior 5.0.0 está causando problemas con las versiones preliminares posteriores 5.0.0-next.* en package.json porque 5.0.0 es mayor que cualquier versión preliminar.

Dado que 5.0.0 está disponible, creo que los números de presentación preliminar next deben aumentarse quizás a 5.0.1-next.1 .

Me di cuenta de esto porque otro paquete que estaba usando estaba recibiendo un mensaje obsoleto y su paquete.json depende de "sinon": "^5.0.0-next.4"

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

No estaba seguro de si valía la pena abrir un nuevo número para un problema preliminar, por lo que un comentario aquí parecía lo más seguro.

Otra solución sería lanzar la próxima versión principal. ¿Qué opinas @sinonjs/core?

@mroderick Ya no puedo decir cuáles son todos los cambios para v5. Según mis últimas pruebas, funcionó bien y estoy ansioso por usar las nuevas falsificaciones. Es una nueva especialidad, así que oye, envíala 😄

Solo hay un PR #1764 más que me gustaría fusionar, antes de lanzar la próxima versión principal.

He publicado [email protected] , con suerte eso hará la vida más fácil para las personas mientras tanto.

Gracias, probé (siempre es bueno verificar dos veces) las dependencias en package.json y "sinon": "^5.0.1" da un error como debería porque no se encontró ninguna coincidencia (todavía no hay lanzamiento), y "sinon": "^5.0.1-next.1" funciona correctamente obteniendo esa versión.

Esto nunca fue un gran problema, solo pensé que valía la pena informarle, especialmente cuando vi que v5 había estado en desarrollo durante un tiempo, así que no estaba seguro de cuánto tiempo hasta que se lanzó. Creo que lanzarlo en un futuro cercano suena como una buena idea.

fake se introdujo con #1768, que se convirtió en [email protected]

¿Fue útil esta página
0 / 5 - 0 calificaciones