Sinon: Idee für zukünftige Meilensteine

Erstellt am 13. Sept. 2017  ·  31Kommentare  ·  Quelle: sinonjs/sinon

Hintergrund

sinon.stub / sandbox.stub ist zur Küchenspüle von geworden
konfigurierbares Verhalten bei Problemen, die ohne Regressionen oft schwer zu finden und zu beheben sind.

Ich denke, dass die eigentliche Ursache der Schwierigkeiten darin besteht, dass es stub viel zu viele Verantwortlichkeiten hat.

Darüber hinaus hat stub auch eine problematische Verwendung, die dadurch verursacht wird, dass das Verhalten festgelegt wird, nachdem es erstellt wurde, und viele Male neu definiert werden kann.

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?

Und dann gibt es die verwirrenderen Szenarien

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

Was bringt das überhaupt?

Anstatt weiterhin stub weitere Verantwortlichkeiten hinzuzufügen, schlage ich vor, dass wir stattdessen neue Mitglieder in sinon einführen, die viel enger gefasst sind.

Das Wichtigste, was mir einfällt, wäre ein unveränderlicher Ersatz für eine Funktion.

Wir können dann später herausfinden, was wir mit Eigenschaften tun werden (als separates, neues Mitglied mit Einzelverantwortung).

sinon.fake

Ein fake (der Rückgabewert des Aufrufs von sinon.fake ) ist ein reines und unveränderliches Function . Es tut eine Sache, und nur eine Sache. Es verhält sich bei jedem Anruf gleich. Im Gegensatz zu stub kann sein Verhalten nicht neu definiert werden. Wenn Sie ein anderes Verhalten benötigen, erstellen Sie ein neues fake .

Alleinige Verantwortung

Eine Fälschung kann eine dieser Verantwortlichkeiten haben

  • löst ein Promise in einen Wert auf
  • lehnen Sie ein Promise zu einem Error ab
  • einen Wert zurückgeben
  • wirf ein Error
  • Rückgabewert(e) an einen Callback

Wenn Sie Nebeneffekte wollen/benötigen und immer noch die Spionageschnittstelle wollen, dann verwenden Sie entweder die echte Funktion, verwenden Sie ein stub oder erstellen Sie eine benutzerdefinierte Funktion

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

Wirft Fehler großzügig

Wird großzügig Fehler ausgeben, wenn der Benutzer versucht, sie auf nicht unterstützte Weise zu erstellen/zu verwenden.

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

Verwendet die Spionage-API

Außer .withArgs , da dies gegen die Unveränderlichkeit verstößt

Nutzungsideen

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

Synonyme

Ich weiß nicht, ob fake hier das beste Substantiv ist, aber ich denke, wir sollten versuchen, uns an die Konvention der Verwendung von Substantiven zu halten und nicht in Adjektive oder Verben abzuschweifen.

Vorgeschlagene API-Änderungen

Verwenden Sie eine Standard-Sandbox

Das ist etwas, was ich schon eine Weile in Betracht gezogen habe, warum machen wir nicht eine Standard-Sandbox? Wenn Benutzer separate Sandboxes benötigen, können sie diese dennoch erstellen.

Wir sollten eine Standard-Sandbox erstellen, die für alle Methoden verwendet wird, die über sinon.* gemacht werden.
Das bedeutet, dass sinon.stub dasselbe wie sandbox.stub wird, wodurch die Beschränkung aufgehoben wird, Eigenschaften mit sinon.stub zu können.

sandbox.replace

Erstellen Sie sandbox.replace und verwenden Sie das für alle Operationen, die irgendetwas irgendwo ersetzen. Geben Sie dies als sinon.replace an und verwenden Sie die Standard-Sandbox, wenn Sie auf diese Weise verwendet werden.

Dies sollte wahrscheinlich eine ernsthafte Eingabevalidierung haben, sodass nur Funktionen durch Funktionen, Zugriffsmethoden durch Zugriffsmethoden usw. ersetzt werden.

