Cucumber-js: Proposal: API terprogram untuk menjalankan mentimun-js

Dibuat pada 28 Jun 2021  ·  29Komentar  ·  Sumber: cucumber/cucumber-js

Masalah

Saat ini kami tidak memiliki cara yang baik untuk menjalankan mentimun-js secara terprogram. Kebutuhan itu dari dua sudut:

  • Menguji mentimun-js - untuk CCK dan tes penerimaan kami dalam proyek
  • Menguji pemformat dan cuplikan khusus
  • Proyek yang menggunakan mentimun-js sebagai bagian dari kerangka kerja (misalnya Serenity, Stryker)

Dengan adanya

Yang cenderung terjadi saat ini adalah instance baru dari Cli dibuat dengan input argv strung-together . Ini jelas sangat berat dan juga tidak ada di API publik .

Terkadang (mungkin karena kerentanan yang dirasakan di atas) kerangka kerja hanya akan bergantung pada CLI mentimun-js tetapi berjuang untuk menemukan cara untuk mengintegrasikan dan memiliki opsi mereka sendiri.

Kelas Runtime saat ini merupakan bagian dari API publik tetapi tidak berguna dalam konteks ini, tergantung pada acar dan kode dukungan yang akan disediakan oleh pemanggil.

Usul

Dua komponen dalam proyek:

runCucumber

Fungsi asinkron baru yang menjalankan uji coba dalam proses. tanggung jawab:

  • Terima objek opsi dengan antarmuka yang bagus
  • Lakukan "pekerjaan" untuk menyelesaikan acar, memuat kode dukungan, menjalankan kasus uji, mengatur formatter
  • Kembalikan janji yang menghasilkan hasil

Ini akan menjadi bagian dari API publik dan kami mendorong pengelola kerangka kerja untuk menggunakannya saat "membungkus" mentimun-js. Kami juga akan menggunakannya untuk pengujian kami sendiri.

Sebisa mungkin, itu akan menghindari interaksi langsung dengan process , alih-alih menerima opsi yang dinormalisasi dan antarmuka aliran untuk keluaran, dan menyerahkannya kepada pemanggil untuk memutuskan bagaimana keluar berdasarkan hasil atau kesalahan yang tidak tertangani.

Juga Runtime harus keluar dari API publik karena ini benar-benar hal internal.

CLI

Secara efektif "klien" dari runCucumber . tanggung jawab:

  • Opsi agregat dari berbagai sumber (argv, env vars, file konfigurasi) (lihat komentar )
  • Hubungi runCucumber dengan opsi yang diselesaikan
  • Keluar sebagaimana mestinya berdasarkan hasil

Ini akan terus tidak ada di API publik. Juga hanya akan menggunakan fungsi/antarmuka yang ada di API publik, sehingga kami dapat dengan mudah memecahnya menjadi paketnya sendiri di beberapa titik, seperti pola umum sekarang dengan proyek-proyek seperti Jest .

Pemisahan ini juga membuka jalan bagi beberapa fitur CLI baru yang menarik tanpa harus mengeluarkannya ke internal, misalnya:

  • --gui untuk barang-barang elektron-ketimun
  • --interactive untuk tayangan ulang yang ditargetkan dengan cepat saat TDD'ing

dll

Kami juga akan mengekspos fungsi (dapat dikonsumsi oleh CLI dan oleh orang lain) untuk:

  • Mendapatkan opsi
  • Menangani i18nKeywords dan i18nLanguages

Skala waktu

Kami akan menargetkan ini pada rilis 8.0.0 mendatang. Saya siap untuk memulai ini hari ini.

breaking change enhancement

Komentar yang paling membantu

Bagaimana cara kerjanya untuk reporter yang tidak memerlukan nama file keluaran?

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

(hanya satu formatter yang dapat menggunakan aliran stdout)

Semua 29 komentar

Paging untuk umpan balik awal: @aslakhellesoy @charlierudolph @aurelien-reeves @mattwynne @nicojs @jan-molak

