sinon.stub
/ sandbox.stub
telah menjadi wastafel dapur
perilaku yang dapat dikonfigurasi dengan masalah yang seringkali sulit ditemukan dan diperbaiki tanpa regresi.
Saya pikir akar penyebab kesulitannya adalah stub
memiliki terlalu banyak tanggung jawab.
Lebih lanjut, stub
juga memiliki masalah penggunaan yang disebabkan oleh fakta bahwa perilaku diatur setelah dibuat, dan dapat didefinisikan ulang berkali-kali.
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?
Dan kemudian ada skenario yang lebih membingungkan
var myStub = sinon.stub()
.withArgs(42)
.onThirdCall()
.resolves('apple pie')
.rejects('no more pie')
Apa itu?
Alih-alih terus menambahkan lebih banyak tanggung jawab ke stub
, saya mengusulkan agar kami memperkenalkan anggota baru ke sinon
, yang cakupannya jauh lebih sempit .
Yang paling penting yang dapat saya pikirkan adalah pendirian yang tidak dapat diubah untuk suatu fungsi.
Kami kemudian dapat mengetahui apa yang akan kami lakukan tentang properti (sebagai anggota tanggung jawab tunggal yang terpisah, baru).
sinon.fake
A fake
(nilai kembalian dari panggilan sinon.fake
) adalah Function
murni dan tidak dapat diubah . Ia melakukan satu hal, dan hanya satu hal. Ini memiliki perilaku yang sama pada setiap panggilan. Tidak seperti stub
, perilakunya tidak dapat didefinisikan ulang. Jika Anda membutuhkan perilaku yang berbeda, buat fake
baru.
Seorang palsu dapat memiliki salah satu dari tanggung jawab ini
Promise
menjadi nilaiPromise
ke Error
Error
Jika Anda ingin/membutuhkan efek samping, dan Anda masih menginginkan antarmuka mata-mata, maka gunakan fungsi sebenarnya, gunakan stub
atau buat fungsi khusus
sinon.replace(myObject, myMethod, sandbox.spy(function(args) {
someFunctionWithSideEffects(args);
});
Akan bermurah hati dengan melempar kesalahan ketika pengguna mencoba membuat/menggunakannya dengan cara yang tidak didukung.
// will throw TypeError when `config` argument has more than one property
const fake = sinon.fake({
resolves: true,
returns: true
});
Kecuali .withArgs
, karena itu melanggar kekekalan
// 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'
});
Saya tidak tahu apakah fake
adalah kata benda terbaik untuk digunakan di sini, tapi saya pikir kita harus mencoba untuk tetap berpegang pada konvensi menggunakan kata benda, dan tidak menyimpang ke kata sifat atau kata kerja.
Ini adalah sesuatu yang telah saya pertimbangkan untuk sementara waktu, mengapa kita tidak membuat kotak pasir default? Jika orang membutuhkan kotak pasir terpisah, mereka masih dapat membuatnya.
Kita harus membuat kotak pasir default yang digunakan untuk semua metode yang diekspos melalui sinon.*
.
Ini berarti sinon.stub
akan menjadi sama dengan sandbox.stub
, yang akan menghilangkan batasan kemampuan untuk mematikan properti menggunakan sinon.stub
.
sandbox.replace
Buat sandbox.replace
dan gunakan itu untuk semua operasi yang menggantikan apa pun di mana saja. Paparkan ini sebagai sinon.replace
dan gunakan kotak pasir default saat digunakan dengan cara ini.
Ini mungkin harus memiliki beberapa validasi input yang serius, jadi itu hanya akan menggantikan fungsi dengan fungsi, pengakses dengan pengakses, dll.
Ping ke @sinonjs/core
Saran yang bagus, Morgan. Terima kasih telah mengangkat ini. Saya juga berpikir bahwa stub
API membingungkan dan saya menyukai semua saran Anda. Berikut adalah beberapa pemikiran:
sinon.fake
Saya setuju bahwa kekekalan adalah kuncinya di sini. Kami dapat mengizinkan beberapa kasus penggunaan "waras" yang saat ini dimungkinkan dengan stub.
Misalnya, ini bisa menjadi kasus penggunaan yang valid untuk menghasilkan dan mengembalikan:
sinon.fake({
yields: [null, 42],
returns: true
})
Kita bisa mengecek mana yang masuk akal dan mana yang tidak.
Juga, jika kami mendukung callsThrough: true
sebagai konfigurasi (yang tidak valid dalam kombinasi dengan properti perilaku mana pun), pemalsuan baru juga dapat digunakan sebagai ganti API "mata-mata". Ini akan lebih menjelaskan diri sendiri daripada mempelajari apa arti "mata-mata" dan "rintisan" dalam bahasa Sinon
Gunakan kotak pasir default
Meskipun saya menyukai ide ini, itu berarti bahwa memanggil sinon.restore()
setelah pengujian dapat mengembalikan beberapa sisa dari pengujian lain dan menghasilkan hasil yang mengejutkan - atau kegagalan pengujian yang sebelumnya berhasil. Hal brilian yang memungkinkan ini adalah mengatur ulang kotak pasir global di beforeEach
untuk meningkatkan isolasi pengujian. 👍
kotak pasir.ganti
Saya sangat menyukai ini. Saya memahami ini sebagai utilitas "biarkan saya menempelkan benda ini di sana", bukan?
Selain itu, jika kami mendukung callThrough: true sebagai konfigurasi (yang tidak valid jika digabungkan dengan properti perilaku mana pun), fakes baru juga dapat digunakan sebagai ganti API "mata-mata". Ini akan lebih menjelaskan diri sendiri daripada mempelajari apa arti "mata-mata" dan "rintisan" dalam bahasa Sinon
Apakah itu berarti kita tidak membutuhkan spy
atau stub
sama sekali?
kotak pasir.ganti
Saya sangat menyukai ini. Saya memahami ini sebagai utilitas "biarkan saya menempelkan benda ini di sana", bukan?
Ya, itu idenya. Alih-alih membebani metode yang sama ( sinon.stub
) untuk melakukan banyak, banyak hal, miliki metode eksplisit yang hanya melakukan satu hal
Seperti yang Anda soroti, fake
API mungkin tidak akan mendukung semua yang saat ini dimungkinkan dengan spies dan stub. Tapi ya, menurut saya fake
API adalah kesempatan untuk menyatukan fungsionalitas stub
dan spy
.
Meskipun saya menyukai ide ini, itu berarti bahwa memanggil sinon.restore() setelah pengujian dapat mengembalikan beberapa sisa dari pengujian lain dan menghasilkan hasil yang mengejutkan - atau kegagalan pengujian yang sebelumnya berhasil. Hal brilian yang memungkinkan ini adalah mengatur ulang kotak pasir global di beforeEach untuk meningkatkan isolasi pengujian. 👍
Ini tentu saja merupakan perubahan besar, dan tidak boleh dianggap enteng.
Saat membuat fake
, jika Anda tidak memberikannya konfigurasi perilaku, itu akan setara dengan 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'
});
Bagaimana Anda membuat rintisan yang tidak melakukan apa-apa?
Bagaimana Anda membuat rintisan yang tidak melakukan apa-apa?
Saya tidak yakin saya sepenuhnya memahami pertanyaan Anda ... tapi begini
// a fake that has no behaviour
const fake = sinon.fake();
// put it in place of an existing method
sandbox.replace(myObject, 'someMethod', fake);
Ah, saya rasa saya mengerti apa yang Anda maksud sekarang: A fake
selalu stub
. Ketika Anda mengatakan ~spy, records all calls
Saya mengerti "memanggil ke fungsi asli". Namun, fake
tidak memiliki pengetahuan tentang fungsi yang diganti – itulah yang dilakukan sandbox.replace
.
Jadi dengan mengingat hal itu, berikut adalah proposal lain bagaimana kita dapat melipat fungsi spy
saat ini (seperti dalam memanggil) menjadi palsu baru:
const fake = sinon.fake(function () {
// Any custom function
});
Fungsi yang diberikan akan dipanggil oleh yang palsu. API ini tidak memungkinkan untuk mencampurnya dengan perilaku lain. Faktanya, objek konfigurasi akan membuat fungsi yang mengimplementasikan perilaku yang ditentukan dan kemudian meneruskannya ke yang palsu.
Implementasi sandbox.spy(object, method)
kemudian bisa menjadi ini:
const original = object[method];
const fake = sinon.fake(original);
sandbox.replace(object, method, fake);
Pada dasarnya satu kalimat
Ya. Setelah Anda menyederhanakan berbagai hal, Anda dapat mulai mencampur ulang untuk bersenang-senang dan untung
Namun, jika kita ingin lebih memilih menggunakan fake
dan tidak lagi menggunakan spy
dan stub
, maka sebaiknya kita biarkan saja keduanya.
Saya sedang memikirkan tentang API "berikutnya" di sini. Anda akan membutuhkan sandbox.spy
untuk memiliki logika penggantian di suatu tempat. Seperti yang saya pahami, itu harus kompatibel ke belakang. Implementasi stub
kemudian dapat dihentikan.
Anda akan membutuhkan sandbox.spy untuk memiliki logika pengganti di suatu tempat. Seperti yang saya pahami, itu harus kompatibel ke belakang. Implementasi rintisan kemudian dapat dihentikan.
Saya tidak yakin saya mengikuti. Bisakah Anda menguraikannya?
Tentu. Seperti yang saya pahami tentang proposal Anda, Anda menginginkan pengganti untuk stub
API yang terlalu rumit. Cara penerapan stub saat ini adalah dengan membuat spy
dengan fungsi yang mengimplementasikan perilaku tersebut. Apa yang saya sarankan adalah melakukan hal yang sama dengan fake
API dan secara internal membuat spy
, tetapi kami tidak akan mengembalikan perilaku lagi, karena kami ingin menyingkirkan rantai . Kami baru saja mengembalikan mata-mata. Ini menjadikan implementasi fake
sebagai alternatif dari stub
dengan fungsi yang dikembalikan kompatibel dengan semua API Sinon saat ini. Apakah itu masuk akal atau saya melewatkan sesuatu?
Oke, saya pikir kami memiliki pemahaman yang sama 👍
Hanya untuk mengulangi, jika kami melewatkan sesuatu dan agar kontributor lain memiliki pemahaman yang sama.
sandbox.replace
(saat ini tinggal di stub
)sinon
akan memiliki kotak pasir default, memungkinkan untuk sinon.reset
dan sinon.restore
(haruskah kita menggabungkannya saja?)sinon.fake
— pengganti yang tidak dapat diubah dan dapat diprogram untuk fungsi yang merekam semua panggilansinon.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);
Pada keadaan ini, proposal hanya berhubungan dengan Function
. Kita perlu mempertimbangkan apa yang harus dilakukan tentang properti dan pengakses non-fungsi. Paling tidak, kita harus melihat apakah kita dapat membatasi sandbox.replace
untuk hanya mengizinkan penggantian waras.
Apakah ini berarti sinon.stub()
dan sinon.spy()
keduanya akan ditinggalkan di masa mendatang demi sinon.fake()
, atau hanya dikerjakan ulang secara internal? Jika demikian, maka kita pada dasarnya bergerak menuju pemikiran TestDouble . Tidak selalu merupakan hal yang buruk, IMHO, tetapi mungkin perlu dipertimbangkan bahwa jika banyak orang merasa bahwa mereka perlu mengganti semua panggilan api Sinon mereka untuk sinon.fake()
, mereka mungkin juga hanya menggunakan perpustakaan lain (walaupun itu berarti mereka akan kehilangan semua pengetahuan mereka tentang API Sinon).
Apakah ini berarti sinon.stub() dan sinon.spy() keduanya akan ditinggalkan di masa mendatang demi sinon.fake(), atau hanya dibuat ulang secara internal? Jika demikian, maka kita pada dasarnya bergerak menuju pemikiran TestDouble.
Saya kira itu agak tumpang tindih. Motivasi utama saya untuk proposal ini adalah memiliki fungsi palsu dengan perilaku yang tidak dapat diubah.
Tidak selalu merupakan hal yang buruk, IMHO, tetapi mungkin perlu dipertimbangkan bahwa jika banyak orang merasa bahwa mereka perlu mengganti semua panggilan api Sinon mereka untuk sinon.fake(), mereka mungkin juga hanya menggunakan perpustakaan lain (walaupun itu berarti mereka akan kehilangan semua pengetahuan mereka tentang API Sinon).
Jika orang menemukan bahwa perpustakaan lain melayani kebutuhan mereka dengan lebih baik, maka saya senang kami membantu mereka mempelajarinya :)
Tetapi apakah kami akan mempertahankan metode mata-mata dan rintisan atau tidak menggunakannya, dengan beberapa kemungkinan pengurangan fungsionalitas? Itu tidak jelas bagi saya.
Tetapi apakah kami akan mempertahankan metode mata-mata dan rintisan atau tidak menggunakannya, dengan beberapa kemungkinan pengurangan fungsionalitas?
Setelah fake
terlihat stabil, maka saya akan menghentikan spy
dan stub
, dan kemudian memberikannya seperti setahun untuk memberi waktu kepada orang-orang untuk meningkatkan.
Saya pikir kita harus mencoba yang terbaik untuk menyediakan codemod dan dokumentasi yang bagus, untuk membantu orang memindahkan kode mereka
Saya sedang mengerjakan cabang untuk bagian pertama ini (kotak pasir default). Saya telah memfaktorkan ulang kode sehingga sandbox
dan collection
sekarang menjadi satu. Saya membuat kotak pasir default berfungsi.
Saya akan merapikan komit selama beberapa hari ke depan, dan kemudian membuat cabang di repositori ini untuk API yang diperbarui.
Ini adalah ide bagus, btw ditulis dengan sangat baik.
Saya juga akan menambahkan pemberitahuan penghentian ke bertopik dan mata-mata.
Saya juga berpikir tentang mungkin mengubah melewatkan objek dengan kunci dengan melewatkan fungsi.
Ini akan menambahkan manfaat berikut:
type
ke fungsi tersebut untuk pengguna yang ingin menggunakan typescript
atau jenis pemeriksa statis lainnyaOleh karena itu, API akan terlihat seperti ini:
// 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'));
Ketika datang untuk mengimplementasikan ini, itu hanya masalah mengembalikan objek yang sangat sederhana seperti yang Anda usulkan. Kemudian, jika kita memiliki lebih dari satu perilaku, kita bisa menggabungkannya.
Juga, ketika datang untuk mencampur hal-hal seperti onThirdCall
dan withArgs
Saya pikir apa yang terjadi dalam kasus tersebut harus didokumentasikan.
Maaf untuk meninjau ini sangat terlambat. Beberapa bulan terakhir ini sangat sibuk.
@lucasfcosta lihat PR #1586
Masalah ini secara otomatis ditandai sebagai basi karena tidak ada aktivitas terbaru. Ini akan ditutup jika tidak ada aktivitas lebih lanjut yang terjadi. Terima kasih atas kontribusi Anda.
Versi 5.0.0 sebelumnya menyebabkan masalah dengan versi prarilis 5.0.0-next.* yang lebih baru di package.json karena 5.0.0 lebih besar daripada versi prarilis mana pun.
Karena 5.0.0 ada di luar sana, saya pikir nomor pra-rilis next
mungkin perlu diubah menjadi 5.0.1-next.1
?
Saya perhatikan ini karena paket lain yang saya gunakan mendapatkan pesan usang dan package.json-nya bergantung pada "sinon": "^5.0.0-next.4"
npm WARN deprecated [email protected]: this version has been deprecated
Saya tidak yakin apakah ini layak untuk membuka edisi baru untuk masalah pra-rilis, jadi komentar di sini tampaknya paling aman.
Solusi lain adalah merilis versi utama berikutnya. Bagaimana menurut Anda @sinonjs/core?
@mroderick Saya tidak tahu lagi apa semua perubahan untuk v5. Dari tes terakhir saya, itu berfungsi dengan baik dan saya menantikan untuk menggunakan palsu baru. Ini jurusan baru, jadi, kirimkan
Hanya ada satu lagi PR #1764 yang ingin saya gabungkan, sebelum kami merilis versi utama berikutnya.
Saya telah menerbitkan [email protected]
, semoga itu akan membuat hidup lebih mudah bagi orang-orang sementara itu.
Terima kasih, saya telah menguji (selalu baik untuk memeriksa ulang) dependensi di package.json dan "sinon": "^5.0.1"
memberikan kesalahan sebagaimana mestinya karena tidak ada kecocokan yang ditemukan (belum ada rilis), dan "sinon": "^5.0.1-next.1"
berfungsi dengan baik untuk mendapatkan versi itu.
Ini bukan masalah besar, saya hanya berpikir itu layak untuk membuat Anda sadar, terutama ketika saya melihat bahwa v5 telah dikembangkan untuk sementara waktu jadi saya tidak yakin berapa lama sampai dirilis. Saya pikir merilis dalam waktu dekat terdengar seperti ide yang bagus.
fake
telah diperkenalkan dengan #1768, yang menjadi [email protected]
Komentar yang paling membantu
Oke, saya pikir kami memiliki pemahaman yang sama 👍
Hanya untuk mengulangi, jika kami melewatkan sesuatu dan agar kontributor lain memiliki pemahaman yang sama.
TL;DR
sandbox.replace
(saat ini tinggal distub
)sinon
akan memiliki kotak pasir default, memungkinkan untuksinon.reset
dansinon.restore
(haruskah kita menggabungkannya saja?)sinon.fake
— pengganti yang tidak dapat diubah dan dapat diprogram untuk fungsi yang merekam semua panggilansinon.spy
sinon.stub
Pada keadaan ini, proposal hanya berhubungan dengan
Function
. Kita perlu mempertimbangkan apa yang harus dilakukan tentang properti dan pengakses non-fungsi. Paling tidak, kita harus melihat apakah kita dapat membatasisandbox.replace
untuk hanya mengizinkan penggantian waras.