Cucumber-js: الاقتراح: API برمجي لتشغيل cucumber-js

تم إنشاؤها على ٢٨ يونيو ٢٠٢١  ·  29تعليقات  ·  مصدر: cucumber/cucumber-js

مشكلة

ليس لدينا حاليًا طريقة جيدة لتشغيل cucumber-js برمجيًا. الحاجة من زاويتين:

  • اختبار cucumber-js - لـ CCK واختبارات القبول لدينا في المشروع
  • اختبار التنسيقات والمقتطفات المخصصة
  • المشاريع التي تستخدم Cucumber-js كجزء من إطار عمل (مثل Serenity و Stryker)

كما هي

ما يحدث في الوقت الحالي هو إنشاء مثيل جديد من Cli باستخدام مدخلات argv مجمعة معًا . من الواضح أنه غير مرهون للغاية ولا يوجد أيضًا على واجهة برمجة التطبيقات العامة .

في بعض الأحيان (ربما بسبب الهشاشة المتصورة لما ورد أعلاه) ستعتمد الأطر فقط على Cucumber-js CLI ولكنها تكافح لإيجاد طرق للتكامل ولديها خياراتها الخاصة.

تعتبر فئة Runtime حاليًا جزءًا من واجهة برمجة التطبيقات العامة ولكنها ليست مفيدة في هذه السياقات ، اعتمادًا على المخللات ورمز الدعم الذي سيوفره المتصل.

عرض

مكونان في المشروع:

runCucumber

وظيفة غير متزامنة جديدة تنفذ اختبار تشغيل قيد التشغيل. المسؤوليات:

  • قبول كائن خيارات مع واجهة لطيفة
  • قم بـ "عمل" حل المخللات ، وتحميل كود الدعم ، وتشغيل حالات الاختبار ، وتنظيم المنسقات
  • أعد الوعد المصمم على نتيجة

سيكون هذا جزءًا من واجهة برمجة التطبيقات العامة ونحن نشجع المشرفين على إطار العمل لاستخدامه عند "تغليف" cucumber-js. سنستخدمه أيضًا للاختبار الخاص بنا.

قدر الإمكان ، سيتجنب التفاعل المباشر مع process ، بدلاً من قبول الخيارات العادية وواجهات التدفق للإخراج ، وترك الأمر للمتصل ليقرر كيفية الخروج بناءً على النتيجة أو خطأ غير معالج.

يجب أيضًا إزالة Runtime من واجهة برمجة التطبيقات العامة لأنها في الحقيقة شيء داخلي.

CLI

بشكل فعال "عميل" runCucumber . المسؤوليات:

  • تجميع الخيارات من مصادر مختلفة (argv ، env vars ، ملفات التكوين) (انظر التعليق )
  • اتصل بـ runCucumber مع الخيارات التي تم حلها
  • قم بالخروج بالشكل المناسب بناءً على النتائج

سيستمر هذا في عدم وجوده على واجهة برمجة التطبيقات العامة. كما أنها ستستخدم فقط الوظائف / الواجهات الموجودة على واجهة برمجة التطبيقات العامة ، بحيث يمكننا بسهولة تقسيمها إلى حزمة خاصة بها في مرحلة ما ، كما هو الحال الآن مع مشاريع مثل Jest .

يمهد هذا الفصل أيضًا الطريق لبعض ميزات CLI الجديدة المثيرة للاهتمام دون جعلها تتسرب إلى الأجزاء الداخلية ، على سبيل المثال:

  • --gui لأشياء الخيار والإلكترون
  • --interactive لعمليات إعادة التشغيل السريعة المستهدفة عند TDD'ing

إلخ

نكشف أيضًا عن الوظائف (التي يستهلكها CLI والآخرون) من أجل:

  • الحصول على الخيارات
  • التعامل مع i18nKeywords و i18nLanguages

الجدول الزمني

سنستهدف هذا في إصدار 8.0.0 القادم. أنا مستعد للبدء في هذا اليوم.

breaking change enhancement

التعليق الأكثر فائدة

كيف يعمل هذا لمراسل لا يحتاج إلى اسم ملف الإخراج؟

formats: {
    stdout: './my-awesome-stryker-formatter',
    files: {
      'report.html': './my/fancy-reporter.js',
      'other-report.html': '@me/reporter-package',
    }
  },

(يمكن لمنسق واحد فقط استخدام تيار stdout)

ال 29 كومينتر

ترحيل لردود الفعل الأولية:aslakhellesoycharlierudolph @ أوريليان-ريفزmattwynnenicojs @ يناير-molak