Saya suka proposal ini! Kami sudah memiliki API seperti ini di runCucumber timun

@davidjgoss - kedengarannya bagus!

Untuk referensi Anda, inilah cara Serenity/JS memanggil Mentimun saat ini - CucumberCLIAdapter
Dan inilah logika seputar mengubah parameter konfigurasi menjadi argv - CucumberOptions .

Mampu memberikan objek opsi alih-alih argv akan jauh lebih baik 👍🏻

Suka sekali!

Saat menentukan API publik baru itu, kami juga dapat mempertimbangkan apa yang baru-baru ini terjadi dengan masalah #1489 dan berpikir tentang menyediakan API publik untuk memiliki interaksi yang lebih banyak dan lebih baik dengan filter dan fitur yang dihasilkan yang sedang diuji

Memiliki API publik lebih baik daripada tidak sama sekali, jadi silakan 👍!

Sebaiknya saya juga memiliki API untuk memuat profil menggunakan aturan yang sama seperti yang dilakukan cucumber-js , jadi saya dapat meniru perilaku yang tepat dari panggilan mentimun-js normal.

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

StrykerJS juga akan sangat bergantung pada custom_formatters API dan event yang diterbitkan oleh eventBroadcaster . Bisakah kita menambahkannya ke API publik juga? Lihat: https://github.com/stryker-mutator/stryker-js/blob/03b1f20ed933d3a50b52022cfe363c606c2b16c5/packages/cucumber-runner/src/stryker-formatter.ts#L45 -L69

Sebaiknya saya juga memiliki API untuk memuat profil menggunakan aturan yang sama seperti yang dilakukan mentimun-js, jadi saya dapat meniru perilaku yang tepat dari panggilan mentimun-js normal.

Ini adalah poin yang bagus. Profil (setidaknya dalam bentuknya saat ini) pada dasarnya digabungkan ke CLI sehingga rasanya tepat untuk menyimpannya di sisi batas itu, tetapi kami masih dapat mengekspos fungsi untuk memuatnya dan menghasilkan objek opsi parsial.

Saat menentukan API publik baru itu, kami juga dapat mempertimbangkan apa yang baru-baru ini terjadi dengan masalah #1489 dan berpikir tentang menyediakan API publik untuk memiliki interaksi yang lebih banyak dan lebih baik dengan filter dan fitur yang dihasilkan yang sedang diuji.

Saya pikir kami dapat menyertakan opsi untuk menyediakan filter acar khusus saat memanggil API (selain nama, tag, dll. yang mendorong pemfilteran bawaan).

Sintaks saat ini untuk profil jika sangat baris perintah-y.

Saya akan ❤️❤️❤️ untuk dapat menentukan profil dalam format yang lebih umum seperti JSON, JavaScript, YAML atau variabel lingkungan. Di JSON itu bisa terlihat seperti ini:

.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
  }
}

Atau, menggunakan 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
  }
}

