Cucumber-js: Запрос функции: пользовательский интерфейс, в котором не используется ключевое слово this.

Созданный на 31 янв. 2017  ·  16Комментарии  ·  Источник: cucumber/cucumber-js

Учитывая такой мир, как:

require('cucumber').defineSupportCode( ({setWorldConstructor:world}) => {
    world(class { 
      constructor(p) { ... }
      foo(bar) { return new Promise(.... ) }
    })
})

Я хочу уметь писать:

  When(/^I do the foo with (.*)$/i, (ctx, bar) => ctx.foo(bar) );

вместо того

  When(/^I do the foo with (.*)$/i, function(bar) {
      return this.foo(bar)
  });

Это также избавит меня от обезьяны .bind(this) во всех асинхронных операциях, или, если хотите, избавится от обезьяны с помощью var that = this .

Еще одно преимущество (IMHO) - это поможет людям выйти из дерева наследования миров и лучше сориентировать их в сторону лучших возможностей JS.

accepted enhancement

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

Хорошо. Однако, поскольку не все являются функциональными программистами, и чтобы предотвратить внесение критических изменений, что вы, ребята, думаете о создании опции, которая заставляет мир передаваться в качестве первого параметра?

defineSupportCode(({setWorldInjectionStrategy}) => {
  setWorldInjectionStrategy('this') // default
  setWorldInjectionStrategy('argument') // makes world be the first argument to steps and hooks 
})

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

Я согласен с тем, что это будет означать, что вы всегда принимаете world качестве аргумента, и это изменение API.
Я думаю, что, как и мокко, в котором есть --ui exports --ui tdd и --ui bdd - cucue тоже может.
По сути, это переменная проекта, которая определяет, вызываются / применяются ли шаги в мире, или принимают их как 1-й аргумент.

Я действительно не думаю, что это того стоит в долгосрочной перспективе. Да, это хорошо для тех шагов в 1 строку, где вы можете использовать => там, где вы иначе не сможете. По моему опыту, таких шагов было очень мало. Я удивлен, что ES6 не включает стрелочную функцию, которая не сохраняет контекст, поскольку это было бы идеально для меня.

Это также избавит меня от обезьяны .bind(this) во всех асинхронных операциях или, если хотите, избавится от обезьяны с помощью var that = this .

Разве вы не можете использовать жирную стрелку внутри определений шагов, чтобы этого избежать?

Еще одно преимущество (IMHO) - это поможет людям выйти из дерева наследования миров и лучше сориентировать их в сторону лучших возможностей JS.

Можете ли вы пояснить это еще немного / привести пример

Проще говоря: я принадлежу к школе программистов, которые избегают формулировки контекста как класса. Чем больше людей знакомится с темной стороной ООП, эта школа становится все больше - и движение в сообществе JS, которое не позволяет стрелочной функции сохранять контекст, является примером силы этого движения.

Не вдаваясь в религиозные дебаты - я просто скажу, что верю в «живи и дай жить другим», вы хотите использовать занятия? Отлично. Пожалуйста , не заставляйте меня :-)

По моему общему мнению, это может быть небольшое изменение, и я буду рад это сделать, если вы дадите мне общее направление того, как, по вашему мнению, это будет сделано лучше всего ;-)

Когда мы определяем шаги, мы определяем функции, а не классы, поэтому логически this не имеет в нем места.

Я думаю, что это ситуация для парадигмы FP, а не для парадигмы ООП.

@osher Я думаю, мы можем дать @charlierudolph преимущество сомнения в том, что он не хочет заставлять вас использовать классы. Тем не менее, его не следует принуждать к интеграции (нарушению) изменений API везде, где он использует этот пакет, если только он не предлагает какое-либо ощутимое преимущество.

@charlierudolph Я думаю, что под «лучшими возможностями JS» @osher подразумевает FP, функциональное программирование.

IMHO это изменение сделало бы определения шагов немного проще и логичнее. По сути, это просто удалило бы эту строку: const thisWorldNotThisStep = this

Определенно нет необходимости обезопасить .bind(this) для всех ваших асинхронных функций, и пока в определении шага вы используете стрелочные функции, нет необходимости использовать шаблон const self = this .

Проблема (которая не является большой проблемой) заключается в том, что внутри определения шага this относится к миру, а не к шагу, что противоречит интуиции.

Хорошо. Однако, поскольку не все являются функциональными программистами, и чтобы предотвратить внесение критических изменений, что вы, ребята, думаете о создании опции, которая заставляет мир передаваться в качестве первого параметра?

defineSupportCode(({setWorldInjectionStrategy}) => {
  setWorldInjectionStrategy('this') // default
  setWorldInjectionStrategy('argument') // makes world be the first argument to steps and hooks 
})

Полностью согласен с идеей внедрить мир / контекст с параметром шага. Начиная с этого проекта, вся экосистема JS движется к тому, чтобы полностью охватить новые функции, разрешенные ES6, и это хорошо ❤️

Если вы посмотрите на другие инструменты, такие как Express или Koa, они устанавливают текущий контекст запроса как this AND в качестве первого параметра промежуточного программного обеспечения (это эквивалент шага огурца). Это решение позволяет использовать традиционные функции и стрелочные функции ES6.
Еще одно преимущество контекста в качестве первого параметра заключается в том, что

Что касается решения, предложенного @charlierudolph , я не думаю, что это может

Я не уверен, что нас беспокоят критические изменения, v2 - идеальное время для внесения таких изменений. Ожидание заставит сделать / дождаться другого крупного.