Feature Request Improvement Needs investigation pinned

Hilfreichster Kommentar

OK, ich denke, wir haben ein ähnliches Verständnis 👍

Nur zur Wiederholung, falls wir etwas übersehen haben und damit andere Mitwirkende das gleiche Verständnis haben.

TL;DR

  • Alle Ersetzungen werden durch ein neues Dienstprogramm durchgeführt: sandbox.replace (dieses befindet sich derzeit in stub )
  • sinon wird eine Standard-Sandbox haben, die sinon.reset und sinon.restore (sollten wir diese einfach zusammenführen?)
  • sinon.fake – ein unveränderlicher, programmierbarer Ersatz für Funktionen, die alle Anrufe aufzeichnen
  • 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);

In diesem Zustand befasst sich das Angebot nur mit Function . Wir müssen überlegen, was mit Nicht-Funktionseigenschaften und Accessoren zu tun ist. Zumindest sollten wir sehen, ob wir sandbox.replace beschränken können, um nur vernünftige Ersetzungen zuzulassen.

Alle 31 Kommentare

Pingen Sie @sinonjs/core

Gute Vorschläge, Morgan. Danke, dass du das angesprochen hast. Ich denke auch, dass die stub -API Verwirrung stiftet, und ich mag alle Ihre Vorschläge. Hier sind einige Gedanken:

sinon.fake

Ich stimme zu, dass Unveränderlichkeit hier der Schlüssel ist. Wir könnten jedoch einige "gesunde" Anwendungsfälle zulassen, die derzeit mit Stubs möglich sind.

Beispielsweise könnte es ein gültiger Anwendungsfall sein, Folgendes zu ergeben und zurückzugeben:

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

Wir können prüfen, was sinnvoll ist und was nicht.

Auch wenn wir callsThrough: true als Konfiguration unterstützen (was in Kombination mit einer der Verhaltenseigenschaften ungültig ist), könnten die neuen Fälschungen auch anstelle der „Spionage“-API verwendet werden. Das wäre selbsterklärender, als zu lernen, was "Spion" und "Stub" in der Sinon-Sprache bedeuten 😄

Verwenden Sie eine Standard-Sandbox

Obwohl ich diese Idee mag, bedeutet dies, dass das Aufrufen sinon.restore() nach einem Test einige Überbleibsel von anderen Tests rückgängig machen und zu überraschenden Ergebnissen führen könnte - oder Tests fehlschlagen, die zuvor zufällig funktioniert haben. Das Geniale, was dies ermöglichen würde, ist das Zurücksetzen der globalen Sandbox in beforeEach , um die Testisolierung zu verbessern. 👍

sandbox.replace

Ich mag das sehr. Ich verstehe das als ein Dienstprogramm "Lass mich das Ding einfach dort stecken", oder?

Wenn wir außerdem "callsThrough: true" als Konfiguration unterstützen (was in Kombination mit einer der Verhaltenseigenschaften ungültig ist), könnten die neuen Fälschungen auch anstelle der "spy"-API verwendet werden. Das wäre selbsterklärender, als zu lernen, was "Spion" und "Stub" in der Sinon-Sprache bedeuten 😄

Würde das bedeuten, dass wir spy oder stub überhaupt nicht brauchen würden?

sandbox.replace

Ich mag das sehr. Ich verstehe das als ein Dienstprogramm "Lass mich das Ding einfach dort stecken", oder?

Ja, das war die Idee. Anstatt dieselbe Methode ( sinon.stub ) zu überladen, um viele, viele Dinge zu tun, verwenden Sie explizite Methoden, die nur eine Sache tun

Wie Sie betont haben, wird die fake -API wahrscheinlich nicht alles unterstützen, was derzeit mit Spies und Stubs möglich ist. Aber ja, ich denke, die fake -API ist eine Gelegenheit, die stub - und spy -Funktionalität zu vereinheitlichen.