أنا أحب هذا الاقتراح! نحن بالفعل واجهة برمجة تطبيقات مثل هذا في RunCucumber الخاص بـ fake-cucumber

davidjgoss - يبدو رائعا!

كمرجع لك ، إليك كيفية استدعاء Serenity / JS للخيار في الوقت الحالي - CucumberCLIAdapter
وإليك المنطق حول تحويل معلمات التكوين إلى argv - CucumberOptions .

أن تكون قادرًا على توفير كائن خيارات بدلاً من argv سيكون أجمل بكثير

أحبها!

أثناء تحديد واجهة برمجة التطبيقات العامة الجديدة ، قد نفكر أيضًا في ما حدث مؤخرًا مع المشكلة رقم 1489 ونفكر في توفير واجهات برمجة تطبيقات عامة للحصول على تفاعل أكثر وأفضل مع المرشحات والميزات الناتجة قيد الاختبار

إن امتلاك واجهة برمجة تطبيقات عامة أفضل من لا شيء ، لذا انطلق 👍!

ويفضل أن يكون لدي أيضًا واجهة برمجة تطبيقات لتحميل ملفات التعريف باستخدام نفس القواعد التي يستخدمها cucumber-js ، لذا يمكنني تقليد السلوك الدقيق لاستدعاء cucumber-js العادي.

loadProfiles(directory = process.cwd()): Record<string, Profile>

ستعتمد StrykerJS أيضًا بشكل كبير على واجهة برمجة تطبيقات custom_formatters والأحداث المنشورة بواسطة eventBroadcaster . هل يمكننا إضافة هذه إلى واجهة برمجة التطبيقات العامة أيضًا؟ انظر: https://github.com/stryker-mutator/stryker-js/blob/03b1f20ed933d3a50b52022cfe363c606c2b16c5/packages/cucumber-runner/src/stryker-formatter.ts#L45 -L69

ويفضل أن يكون لدي أيضًا واجهة برمجة تطبيقات لتحميل ملفات التعريف باستخدام نفس القواعد التي يستخدمها cucumber-js ، حتى أتمكن من محاكاة السلوك الدقيق لاستدعاء cucumber-js العادي.

هذه نقطة جيدة. تقترن الملفات الشخصية (في شكلها الحالي على الأقل) بشكل أساسي بـ CLI لذا من الصواب الاحتفاظ بها على هذا الجانب من الحدود ، ولكن لا يزال بإمكاننا كشف وظيفة لتحميلها وإنشاء كائن خيارات جزئي.

أثناء تحديد واجهة برمجة التطبيقات العامة الجديدة ، قد نفكر أيضًا في ما حدث مؤخرًا مع المشكلة رقم 1489 ونفكر في توفير واجهات برمجة تطبيقات عامة للحصول على تفاعل أكثر وأفضل مع المرشحات والميزات الناتجة قيد الاختبار.

أعتقد أنه يمكننا تضمين خيار لتوفير مرشح مخلل مخصص عند استدعاء واجهة برمجة التطبيقات (بالإضافة إلى الأسماء والعلامات وما إلى ذلك التي تدفع التصفية المدمجة).

الصيغة الحالية لملفات التعريف إذا كانت سطر الأوامر y.

سأكون قادرًا على تحديد ملفات التعريف بتنسيق أكثر عمومية مثل JSON أو JavaScript أو YAML أو متغيرات البيئة. في JSON يمكن أن يبدو كالتالي:

.cucumber.json

{
  "default": {
    "requireModule": ["ts-node/register"],
    "require": ["support/**/*./ts"],
    "worldParameters": {
      "appUrl": "http://localhost:3000/",
    },
    "format": ["progress-bar", "html:./cucumber-report.html"]
  },
  "ci": {
    "requireModule": ["ts-node/register"],
    "require": ["support/**/*./ts"],
    "worldParameters": {
      "appUrl": "http://localhost:3000/",
    },
    "format": ["html:./cucumber-report.html"],
    "publish": true
  }
}

أو باستخدام JavaScript

.cucumber.js

const common = {
  "requireModule": ["ts-node/register"],
  "require": ["support/**/*./ts"],
  "worldParameters": {
    "appUrl": "http://localhost:3000/",
  }
}

module.exports = {
  default: {
    ...common,
    "format": ["progress-bar", "html:./cucumber-report.html"]
  },
  ci: {
    ...common,
    "format": ["html:./cucumber-report.html"],
    "publish": true
  }
}