Как насчет того, чтобы мы изменили Before и After чтобы ввести world :

defineSupportCode(function({After, Before, Given}) {
  let world;

  // Asynchronous Callback
  Before(function (w, scenarioResult, callback) {
    world = w;
    callback();
  });
  After(function (world, scenarioResult, callback) {
    callback();
  });

  Given('I access the world', () => {
    assert(world); // Yay!
  });
});

Таким образом, вы можете зафиксировать world в переменной на шаге before . Это не испортит определение каждого шага.

Затем мы сохраняем старый способ работы, но генерируем сообщение deprecated . Или даже не делайте этого, просто оставьте вариант для людей, которые хотят использовать этот подход и знают, что они делают.

Боюсь, аргумент о совместимости фрагментов, найденных в Интернете, является веским аргументом.

Даже если будут --ui flags или setWorldInjectionStrategy('argument') - это становится ошибкой, о которой лучше сообщать как о критическом изменении основной версии, сопровождаемом всеми онлайн-обсуждениями и шумом, подобающим таким изменениям, и помогает устранить путаницу.

Так что я голосую за то, чтобы сделать это в следующей версии cuke, и ... македо, пока она не выйдет.

Канареечная версия или скрытый флаг были бы отличной рекламой, которую я бы использовал и откликнулся на раннем этапе

Я бы с удовольствием представил альтернативный интерфейс FP.

А как насчет следующего? (с использованием async / await, чтобы проиллюстрировать его удобство для обещаний)

import {initialize, Given, Before} from 'cucumber/fn'

// specify a function that returns the initial context:
initialize(async () => ({ a: 42 }))

Before({ timeout: 10 }, async (ctx) => {
  await doStuff(ctx.a)
})

Given(/^a step passes with {number}$/, async (ctx, number) => {
  const newA = await computeStuff(ctx.a, number)
  // tell cucumber about the new context:
  return Object.assign({}, ctx, { a: newA })
})

Я собрал вместе cucumber-fp, который предлагает функциональные определения шагов. У меня уже есть несколько идей по улучшению. Обратная связь приветствуется!

Я предлагаю закрыть эту проблему и позволить людям поэкспериментировать с этой небольшой библиотекой. Возможно, однажды мы сможем перенести его в Cucumber.js.

@jbpros Я бы предпочел не устанавливать еще одну зависимость, чтобы я мог использовать стрелочные функции в своих тестах.

Это должно быть действительно просто для всех, чтобы реализовать себя. Следующий фрагмент кода работает для меня очень хорошо (функционально) с огромной оговоркой, которая делает его непригодным для использования:

// Don't rely on `this` in step definitions. It's 2021 for crying out loud.
const definitionFunctionWrapper = (fn) =>
    function(...args) {
        return fn(...args.slice(0, -1), this);
    }

Это предупреждение, являющееся определением каждого отдельного шага, теперь регистрирует следующую ошибку из-за дополнительного параметра:

    Error: function uses multiple asynchronous interfaces: callback and promise
       to use the callback interface: do not return a promise
       to use the promise interface: remove the last argument to the function

Если вы попытаетесь добавить дополнительные параметры, чтобы обойти это, вы получите

function has 3 arguments, should have 1 (if synchronous or returning a promise) or 2 (if accepting a callback)

Все, что нужно cucumber-js, - это возможность отключить проверку аргументов функции, и эта проблема исчезнет. На самом деле, если подумать, похоже, что тест тоже истечет, так как огурец предполагает, что требуется обратный вызов на основе количества аргументов в функции определения шага. Однако это можно обойти, отдав приоритет возвращенному обещанию.

@andyearnshaw спасибо за ваш вклад, я слышал вашу озабоченность по поводу зависимости "только для стрелочных функций в stepdefs". Эта библиотека - в основном решение, которое я использую лично для получения определений шагов без сохранения состояния, я просто упаковал ее для всех, кто там заинтересован.

Считайте это временным решением продолжающихся дебатов о таком чистом API-интерфейсе stepdef в ядре (верите вы или нет, это продолжается уже более 4 лет). Как я уже сказал, это эксперимент, который в какой-то момент может оказаться в ядре, и я был бы очень признателен за отзывы людей, действительно использующих его. Если он получит достаточную тягу, это станет лучшим аргументом в пользу интеграции с огурцом.

Также обратите внимание, что он предлагает несколько других (небольших) полезных функциональных инструментов: tap() и принудительные контексты только для чтения.

Проверка arity для функций stepdef определенно является проблемой, которую мне пришлось обойти в этой библиотеке (довольно уродливым образом). Возможность отключить его как в интерфейсе командной строки, так и программно была бы очень полезна для этого (и, возможно, других вариантов использования). Я бы с удовольствием это сделал, но на данный момент время для меня - дефицитный ресурс.

Не стесняйтесь черпать вдохновение в cucumber-fp, чтобы пока что исправить проверку арности.

@jbpros , это здорово, и я очень ценю ваши усилия. Я был больше не согласен с мнением, что этот вопрос следует закрыть. Я посмотрю в вашу библиотеку и посмотрю, поможет ли она мне обойти эту надоедливую проверку. 🙂

@andyearnshaw правильно, спасибо за разъяснения. Я согласен, что мы не должны отказываться от этой идеи, и, вероятно, сохранение этого вопроса открытым - это действительно хороший способ сохранить прозрачность.

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