Atau bahkan dengan variabel lingkungan (misalnya dimuat dengan alat seperti 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

Faktanya, perpustakaan konfigurasi melakukan hal ini. Kami tidak pernah akhirnya mengintegrasikannya di Mentimun-JVM karena hal-hal lain menghalangi, tapi mungkin kami bisa mencobanya dengan implementasi JavaScript?

@aslakhellesoy setuju itu akan luar biasa! Saya akan mencoba dan mendapatkan POC untuk proposal ini sehingga kami memiliki sesuatu yang sedikit lebih konkret untuk dibicarakan, dan akan senang untuk membuat profil yang benar sebagai bagian dari itu (4,5 tahun dan mengandalkan #751 )

Referensi #1004

Ini adalah poin yang bagus. Profil (setidaknya dalam bentuknya saat ini) pada dasarnya digabungkan ke CLI sehingga rasanya tepat untuk menyimpannya di sisi batas itu, tetapi kami masih dapat mengekspos fungsi untuk memuatnya dan menghasilkan objek opsi parsial.

Ya, itu akan luar biasa dan sangat dihargai dari sudut pandang pembuat plugin.

Saya akan ❤️❤️❤️ untuk dapat menentukan profil dalam format yang lebih umum seperti JSON, JavaScript, YAML atau variabel lingkungan. Di JSON itu bisa terlihat seperti ini:

Kedengarannya bagus! Dan juga merupakan alasan saya akan menghargai API untuk memuatnya dengan cara yang sama seperti yang dilakukan mentimun. Memuat satu file cucumber.js adalah hal yang sepele. Mereplikasi algoritma pemuatan file konfigurasi, termasuk prioritas, format file, dll DAN mempertahankannya adalah sesuatu yang sama sekali berbeda .

T: Apakah saya dapat menjalankan runCucumber dua kali berturut-turut _tanpa mengosongkan cache yang diperlukan _? Ini penting untuk kasus penggunaan pengujian mutasi.

Kami ingin memuat lingkungan dan menjalankan tes beberapa kali berturut-turut dengan cepat sambil mengubah variabel global untuk mengganti mutan aktif.

Saat ini, kami menggunakan API pribadi cli dan kami perlu menghapus file definisi langkah dari require.cache antara setiap uji coba. Ini tidak ideal untuk CommonJS dan tidak akan berfungsi sama sekali untuk esm.

Kode pseudo dari kasus penggunaan kami:

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 pasti setuju kita membutuhkan ini, ini muncul beberapa kali sebelumnya dengan misalnya orang yang ingin menjalankan mentimun di lambda, dan saya juga ingin menambahkan mode interaktif yang juga membutuhkannya.

Apa yang telah saya gambarkan sejauh ini lebih merupakan gaya fungsional tetapi konsep dasar yang sama menurut saya:

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
})

Itu bekerja untuk kita 👍

Bekerja untuk kita juga 👍🏻

Saya benar-benar berpikir bahwa sesuatu seperti ini akan sangat berguna sebagai alternatif untuk kekacauan besar yang kita miliki saat ini dengan integrasi alat pengujian (Jest, Cypress), misalnya saya menemukan masalah ini (dalam urutan kepentingan):

  • cypress-cucumber-preprocessor tidak mendukung tag pada Contoh (https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/issues/196)
  • jest-cucumber tidak mendukung pelaporan JSON Mentimun (https://github.com/bencompton/jest-cucumber/issues/27)
  • cypress-cucumber-preprocessor menghasilkan beberapa laporan Cucumber JSON tanpa dukungan resmi untuk agregasi (https://github.com/TheBrainFamily/cypress-cucumber-preprocessor/issues/423)
  • jest-cucumber tidak senyaman jest-cucumber-fusion
  • ada juga cucumber-jest ...
  • Karma tidak memiliki implementasi yang berfungsi lagi (https://github.com/cucumber/cucumber-js/issues/1095)

Saya lebih suka melihat beberapa kode lem minimal antara Jest/Karma/Cypress/etc. dan mentimun-js jadi saya tidak perlu menderita untuk semua fitur yang hilang yang perlu saya gunakan.

Saran bagus @davidjgoss 👍

Pemisahan masalah antara antarmuka pengguna baris perintah dan "logika bisnis" dari penguraian dan eksekusi skenario saat pengujian mengingatkan saya pada pola arsitektur heksagonal .

Dalam mentimun-ruby kami benar-benar membagi logika domain inti (atau "segi enam bagian dalam") menjadi paket permata terpisah, saat kami membangunnya kembali dari awal di "ruang bersih". Saya menyadari bahwa bukan itu konteksnya di sini, tetapi mungkin ada baiknya menarik beberapa inspirasi dari, atau memberi umpan balik inovasi dari desain ini ke dalam API Ruby. Ada contoh di README permata mentimun-ruby-core tentang cara menggunakan API itu.

Oke, ini pass pertama di tanda tangan API untuk bit "jalankan". Ini sangat didasarkan pada objek IConfiguration kami miliki secara internal (jadi seharusnya tidak menyebabkan terlalu banyak refactoring ke bawah) tetapi hanya sedikit kurang "datar":

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
}

Dan contoh penggunaan yang sangat dibuat-buat:

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,
    },
  },
})