أو حتى مع متغيرات البيئة (على سبيل المثال محملة بأداة مثل dotenv):

.cucumber.env

CUCUMBER_PROFILE_DEFAULT_REQUIREMODULE=ts-node/register
CUCUMBER_PROFILE_DEFAULT_REQUIRE=ts-node/register
CUCUMBER_PROFILE_DEFAULT_WORLDPARAMETERS_APPURL=http://localhost:3000/
CUCUMBER_PROFILE_DEFAULT_FORMAT=progress-bar,html:./cucumber-report.html
CUCUMBER_PROFILE_CI_REQUIREMODULE=ts-node/register
CUCUMBER_PROFILE_CI_REQUIRE=ts-node/register
CUCUMBER_PROFILE_CI_WORLDPARAMETERS_APPURL=http://localhost:3000/
CUCUMBER_PROFILE_CI_FORMAT=progress-bar,html:./cucumber-report.html
CUCUMBER_PROFILE_CI_PUBLISH=true

في الواقع ، تقوم مكتبة التكوين بهذا بالضبط. لم ننتهي أبدًا بدمجه في Cucumber-JVM لأن أشياء أخرى تعترض طريقنا ، لكن ربما يمكننا أن نجربها باستخدام تطبيق JavaScript؟

aslakhellesoy توافق على أنه سيكون رائع! سأحاول الحصول على POC لهذا الاقتراح لذلك لدينا شيء أكثر واقعية للتحدث حوله ، ونود أن نجعل الملفات الشخصية جزءًا منه (4.5 سنوات والاعتماد على # 751 😄)

الحكام. # 1004

هذه نقطة جيدة. تقترن الملفات الشخصية (في شكلها الحالي على الأقل) بشكل أساسي بـ CLI لذا من الصواب الاحتفاظ بها على هذا الجانب من الحدود ، ولكن لا يزال بإمكاننا كشف وظيفة لتحميلها وإنشاء كائن خيارات جزئي.

نعم ، سيكون ذلك رائعًا ويحظى بتقدير كبير من وجهة نظر منشئي المكونات الإضافية.

سأكون قادرًا على تحديد ملفات التعريف بتنسيق أكثر عمومية مثل JSON أو JavaScript أو YAML أو متغيرات البيئة. في JSON يمكن أن يبدو كالتالي:

هذا يبدو رائعًا! وهو أيضًا سبب تقديري لواجهة برمجة التطبيقات لتحميلها بنفس الطريقة التي يقوم بها cucumberJS. تحميل ملف واحد cucumber.js تافه. يعد تكرار خوارزمية تحميل ملف التكوين ، بما في ذلك الأسبقية وتنسيق الملف وما إلى ذلك والحفاظ عليه شيئًا آخر تمامًا 😅.

س: هل سأتمكن من تشغيل runCucumber مرتين متتاليتين _ دون مسح ذاكرة التخزين المؤقت المطلوبة _؟ هذا مهم لحالة استخدام اختبار الطفرات.

نريد تحميل البيئة وتشغيل الاختبارات عدة مرات في تتابع سريع أثناء تغيير متغير عالمي من أجل تبديل المسوخ النشط.

في الوقت الحالي ، نستخدم واجهة برمجة التطبيقات الخاصة cli ونحتاج إلى مسح ملفات تعريف الخطوة من require.cache بين كل تشغيل اختباري. هذا ليس مثاليًا لـ CommonJS ولن يعمل على الإطلاق لـ esm.

الكود الزائف لحالة الاستخدام الخاصة بنا:

const profiles = await loadProfiles();
const options = {
  ...profiles.default,
  formatter: require.resolve('./our-awesomely-crafted-formatter'),
  some: 'other options we want to override',
}
const cucumber = new Cucumber(options);

// Allow cucumber to load the step definitions once. 
// This is `async`, so support for esm can be added without a breaking change
await cucumber.initialize();

// Initial test run ("dry run"), without mutants active
await cucumber.run();

collectMutantCoveragePerTestFromFormatter();

// Start mutation testing:

global.activeMutant = 1;
await cucumber.run({ features: ['features/a.feature:24']);
collectResultsFromFormatterToDetermineKilledOrSurvivedMutant()

global.activeMutant = 2;
await cucumber.run({ features: ['features/b.feature:24:25:26', 'features/c.feature:12']);
collectResultsFromFormatterToDetermineKilledOrSurvivedMutant()

// etc

نوافق علىnicojs بالتأكيد أننا بحاجة إلى هذا ، لقد تم

ما كنت قد رسمته حتى الآن كان أكثر من أسلوب وظيفي ولكن نفس المفهوم الأساسي على ما أعتقد:

const runnerOptions = {
    support: {
        require: ['features/support/**/*.js']
    }
}

// initial run returns support code library
const { support } = await runCucumber(runnerOptions)

// subsequent run reuses support code library
await runCucumber({
    ...runnerOptions,
    support
})

هذا يعمل معنا 👍

يعمل معنا أيضًا 👍🏻

أعتقد حقًا أن شيئًا كهذا سيكون مفيدًا للغاية كبديل للفوضى العظيمة التي نعيشها اليوم من خلال اختبار تكامل الأدوات (Jest ، Cypress) ، على سبيل المثال ، وجدت هذه المشكلات (بترتيب الأهمية):

  • لا يدعم cypress-cucumber-preprocessor العلامات الموجودة في الأمثلة (https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/issues/196)
  • لا يدعم jest-cucumber الإبلاغ عن خيار JSON (https://github.com/bencompton/jest-cucumber/issues/27)
  • ينشئ cypress-cucumber-preprocessor عدة تقارير JSON للخيار بدون دعم رسمي للتجميع (https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/issues/423)
  • jest-cucumber ليس ملائمًا مثل jest-cucumber-fusion
  • هناك أيضًا cucumber-jest ...
  • لم يعد لدى Karma تطبيق عملي بعد الآن (https://github.com/cucumber/cucumber-js/issues/1095)

أفضل أن أرى بعض كود الغراء الأدنى بين Jest / Karma / Cypress / إلخ. و cucumber-js حتى لا أعاني من كل تلك الميزات المفقودة التي أحتاج إلى استخدامها.

اقتراح عظيم @ davidjgoss 👍

هذا الفصل في الاهتمامات بين واجهة مستخدم سطر الأوامر و "منطق العمل" لتحليل السيناريوهات وتنفيذها كاختبارات تذكرني بنمط البنية السداسية .

في cucumber-ruby ، ​​قمنا بتقسيم منطق المجال الأساسي (أو "السداسي الداخلي") إلى حزمة جوهرة منفصلة ، حيث كنا نعيد بنائها من نقطة الصفر في "غرفة نظيفة". أدرك أن هذا ليس هو السياق هنا ، ولكن قد يكون من المفيد استلهام بعض الإلهام أو إعادة تغذية الابتكارات من هذا التصميم إلى واجهة برمجة تطبيقات Ruby. يوجد مثال في README الخاص بجوهرة الخيار والياقوت الأساسية حول كيفية استخدام واجهة برمجة التطبيقات هذه.

حسنًا ، هذا هو المرور الأول في توقيع API لجزء "التشغيل". يعتمد بشكل كبير على الكائن IConfiguration الذي لدينا داخليًا (لذلك لا ينبغي أن يتسبب في إعادة بناء حجم كبير جدًا لأسفل) ولكن أقل "ثابت" قليلاً:

export interface IRunCucumberOptions {
  cwd: string
  features: {
    defaultDialect?: string
    paths: string[]
  }
  filters: {
    name?: string[]
    tagExpression?: string
  }
  support:
    | {
        transpileWith?: string[]
        paths: string[]
      }
    | ISupportCodeLibrary
  runtime: {
    dryRun?: boolean
    failFast?: boolean
    filterStacktraces?: boolean
    parallel?: {
      count: number
    }
    retry?: {
      count: number
      tagExpression?: string
    }
    strict: boolean
    worldParameters?: any
  }
  formats: {
    stdout: string
    files: Record<string, string>
    options: IParsedArgvFormatOptions
  }
}

export interface IRunResult {
  success: boolean
  support: ISupportCodeLibrary
}

export async function runCucumber(
  options: IRunCucumberOptions
): Promise<IRunResult> {
  // do stuff
}

واستخدام مثال مفتعل للغاية:

const result = await runCucumber({
  cwd: process.cwd(),
  features: {
    paths: ['features/**/*.feature'],
  },
  filters: {
    name: ['Acme'],
    tagExpression: '<strong i="10">@interesting</strong>',
  },
  support: {
    transpileWith: ['ts-node'],
    paths: ['features/support/**/*.ts'],
  },
  runtime: {
    failFast: true,
    retry: {
      count: 1,
      tagExpression: '<strong i="11">@flaky</strong>',
    },
    strict: true,
    worldParameters: {
      foo: 'bar',
    },
  },
  formats: {
    stdout: '@cucumber/pretty-formatter',
    files: {
      'report.html': 'html',
      'TEST-cucumber.xml': 'junit',
    },
    options: {
      printAttachments: false,
    },
  },
})

نرحب بالتعليقات! لاحظ أن هذا لا يغطي عناصر تحميل الملف الشخصي / التكوين والتي ستكون وظيفة أخرى.

أعتقد أن هذا يبدو جيدًا. سؤال: كيف يمكنني تكوين منسق مخصص؟

nicojs شيء مثل على CLI

formats: {
    files: {
      'report.html': './my/fancy-reporter.js',
      'other-report.html': '@me/reporter-package',
    }
  },

من الرائع رؤية التقدم في هذاdavidjgoss!

لا أريد إبطاء التقدم في هذا الأمر ، ولكن في نفس الوقت أريد التأكد من اعتمادنا تنسيقًا يمكن أن يعمل مع تطبيقات Cucumber الأخرى أيضًا.

في النهاية مخطط JSON ، ولكن أثناء مناقشته ، أعتقد أن أنواع TypeScript أسهل بالنسبة لنا كبشر في التحليل.

أقترح إنشاء مشكلة جديدة حول التنسيق المقترح في cucumber/common monorepo وندعو الفريق الأساسي للمناقشة هناك.

aslakhellesoy سوف تفعل.

ما رأيك في عدم ربط واجهة برمجة التطبيقات البرمجية بهيكل الخيارات الشائعة؟ مثلما كنا نرسم من ذلك إلى خيارات runCucumber . ربما تضيف بعض التعقيد ولكنها جذابة بسبب أشياء مثل وجود كتلة support تكون إما معلمات يتم تحميلها أو مكتبة رموز دعم تم تحميلها مسبقًا. يمكن أن تفعل الشيء نفسه بالنسبة للميزات + المخللات أيضًا. وهناك العديد من الخيارات التي ندعمها على CLI (على سبيل المثال --exit ) والتي لا تناسب واجهة برمجة التطبيقات البرمجية.

ما رأيك في عدم ربط واجهة برمجة التطبيقات البرمجية بهيكل الخيارات الشائعة؟

أعتقد أن هذا جيد ، طالما أننا نقدم وظيفة تتحول من محتويات ملف الخيارات إلى بنية البيانات runCucumber يريدها.

في النهاية مخطط JSON ، ولكن أثناء مناقشته ، أعتقد أن أنواع TypeScript أسهل بالنسبة لنا كبشر في التحليل.

لماذا نحتاج للاختيار؟ نحن نستخدم مخطط JSON في StrykerJS لإنشاء نص مكتوب باستخدام prebuild الخطوة.

لا تزال مخططات JSON قابلة للقراءة إلى حد ما بالنسبة للبشر IMO. لدينا بالفعل علاقات عامة على Stryker repo ويبدو أن الناس يعرفون ما يجب عليهم فعله 🤷‍♀️

شيء مثل على CLI

formats: {
    files: {
      'report.html': './my/fancy-reporter.js',
      'other-report.html': '@me/reporter-package',
    }
  },

كيف يعمل هذا لمراسل لا يحتاج إلى اسم ملف الإخراج؟ مثل ذلك:

formats: {
  files: {
    '': require.resolve('./my-awesome-stryker-formatter')
  }
}

لماذا نحتاج للاختيار؟

أعتقد أننا يجب أن نستخدم مخطط JSON كمصدر وحيد للحقيقة لهيكل التكوين. -ثم قم بإنشاء TypeScript / Java / Whatever code من هذا المخطط.

لكن مخطط JSON يصعب قراءته قليلاً بالنسبة للبشر ، لذلك بينما نناقش المخطط في مشكلة GitHub في cucumber/common كنت أقترح TypeScript لتسهيل المناقشة.

هل تفهم ما اعني؟

لا تزال مخططات JSON قابلة للقراءة إلى حد ما بالنسبة للبشر IMO

ليس لي :-) مطول جدا.

كيف يعمل هذا لمراسل لا يحتاج إلى اسم ملف الإخراج؟

formats: {
    stdout: './my-awesome-stryker-formatter',
    files: {
      'report.html': './my/fancy-reporter.js',
      'other-report.html': '@me/reporter-package',
    }
  },

(يمكن لمنسق واحد فقط استخدام تيار stdout)

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

bmsoko picture bmsoko  ·  7تعليقات

edwinwright picture edwinwright  ·  3تعليقات

pellekrogholt picture pellekrogholt  ·  3تعليقات

protoman92 picture protoman92  ·  3تعليقات

lamartire picture lamartire  ·  6تعليقات