Obwohl ich diese Idee mag, bedeutet dies, dass das Aufrufen von sinon.restore() nach einem Test einige Überbleibsel von anderen Tests rückgängig machen und zu überraschenden Ergebnissen führen könnte – oder Tests fehlschlagen, die zuvor zufällig funktionierten. Das Geniale, was dies ermöglichen würde, ist das Zurücksetzen der globalen Sandbox in beforeEach, um die Testisolation zu verbessern. 👍

Es ist sicherlich eine bahnbrechende Änderung und sollte nicht auf die leichte Schulter genommen werden.

Wenn Sie beim Erstellen eines fake keine Verhaltenskonfiguration übergeben, entspricht dies einem 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'
});

Wie würden Sie einen Stub erstellen, der dann nichts tut?

Wie würden Sie einen Stub erstellen, der dann nichts tut?

Ich bin mir nicht sicher, ob ich Ihre Frage ganz verstehe ... aber hier geht's

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

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

Ah, ich glaube, ich verstehe jetzt, was du meinst: Ein fake ist immer ein stub . Als Sie ~spy, records all calls sagten, habe ich "Durchrufe zur ursprünglichen Funktion" verstanden. Das fake hat jedoch kein Wissen über die Funktion, die es ersetzt – das ist es, was sandbox.replace tut.

In Anbetracht dessen ist hier ein weiterer Vorschlag, wie wir die aktuelle spy -Funktionalität (wie beim Durchrufen) in die neuen Fälschungen falten könnten:

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

Die angegebene Funktion würde von der Fälschung aufgerufen. Diese API macht es unmöglich, sie mit anderen Verhaltensweisen zu mischen. Tatsächlich würde ein Konfigurationsobjekt eine Funktion erstellen, die das angegebene Verhalten implementiert, und es dann an die Fälschung übergeben.

Die Implementierung sandbox.spy(object, method) könnte dann wie folgt aussehen:

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

Im Grunde ein Einzeiler 🤓

Ja. Sobald Sie die Dinge vereinfacht haben, können Sie zum Spaß 🎉 und zum Gewinn 💰 mit dem Remixen beginnen

Wenn wir jedoch dazu übergehen wollen, nur fake zu verwenden und spy und stub nicht mehr zu verwenden, dann sollten wir diese beiden wahrscheinlich einfach in Ruhe lassen.

Ich denke hier an die "nächste" API. Sie würden sandbox.spy brauchen, um die Ersetzungslogik irgendwo zu haben. So wie ich es verstehe, sollte das abwärtskompatibel sein. Die Implementierung stub könnte dann als veraltet markiert werden.

Sie benötigen sandbox.spy, um die Ersetzungslogik irgendwo zu haben. So wie ich es verstehe, sollte das abwärtskompatibel sein. Die Stub-Implementierung könnte dann veraltet sein.

Ich bin mir nicht sicher, ob ich folgen kann. Könnten Sie das näher erläutern?

Sicher. So wie ich Ihren Vorschlag verstehe, möchten Sie einen Ersatz für die überkomplizierte stub -API. Die Art und Weise, wie Stubs derzeit implementiert werden, besteht darin, ein spy mit einer Funktion zu erstellen, die das Verhalten implementiert. Was ich vorschlage, ist, dasselbe mit der fake API zu tun und intern ein spy zu erstellen, aber wir würden kein Verhalten mehr zurückgeben, weil wir die Verkettung loswerden wollen . Wir würden den Spion einfach zurückgeben. Dies macht die Implementierung fake zu einer Alternative zu stub , wobei die zurückgegebene Funktion mit allen aktuellen Sinon-APIs kompatibel ist. Macht das Sinn oder übersehe ich etwas?

OK, ich denke, wir haben ein ähnliches Verständnis 👍

Nur zur Wiederholung, falls wir etwas übersehen haben und damit andere Mitwirkende das gleiche Verständnis haben.

