sinon.stub
/ sandbox.stub
стала кухонной раковиной
настраиваемое поведение с проблемами, которые часто трудно найти и исправить без регрессии.
Я думаю, что stub
причина трудностей в том, что у него слишком много обязанностей.
Кроме того, stub
также имеет проблемное использование, вызванное тем фактом, что поведение устанавливается после его создания и может многократно переопределяться.
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?
И затем есть более запутанные сценарии
var myStub = sinon.stub()
.withArgs(42)
.onThirdCall()
.resolves('apple pie')
.rejects('no more pie')
Что это вообще делает?
Вместо того, чтобы продолжать добавлять новые обязанности в stub
, я предлагаю вместо этого ввести новых членов в sinon
, которые намного уже по объему.
Самое важное, что я могу придумать, это неизменная замена функции.
Позже мы сможем выяснить, что мы будем делать со свойствами (как с отдельным, новым членом с единой ответственностью).
sinon.fake
fake
(возвращаемое значение вызова sinon.fake
) является чистым и неизменным Function
. Он делает одно и только одно. Он ведет себя одинаково при каждом вызове. В отличие от stub
, его поведение нельзя переопределить. Если вам нужно другое поведение, создайте новый fake
.
Подделка может иметь одну из этих обязанностей
Promise
в значениеPromise
на Error
Error
Если вам нужны/нужны побочные эффекты, и вам все еще нужен шпионский интерфейс, то либо используйте настоящую функцию, либо используйте stub
, либо создайте пользовательскую функцию.
sinon.replace(myObject, myMethod, sandbox.spy(function(args) {
someFunctionWithSideEffects(args);
});
Будет щедро выбрасывать ошибки, когда пользователь пытается создать/использовать их неподдерживаемыми способами.
// will throw TypeError when `config` argument has more than one property
const fake = sinon.fake({
resolves: true,
returns: true
});
За исключением .withArgs
, так как это нарушает неизменность
// 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'
});
Я не знаю, является ли fake
лучшим существительным для использования здесь, но я думаю, что мы должны попытаться придерживаться соглашения об использовании существительных и не отклоняться от прилагательных или глаголов.
Это то, о чем я думал некоторое время, почему бы нам не сделать песочницу по умолчанию? Если людям нужны отдельные песочницы, они все равно могут их создать.
Мы должны создать песочницу по умолчанию, которая будет использоваться для всех методов, доступных через sinon.*
.
Это означает, что sinon.stub
станет таким же, как sandbox.stub
, что снимет ограничение возможности заглушать свойства с помощью sinon.stub
.
sandbox.replace
Создайте sandbox.replace
и используйте его для всех операций, которые заменяют что-либо в любом месте. Предоставьте это как sinon.replace
и используйте песочницу по умолчанию при использовании таким образом.
Это, вероятно, должно иметь серьезную проверку ввода, поэтому он заменит только функции на функции, средства доступа на средства доступа и т. д.
Пинг @sinonjs/ядро
Хорошие предложения, Морган. Спасибо, что подняли этот вопрос. Я также думаю, что API stub
вызывает путаницу, и мне нравятся все ваши предложения. Вот некоторые мысли:
Синон.фейк
Я согласен, что неизменность здесь ключевая. Однако мы могли бы разрешить некоторые «разумные» варианты использования, которые в настоящее время возможны с заглушками.
Например, допустимым вариантом использования может быть yield и return:
sinon.fake({
yields: [null, 42],
returns: true
})
Мы можем проверить, что имеет смысл, а что нет.
Кроме того, если мы поддерживаем callsThrough: true
в качестве конфигурации (что недопустимо в сочетании с любым из свойств поведения), новые подделки также могут использоваться вместо «шпионского» API. Это было бы более понятным, чем изучение того, что означает «шпион» и «заглушка» на синонском языке 😄
Использовать песочницу по умолчанию
Хотя мне нравится эта идея, это означает, что вызов sinon.restore()
после теста может отменить некоторые пережитки других тестов и привести к удивительным результатам — или неудачным тестам, которые раньше работали. Это позволит сбросить глобальную песочницу в beforeEach
, чтобы улучшить изоляцию тестов. 👍
песочница.replace
Мне это очень нравится. Я понимаю это как утилиту «просто дай мне воткнуть вот эту штуку», верно?
Кроме того, если мы будем поддерживать callThrough: true в качестве конфига (что недопустимо в сочетании с любым из свойств поведения), новые подделки также могут использоваться вместо «шпионского» API. Это было бы более понятным, чем изучение того, что означает «шпион» и «заглушка» на синонском языке 😄
Означает ли это, что нам вообще не нужны spy
или stub
?
песочница.replace
Мне это очень нравится. Я понимаю это как утилиту «просто дай мне воткнуть вот эту штуку», верно?
Да, это была идея. Вместо того, чтобы перегружать один и тот же метод ( sinon.stub
), чтобы делать много-много вещей, используйте явные методы, которые делают только одну вещь
Как вы подчеркнули, API fake
, вероятно, не будет поддерживать все, что в настоящее время возможно со шпионами и заглушками. Но да, я думаю, что API fake
— это возможность объединить функции stub
и spy
.
Хотя мне нравится эта идея, это означает, что вызов sinon.restore() после теста может вернуть некоторые остатки от других тестов и привести к удивительным результатам — или провальным тестам, которые раньше работали. Это позволит сбросить глобальную песочницу в beforeEach, чтобы улучшить изоляцию тестов. 👍
Это, безусловно, критическое изменение, и его не следует вводить легкомысленно.
При создании fake
, если вы не передадите ему конфигурацию поведения, это будет эквивалентно 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'
});
Как бы вы тогда создали заглушку, которая ничего не делает?
Как бы вы тогда создали заглушку, которая ничего не делает?
Я не уверен, что полностью понимаю ваш вопрос... но вот
// a fake that has no behaviour
const fake = sinon.fake();
// put it in place of an existing method
sandbox.replace(myObject, 'someMethod', fake);
Ах, кажется, теперь я понимаю, что вы имеете в виду: fake
— это всегда stub
. Когда вы сказали ~spy, records all calls
, я понял «вызов исходной функции». Однако fake
ничего не знает о функции, которую он заменяет — это то, что делает sandbox.replace
.
Имея это в виду, вот еще одно предложение, как мы могли бы свернуть текущую функциональность spy
(как при сквозном вызове) в новые подделки:
const fake = sinon.fake(function () {
// Any custom function
});
Данная функция будет вызываться подделкой. Этот API делает невозможным его смешивание с другими видами поведения. На самом деле объект конфигурации создаст функцию, которая реализует указанное поведение, а затем передаст ее подделке.
Тогда реализация sandbox.spy(object, method)
может стать такой:
const original = object[method];
const fake = sinon.fake(original);
sandbox.replace(object, method, fake);
В основном однострочный 🤓
Ага. Как только вы упростите вещи, вы сможете начать повторное микширование для удовольствия 🎉 и получения прибыли 💰
Однако, если мы хотим просто использовать fake
и больше не использовать spy
и stub
, то нам, вероятно, следует просто оставить эти два параметра в покое.
Я думаю о «следующем» API здесь. Вам понадобится sandbox.spy
, чтобы где-то иметь логику замены. Насколько я понимаю, это должно быть обратно совместимо. После этого реализация stub
может быть признана устаревшей.
Вам понадобится sandbox.spy, чтобы где-то иметь логику замены. Насколько я понимаю, это должно быть обратно совместимо. После этого реализация заглушки может быть признана устаревшей.
Я не уверен, что следую. Не могли бы вы уточнить?
Конечно. Насколько я понимаю ваше предложение, вы хотите заменить слишком сложный API stub
. В настоящее время заглушки реализуются путем создания spy
с функцией, реализующей поведение. Я предлагаю сделать то же самое с API fake
и внутренне создать spy
, но мы больше не будем возвращать поведение, потому что мы хотим избавиться от цепочки . Мы бы просто вернули шпиона. Это делает реализацию fake
альтернативой реализации stub
с возвращаемой функцией, совместимой со всеми текущими API Sinon. Это имеет смысл или я что-то упускаю?
Хорошо, я думаю, что у нас схожее понимание 👍
Просто повторюсь, на случай, если мы что-то пропустили, и чтобы у других участников было такое же понимание.
sandbox.replace
(в настоящее время она находится в stub
)sinon
будет иметь песочницу по умолчанию, позволяющую использовать sinon.reset
и sinon.restore
(мы должны просто объединить их?)sinon.fake
— неизменяемая программируемая замена функций, записывающая все вызовы.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);
В этом состоянии предложение касается только Function
. Нам нужно подумать, что делать с нефункциональными свойствами и средствами доступа. По крайней мере, мы должны посмотреть, сможем ли мы ограничить sandbox.replace
, чтобы разрешить только разумные замены.
Означает ли это, что sinon.stub()
и sinon.spy()
оба будут объявлены устаревшими в будущем в пользу sinon.fake()
или просто переделаны внутри? Если это так, то мы по сути движемся к мышлению TestDouble . Не обязательно плохо, ИМХО, но, возможно, стоит подумать о том, что если многие люди обнаружат, что им все равно придется заменить все свои вызовы API Sinon на sinon.fake()
, они могут просто использовать другую библиотеку (хотя это означало бы, что они потеряли бы все свои существующие знания об API Sinon).
Означает ли это, что и sinon.stub(), и sinon.spy() в будущем будут объявлены устаревшими в пользу sinon.fake(), или они будут переделаны внутри? Если это так, то мы, по сути, движемся к мышлению TestDouble.
Я предполагаю, что это действительно частично пересекается. Моя основная мотивация для этого предложения — иметь поддельные функции с неизменным поведением.
Не обязательно плохо, ИМХО, но, возможно, стоит подумать о том, что если многие люди обнаружат, что им все равно придется заменить все свои вызовы API Sinon на sinon.fake(), они могут просто использовать другую библиотеку (хотя это означало бы, что они потеряют все имеющиеся у них знания об API Sinon).
Если люди обнаружат, что другая библиотека лучше удовлетворяет их потребности, то я рад, что мы помогли им это понять :)
Но сохраним ли мы методы-шпионы и методы-заглушки или откажемся от них с возможным сокращением функциональности? Мне это было непонятно.
Но сохраним ли мы методы-шпионы и методы-заглушки или откажемся от них с возможным сокращением функциональности?
Как только fake
станет стабильным, я бы отказался от поддержки spy
и stub
, а затем дал бы им год, чтобы дать людям время на обновление.
Я думаю, мы должны сделать все возможное, чтобы предоставить кодмоды и отличную документацию, чтобы помочь людям перемещать свой код.
Я работаю над веткой для первых частей этого (песочница по умолчанию). Я реорганизовал код так, чтобы sandbox
и collection
теперь были одним целым. У меня работает песочница по умолчанию.
Я приведу в порядок коммиты в течение следующих нескольких дней, а затем создам ветку в этом репозитории для обновленного API.
Это отличная идея, кстати, очень хорошо написанная.
Я бы также добавил уведомления об устаревании для заглушек и шпионов.
Я также думал о том, чтобы изменить передачу объекта с помощью ключей с помощью функций.
Это добавит следующие преимущества:
type
к тем функциям для пользователей, которые хотят использовать typescript
или другие статические средства проверки.Поэтому вместо этого API будет выглядеть так:
// 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'));
Когда дело доходит до реализации этого, это может быть просто вопрос возврата очень простых объектов, таких как те, которые вы предлагаете. Затем, если у нас есть более одного поведения, мы можем просто объединить их.
Кроме того, когда дело доходит до смешивания таких вещей, как onThirdCall
и withArgs
, я думаю, что то, что происходит в этих случаях, должно быть задокументировано.
Извините, что просматриваю это так поздно. Последние несколько месяцев были очень заняты.
@lucasfcosta посмотрите PR #1586
Эта проблема была автоматически помечена как устаревшая, поскольку в последнее время с ней не было никаких действий. Он будет закрыт, если дальнейшая активность не будет выполняться. Спасибо за ваш вклад.
Предыдущая версия 5.0.0 вызывает проблемы с более поздними предварительными версиями 5.0.0-next.* в package.json, потому что 5.0.0 больше, чем любая предварительная версия.
Поскольку версия 5.0.0 вышла, я думаю, что предварительные цифры next
должны быть увеличены, возможно, до 5.0.1-next.1
?
Я заметил это, потому что другой пакет, который я использовал, получал устаревшее сообщение, а его package.json зависит от "sinon": "^5.0.0-next.4"
npm WARN deprecated [email protected]: this version has been deprecated
Я не был уверен, стоит ли открывать новую проблему для проблемы с предварительным выпуском, поэтому комментарий здесь казался самым безопасным.
Другим решением было бы выпустить следующую основную версию. Что вы думаете о @sinonjs/core?
@mroderick Я больше не могу сказать, каковы все изменения для v5. Судя по моим последним тестам, он работал нормально, и я с нетерпением жду возможности использовать новые подделки. Это новый мейджор, так что эй, отправляйте 😄
Есть еще один PR #1764, который я хотел бы объединить, прежде чем мы выпустим следующую основную версию.
Я опубликовал [email protected]
, надеюсь, тем временем это облегчит жизнь людям.
Спасибо, я протестировал (всегда полезно перепроверить) зависимости в package.json, и "sinon": "^5.0.1"
выдает ошибку, как и должно быть, потому что совпадений не найдено (еще нет выпуска), и "sinon": "^5.0.1-next.1"
работает правильно, получая эту версию.
Это никогда не было большой проблемой, я просто подумал, что стоит предупредить вас, особенно когда я увидел, что версия 5 некоторое время находилась в разработке, поэтому я не был уверен, как долго она будет выпущена. Я думаю, что выпуск в ближайшем будущем звучит как хорошая идея.
fake
был введен с #1768, который стал [email protected]
Самый полезный комментарий
Хорошо, я думаю, что у нас схожее понимание 👍
Просто повторюсь, на случай, если мы что-то пропустили, и чтобы у других участников было такое же понимание.
TL;DR
sandbox.replace
(в настоящее время она находится вstub
)sinon
будет иметь песочницу по умолчанию, позволяющую использоватьsinon.reset
иsinon.restore
(мы должны просто объединить их?)sinon.fake
— неизменяемая программируемая замена функций, записывающая все вызовы.sinon.spy
sinon.stub
В этом состоянии предложение касается только
Function
. Нам нужно подумать, что делать с нефункциональными свойствами и средствами доступа. По крайней мере, мы должны посмотреть, сможем ли мы ограничитьsandbox.replace
, чтобы разрешить только разумные замены.