Sinon: Идея для будущей вехи

Созданный на 13 сент. 2017  ·  31Комментарии  ·  Источник: sinonjs/sinon

Задний план

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
  • значение yield для обратного вызова

Если вам нужны/нужны побочные эффекты, и вам все еще нужен шпионский интерфейс, то либо используйте настоящую функцию, либо используйте 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
});

Использует шпионский API

За исключением .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 лучшим существительным для использования здесь, но я думаю, что мы должны попытаться придерживаться соглашения об использовании существительных и не отклоняться от прилагательных или глаголов.

Предлагаемые изменения API

Использовать песочницу по умолчанию

Это то, о чем я думал некоторое время, почему бы нам не сделать песочницу по умолчанию? Если людям нужны отдельные песочницы, они все равно могут их создать.

Мы должны создать песочницу по умолчанию, которая будет использоваться для всех методов, доступных через sinon.* .
Это означает, что sinon.stub станет таким же, как sandbox.stub , что снимет ограничение возможности заглушать свойства с помощью sinon.stub .

sandbox.replace

Создайте sandbox.replace и используйте его для всех операций, которые заменяют что-либо в любом месте. Предоставьте это как sinon.replace и используйте песочницу по умолчанию при использовании таким образом.

Это, вероятно, должно иметь серьезную проверку ввода, поэтому он заменит только функции на функции, средства доступа на средства доступа и т. д.

Feature Request Improvement Needs investigation pinned

Самый полезный комментарий

Хорошо, я думаю, что у нас схожее понимание 👍

Просто повторюсь, на случай, если мы что-то пропустили, и чтобы у других участников было такое же понимание.

TL;DR

  • все замены будут выполняться новой утилитой: 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 , чтобы разрешить только разумные замены.

Все 31 Комментарий

Пинг @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. Это имеет смысл или я что-то упускаю?

Хорошо, я думаю, что у нас схожее понимание 👍

Просто повторюсь, на случай, если мы что-то пропустили, и чтобы у других участников было такое же понимание.

TL;DR

  • все замены будут выполняться новой утилитой: 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 или другие статические средства проверки.
  • Пользователи будут получать ошибки при попытке вызвать функции для несуществующего поведения.
  • Мы могли бы задокументировать эти функции отдельно и сделать документацию еще лучше.
  • Мы могли бы предоставить полезные ошибки при передаче аргументов, которые не имеют смысла для этого поведения, и разрешить им иметь необязательные/более одного аргумента.
  • Это также сделало бы вещи более компонуемыми (хотя я не вижу много случаев для этого в этом случае) и позволило бы людям повторно использовать созданные поведения.
  • IMO, это также было бы проще, чем иметь объект с поведением

Поэтому вместо этого 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]

Была ли эта страница полезной?
0 / 5 - 0 рейтинги