TL;DR

  • Alle Ersetzungen werden durch ein neues Dienstprogramm durchgeführt: sandbox.replace (dieses befindet sich derzeit in stub )
  • sinon wird eine Standard-Sandbox haben, die sinon.reset und sinon.restore (sollten wir diese einfach zusammenführen?)
  • sinon.fake – ein unveränderlicher, programmierbarer Ersatz für Funktionen, die alle Anrufe aufzeichnen
  • 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);

In diesem Zustand befasst sich das Angebot nur mit Function . Wir müssen überlegen, was mit Nicht-Funktionseigenschaften und Accessoren zu tun ist. Zumindest sollten wir sehen, ob wir sandbox.replace beschränken können, um nur vernünftige Ersetzungen zuzulassen.

Bedeutet dies, dass sowohl sinon.stub() als auch sinon.spy() in Zukunft zugunsten von sinon.fake() veraltet sind oder nur intern neu erstellt werden? Wenn ja, dann bewegen wir uns im Wesentlichen in Richtung der Denkweise von TestDouble . Nicht unbedingt eine schlechte Sache, meiner Meinung nach, aber es könnte eine Überlegung wert sein, dass, wenn viele Leute feststellen, dass sie alle ihre Sinon-API-Aufrufe sowieso für sinon.fake() ersetzen müssen, sie genauso gut einfach eine andere Bibliothek verwenden könnten (obwohl das würde bedeuten, dass sie ihr gesamtes vorhandenes Wissen über die API von Sinon verlieren würden).

Bedeutet dies, dass sinon.stub() und sinon.spy() in Zukunft zugunsten von sinon.fake() veraltet sind oder nur intern neu erstellt werden? Wenn ja, dann bewegen wir uns im Wesentlichen in Richtung der Denkweise von TestDouble.

Ich denke, es überschneidet sich etwas. Meine Hauptmotivation für diesen Vorschlag sind gefälschte Funktionen mit unveränderlichem Verhalten.

Nicht unbedingt eine schlechte Sache, meiner Meinung nach, aber es könnte eine Überlegung wert sein, dass, wenn viele Leute feststellen, dass sie alle ihre Sinon-API-Aufrufe sowieso für sinon.fake() ersetzen müssen, sie genauso gut einfach eine andere Bibliothek verwenden könnten (obwohl das würde bedeuten, dass sie ihr gesamtes vorhandenes Wissen über die API von Sinon verlieren würden).

Wenn Leute feststellen, dass eine andere Bibliothek ihren Bedürfnissen besser entspricht, dann freue ich mich, dass wir ihnen dabei geholfen haben, das zu lernen :)

Aber werden wir die Spy- und Stub-Methoden beibehalten oder sie mit einigen möglichen Einschränkungen der Funktionalität verwerfen? Das war mir unklar.

Aber werden wir die Spy- und Stub-Methoden beibehalten oder sie mit einigen möglichen Einschränkungen der Funktionalität verwerfen?

Sobald fake stabil aussieht, würde ich spy und stub verwerfen und es dann etwa ein Jahr geben, um den Leuten Zeit für ein Upgrade zu geben.

Ich denke, wir sollten unser Bestes geben, um Codemods und großartige Dokumentation bereitzustellen, um den Leuten zu helfen, ihren Code zu verschieben

Ich arbeite an einem Zweig für die ersten Teile davon (Standard-Sandbox). Ich habe den Code so umgestaltet, dass sandbox und collection jetzt eins sind. Ich habe die Standard-Sandbox zum Laufen gebracht.

Ich werde die Commits in den nächsten Tagen aufräumen und dann einen Zweig in diesem Repository für die aktualisierte API erstellen.

Das ist eine großartige Idee, übrigens sehr gut geschrieben.

Ich würde Stubs und Spies auch Deprecation Notices hinzufügen.

Ich habe auch darüber nachgedacht, ob ich die Übergabe eines Objekts mit Schlüsseln ändern könnte, indem ich Funktionen übergebe.

