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
.
Eine Fälschung kann eine dieser Verantwortlichkeiten haben
Promise
in einen Wert aufPromise
zu einem Error
abError
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);
});
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
});
Außer .withArgs
, da dies gegen die Unveränderlichkeit verstößt
// 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'
});
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.
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.
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.
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 aufzeichnensinon.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:
type
zu diesen Funktionen für Benutzer hinzuzufügen, die typescript
oder andere Arten von statischen Prüfern verwenden möchtenDaher 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
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
sandbox.replace
(dieses befindet sich derzeit instub
)sinon
wird eine Standard-Sandbox haben, diesinon.reset
undsinon.restore
(sollten wir diese einfach zusammenführen?)sinon.fake
– ein unveränderlicher, programmierbarer Ersatz für Funktionen, die alle Anrufe aufzeichnensinon.spy
sinon.stub
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 wirsandbox.replace
beschränken können, um nur vernünftige Ersetzungen zuzulassen.