Umpan balik selamat datang! Perhatikan ini tidak mencakup hal-hal pemuatan profil/konfigurasi yang akan menjadi fungsi lain.

Saya pikir ini terlihat bagus. Pertanyaan: Bagaimana cara mengonfigurasi pemformat khusus?

@nicojs agak seperti di CLI

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

Senang melihat kemajuan @davidjgoss ini!

Saya tidak ingin memperlambat kemajuan dalam hal ini, tetapi pada saat yang sama saya ingin memastikan kami mengadopsi format yang dapat bekerja untuk implementasi Mentimun lainnya juga.

Akhirnya skema JSON, tetapi sementara kami membahasnya, saya pikir tipe TypeScript lebih mudah bagi kita manusia untuk menguraikannya.

Saya sarankan kita membuat masalah baru tentang format yang diusulkan di cucumber/common monorepo dan mengundang tim inti untuk berdiskusi di sana.

@aslakhellesoy akan melakukannya.

Apa pendapat Anda tentang API terprogram yang tidak terikat dengan struktur opsi umum? Seperti kami memetakan dari itu ke opsi runCucumber . Ini mungkin menambahkan sedikit kerumitan tetapi menarik karena hal-hal seperti memiliki blok support yang merupakan parameter untuk dimuat, atau pustaka kode dukungan yang dimuat sebelumnya. Bisa melakukan hal serupa untuk fitur + acar juga. Dan ada berbagai opsi yang akan kami dukung pada CLI (misalnya --exit ) yang tidak sesuai pada API terprogram.

Apa pendapat Anda tentang API terprogram yang tidak terikat dengan struktur opsi umum?

Saya pikir tidak apa-apa, selama kami menyediakan fungsi yang mengubah dari konten file opsi ke struktur data yang diinginkan runCucumber .

Akhirnya skema JSON, tetapi sementara kami membahasnya, saya pikir tipe TypeScript lebih mudah bagi kita manusia untuk menguraikannya.

Mengapa kita perlu memilih? Kami menggunakan skema JSON di StrykerJS untuk menghasilkan TypeScript menggunakan json-schema-to-typescript . Kami tidak mengkomit file keluaran TS ke kontrol sumber, tetapi kami membuatnya dengan cepat menggunakan langkah prebuild .

Skema JSON masih dapat dibaca oleh IMO manusia. Kami sudah memiliki PR di repo Stryker dan orang-orang tampaknya tahu apa yang harus dilakukan ️

agak seperti di CLI

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

Bagaimana cara kerjanya untuk reporter yang tidak memerlukan nama file keluaran? Seperti:

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

Mengapa kita perlu memilih?

Saya pikir kita harus menggunakan Skema JSON sebagai sumber kebenaran tunggal untuk struktur konfigurasi. -Dan kemudian buat kode TypeScript/Java/Apa pun dari skema itu.

Tetapi Skema JSON agak sulit dibaca untuk manusia, jadi saat kita mendiskusikan skema dalam masalah GitHub di cucumber/common saya menyarankan TypeScript untuk memfasilitasi diskusi.

Lihat apa yang saya maksud?

Skema JSON masih agak dapat dibaca untuk manusia IMO

Tidak bagi saya :-) Terlalu bertele-tele.

Bagaimana cara kerjanya untuk reporter yang tidak memerlukan nama file keluaran?

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

(hanya satu formatter yang dapat menggunakan aliran stdout)

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

charlierudolph picture charlierudolph  ·  17Komentar

charlierudolph picture charlierudolph  ·  19Komentar

D0rmouse picture D0rmouse  ·  29Komentar

mobygeek picture mobygeek  ·  21Komentar

osher picture osher  ·  16Komentar