Dies würde die folgenden Vorteile hinzufügen:

  • Dies würde es uns ermöglichen, ein type zu diesen Funktionen für Benutzer hinzuzufügen, die typescript oder andere Arten von statischen Prüfern verwenden möchten
  • Benutzer würden Fehler erhalten, wenn sie versuchten, Funktionen für nicht vorhandene Verhaltensweisen aufzurufen
  • Wir könnten diese Funktionen separat dokumentieren und die Dokumentation noch besser machen
  • Wir könnten nützliche Fehler liefern, wenn Argumente übergeben werden, die für diese Verhaltensweisen keinen Sinn ergeben, und ihnen erlauben, optionale/mehr als ein Argument zu haben
  • Es würde die Dinge auch zusammensetzbarer machen (obwohl ich in diesem Fall nicht viele Fälle dafür sehe) und es den Leuten ermöglichen, erstellte Verhaltensweisen wiederzuverwenden
  • IMO wäre dies auch einfacher als ein Objekt mit Verhalten zu haben

Daher würde die API stattdessen so aussehen:

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

Bei der Implementierung könnte es nur darum gehen, sehr einfache Objekte wie die von Ihnen vorgeschlagenen zurückzugeben. Wenn wir dann mehr als ein Verhalten haben, können wir sie einfach zusammenführen.

Auch wenn es um das Mischen von Dingen wie onThirdCall und withArgs geht, denke ich, dass das, was in diesen Fällen passiert, dokumentiert werden sollte.

Tut mir leid, dass ich das so spät rezensiere. Die letzten Monate waren sehr arbeitsreich.

@lucasfcosta sehen Sie sich die PR #1586 an

Dieses Problem wurde automatisch als veraltet markiert, da es in letzter Zeit keine Aktivität gab. Es wird geschlossen, wenn keine weiteren Aktivitäten stattfinden. Vielen Dank für Ihre Beiträge.

Die Vorgängerversion 5.0.0 verursacht Probleme mit den späteren Vorabversionen 5.0.0-next.* in package.json, da 5.0.0 größer ist als jede Vorabversion.

Da 5.0.0 da draußen ist, denke ich, dass die next Prerelease-Nummern vielleicht auf 5.0.1-next.1 $ erhöht werden müssen?

Ich habe das bemerkt, weil ein anderes Paket, das ich benutzte, eine veraltete msg erhielt und seine package.json von "sinon": "^5.0.0-next.4" abhängt

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

Ich war mir nicht sicher, ob dies es wert war, ein neues Problem für ein Vorabveröffentlichungsproblem zu eröffnen, daher schien ein Kommentar hier am sichersten.

Eine andere Lösung wäre, die nächste Hauptversion zu veröffentlichen. Was denkst du @sinonjs/core?

@mroderick Ich kann nicht mehr sagen, was alle Änderungen für v5 sind. Von meinen letzten Tests hat es gut funktioniert und ich freue mich darauf, die neuen Fälschungen zu verwenden. Es ist ein neues Major, also hey, versende es 😄

Es gibt nur eine weitere PR #1764, die ich zusammenführen möchte, bevor wir die nächste Hauptversion veröffentlichen.

Ich habe [email protected] veröffentlicht, hoffentlich macht das den Leuten in der Zwischenzeit das Leben leichter.

Danke, ich habe Abhängigkeiten in package.json getestet (immer gut zu überprüfen) und "sinon": "^5.0.1" gibt einen Fehler aus, wie es sollte, weil keine Übereinstimmung gefunden wurde (noch keine Version), und "sinon": "^5.0.1-next.1" funktioniert richtig, diese Version zu bekommen.

Das war nie eine große Sache, ich dachte nur, dass es sich lohnt, Sie darauf aufmerksam zu machen, besonders als ich sah, dass v5 schon eine Weile in der Entwicklung war, also war ich mir nicht sicher, wie lange es bis zur Veröffentlichung dauern würde. Ich denke, die Veröffentlichung in naher Zukunft klingt nach einer guten Idee.

fake wurde mit #1768 eingeführt, das zu [email protected] wurde

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen