window.pipedriveLeadboosterConfig={です。 ベース:'leadbooster-chat.pipedrive.com'、 companyId:11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2、 } ;(function () { var w = window もし (w.LeadBooster) {なら console.warn('LeadBooster already exists') } else { w.LeadBooster = { {. q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: 関数 (n) { { this.q.push({ t: 'o', n: n, h: h }) this.q.push({ t: 't', n: n }) }, } } })() ジャバスクリプトのテスト...WITH RUBY?- The Codest
The Codest
  • 会社概要
  • サービス
    • ソフトウェア開発
      • フロントエンド開発
      • バックエンド開発
    • Staff Augmentation
      • フロントエンド開発者
      • バックエンド開発者
      • データエンジニア
      • クラウドエンジニア
      • QAエンジニア
      • その他
    • アドバイザリー
      • 監査&コンサルティング
  • 産業
    • フィンテック&バンキング
    • E-commerce
    • アドテック
    • ヘルステック
    • 製造業
    • 物流
    • 自動車
    • アイオーティー
  • 価値
    • CEO
    • CTO
    • デリバリー・マネージャー
  • チーム
  • Case Studies
  • ノウハウ
    • ブログ
    • ミートアップ
    • ウェビナー
    • リソース
採用情報 連絡先
  • 会社概要
  • サービス
    • ソフトウェア開発
      • フロントエンド開発
      • バックエンド開発
    • Staff Augmentation
      • フロントエンド開発者
      • バックエンド開発者
      • データエンジニア
      • クラウドエンジニア
      • QAエンジニア
      • その他
    • アドバイザリー
      • 監査&コンサルティング
  • 価値
    • CEO
    • CTO
    • デリバリー・マネージャー
  • チーム
  • Case Studies
  • ノウハウ
    • ブログ
    • ミートアップ
    • ウェビナー
    • リソース
採用情報 連絡先
戻る矢印 戻る
2019-02-04
ソフトウェア開発

javascriptのテスト...rubyで?

パヴェル・ヴァル

Codestは主にRubyのショップですが、私たちが構築している多くのプロジェクトの1つはJavaScriptです。これはクライアント・サイド・ライブラリで、かなり難しい環境で動作する。非常に古いものも含めて、現存するほとんどすべてのブラウザをサポートしなければならないし、その上、たくさんの外部スクリプトやサービスとやりとりする。とても楽しい。

バンドルされていない依存関係の不思議なケース

上記のような要件には、クライアントサイドでは通常存在しない一連の課題が伴う。 プロジェクトそして、それらの問題の1つがテストに関係している。もちろん、私たちは非の打ちどころのないユニットテストのセットを持っており、CI/CD環境で非常に大きなブラウザ/オペレーティング・システムの組み合わせに対してそれらを実行しているが、それだけではうまくいかない可能性をすべて探ることはできない。

我々のエコシステムの包括的なアーキテクチャのため、いくつかの外部ライブラリーは我々のライブラリーと一緒にロードされることに依存している。 コードできないし、どうしようもない。というのも、そのようなライブラリは興味深い課題を提示しているからだ:

  • もし誰かがライブラリーの実装をしくじったら、そこに存在しないかもしれない、
  • あるかもしれないが、バージョンが違っていたり互換性がなかったりする、
  • は、特定の実装の中で、他のコードによって変更されている可能性がある。

これは、単体テストが十分でない理由を明確に示している。ある外部ライブラリの公開APIの一部を、そのAPIのドキュメントで発見した内容に基づいてモックアップし、それに対して単体テストを実行したとしよう。それで何が証明できるだろうか?

外部ライブラリのAPIで動作するということだ」と言いたくなるかもしれないが、それは悲しいかな間違いだ。外部ライブラリのパブリックAPIのサブセットと正しく相互作用するという意味でしかなく、それも私たちがモックアップしたバージョンのみである。

もしライブラリが文字通り私たちの足元から変わってしまったら?もし野放しになっているときに、奇妙な反応が返ってきて、文書化されていない別のコード・パスにぶつかったら?私たちはそれを防ぐことができるのだろうか?

合理的な保護

100%ではない。しかし、私たちのコードに起こるかもしれない一般的な例を使って、すべてが想定通りに動くことを合理的に確認することはできる。単体テストは、私たちのコードが内部的に正しく動作することを保証し、統合テストは、私たちがコントロールできないライブラリと正しく「会話」することを保証する必要がある。それも、ライブラリのスタブではなく、実際の生きたライブラリとだ。

のために利用可能な統合テストフレームワークのひとつを使えばいい。 JavaScriptシンプルなHTMLページを作成し、私たちのライブラリとリモート・ライブラリの呼び出しをいくつか投げて、それをうまく使ってみる。しかし、CI/CD環境によって生成されたコールでリモートサービスのエンドポイントを氾濫させたくはない。それは、いくつかの統計情報を混乱させ、いくつかのものを壊してしまう可能性がある。

しかし、これほど複雑な統合テストは可能なのだろうか?私たちはRubyが何よりもまず好きなので、専門知識を頼りに、Rubyプロジェクトでリモート・サービスとの統合テストを通常どのように行うかについて考え始めた。例えば ビデオデッキ 何が起きているかを一度記録し、必要なときにいつでもテストに再生し続けることができる。

プロキシを入力

内部的には、vcrはリクエストをプロキシすることでこれを実現している。これが私たちのハッとした瞬間だった。私たちは、"本当の "インターネット上の何もヒットしないはずのすべてのリクエストを、いくつかのスタブ・レスポンスにプロキシする必要があった。そうすれば、その受信データは外部ライブラリに渡され、コードは通常通りに実行される。

複雑そうなプロトタイプを作るとき、時間を節約する方法としてRubyに頼ることがよくある。私たちは、(おそらく)JavaScriptでもっと複雑なものを作ることにコミットする前に、プロキシのアイデアがどの程度うまくいくかを確認するために、RubyでJavaScript用のテストハーネスのプロトタイプを作ることにした。その結果、驚くほどシンプルであることが判明した。実際、とてもシンプルなので、この記事で一緒に作ってみようと思う。

ライト、カメラ...待って、小道具を忘れた!

もちろん、私たちは「本物」を扱うわけではない。私たちが作っているものを少し説明するだけでも、ブログ記事の範囲をはるかに超えている。私たちは、問題のライブラリの代わりとなるものを素早く簡単に作り、その後Rubyの部分にもっと集中することができる。

まず、我々が扱っている外部ライブラリの代わりになるものが必要だ。外部サービスにコンタクトし、あちこちでイベントを発生させ、そして何よりも、簡単な統合を念頭に置いて構築されていないことが重要だ🙂。

使うのはこんな感じだ:

/* global XMLHttpRequest, Event */

const URL = 'https://poloniex.com/public/?command=returnTicker'
const METHOD = 'GET'

module.exports = {
fetch: function () {
var req = new XMLHttpRequest()
req.responseType = 'json'
req.open(METHOD, URL, true)
var doneEvent = new Event('example:fetched')

req.onreadystatechange = function (aEvt) {
  if (req.readyState === 4) {
    if (req.status === 200) {
      this.data = req.response
    } else {
      this.error = true
    }
    window.dispatchEvent(doneEvent)
  }
}.bind(this)

req.send(null)

},
error: false,
data: {}
}

このAPIはサンドボックスを公開しないし、レートも制限されているので、テストで実際にヒットさせるべきでないものの典型例となる。

通常扱うスクリプトは簡単にバンドルできるようにNPMで利用できないことをほのめかしたが、これは実際にはNPM互換のモジュールであることにお気づきだろう。このデモンストレーションでは、ある特定の動作を示すだけで十分であり、ここではむしろ、単純化しすぎることを犠牲にしてでも、説明しやすさを重視したい。

俳優の招聘

さて、ライブラリーの代わりになるものも必要だ。ここでも要件はシンプルにする。「外部」ライブラリーを呼び出して、その出力で何かをする必要がある。テスト可能な」部分をシンプルに保つために、コンソールへのロギングと、グローバルに利用可能な配列へのロギングの両方を行うようにします。

window.remote = require('remote-calling-example')

window.failedMiserably = true
window.logs = [].

関数ログ (メッセージ) {
window.logs.push(メッセージ)
console.log(message)
}

window.addEventListener('example:fetched', function () {)
if (window.remote.error) {
log('[EXAMPLE] リモートフェッチに失敗しました')
window.failedMiserably = true
} else {
log('[EXAMPLE] リモート・フェッチ成功')
log([EXAMPLE] BTC to ETH: ${window.remote.data.BTC_ETH.last})。
}
})

window.remote.fetch()

また、意図的に動作は驚くほどシンプルにしている。このままでは、実際に興味深いコードパスが2つしかないので、ビルドが進むにつれて雪崩をうって仕様に振り回されることはないだろう。

すべてがカチッとはまる

簡単なHTMLページを作ってみよう:

<code> <!DOCTYPE html>
 <html>
 <head>
   <title>ページ例</title>
   <script type="text/javascript" src="./index.js"></script>
 </head>
 <body></body>
 </html>

このデモでは、HTMLとJavaScriptを次のようにバンドルする。 小包非常にシンプルである。 ウェブアプリ bundler。手っ取り早くサンプルをまとめたり、ナプキンで思いついたクラスのアイデアをハックしたり。Webpackを設定するほうが、書きたいコードを書くよりも時間がかかるような単純なことをするときには、Parcelは最高だ。

また、邪魔にならないので、もう少しテストされたものに切り替えたいときに、Parcelからほとんど後退する必要がない。しかし、注意点として、Parcelは開発が進んでいるため、問題が発生する可能性があります。 Node.js.結論:まだ生産パイプラインの一部にする必要はないが、それでも試してみてほしい。

統合の力を活用する

これでテストハーネスを構築できる。

スペック・フレームワーク自体には スペック.開発環境では、ヘッドレスでない実際のChromeを使用してテストします。 ワチアー (そして信頼できる相棒watir-rspec)。プロキシには パフ・ビリー そして ラック を追加した。最後に、スペックを実行するたびにJavaScriptビルドを再実行したい。 コカイン.

そのため、スペック・ヘルパーはこの単純な例でさえも、少々...複雑になっている。それを分解して見てみよう。

Dir['./spec/support/*/.rb'].each { |f| require f }.

TEST_LOGGER = Logger.new(STDOUT)

RSpec.configureを行う |config|
config.before(:suite) { Cocaine::CommandLine.new('npm', 'run build', logger: TEST_LOGGER).run }.

config.include Watir::RSpec::Helper
config.include Watir::RSpec::Matchers

config.include ProxySupport

config.order = :random
ブラウザサポート.configure(config)
終了

Billy.configureは|c|を行う
c.cache = false
c.cacherequestheaders = false
c.persistcache = false
c.recordstubrequests = true
c.logger = Logger.new(File.expandpath('../log/billy.log', FILE))
終了

スイート全体の前に、cocaineを通してカスタムビルドコマンドを実行している。TEST_LOGGER定数は少しやりすぎかもしれないが、ここではオブジェクトの数をあまり気にしていない。もちろん、specをランダムな順番で実行するので、watir-rspecのすべてのグッズを含める必要がある。また、Billyがキャッシュを行わず、ロギングを広範囲に行うように設定する必要がある。 spec/log/billy.log.リクエストが実際にスタブされているのか、ライブサーバーに当たっているのか(おっと!)わからない場合、このログはまさに金です。

あなたの鋭い目は、すでにProxySupportとBrowserSupportを見つけたことでしょう。私たちのカスタムグッズはこの中にあると思うかもしれない...まさにその通りだ!まずはBrowserSupportが何をするのか見てみましょう。

コントロールされたブラウザ

まずは、次のことを紹介しよう。 テンプブラウザ:

クラス TempBrowser
def get
ブラウザ ||= Watir::Browser.new(web_driver)
終了

def kill
if @browser.close if @browser
ブラウザ = nil
終了

プライベート

def web_driver
Selenium::WebDriver.for(:chrome, options: オプション)
終了

def オプション
Selenium::WebDriver::Chrome::Options.new.tap do |options|.
options.addargument '--auto-open-devtools-for-tabs'を追加します。
options.addargument "--proxy-server=#{Billy.proxy.host}:#{Billy.proxy.port}"
終了
終了
終了

コールツリーを逆算すると、Chrome用のSeleniumブラウザオプションセットをセットアップしていることがわかる。ChromeインスタンスにPuffing Billyインスタンスを通してすべてをプロキシするように指示します。もう一つのオプションは、持っていると便利なものです。 ヘッドレス をクリックすると、検査ツールが自動的に開きます。一日にCmd+Alt+Iを数え切れないほど使わなくてすむ。

これらのオプションでブラウザをセットアップしたら、Watirにそれを渡す。これで 殺す メソッドは、TempBrowserインスタンスを捨てることなく、必要であればドライバーの停止と再起動を繰り返すことができる、ちょっとした砂糖のようなものです。

これでrspecのサンプルに2つのスーパーパワーを与えることができる。まず、粋な ブラウザ ヘルパー・メソッドを使用します。また、超繊細な作業をしている場合は、特定の例でブラウザを再起動する便利なメソッドも利用できる。もちろん、テストスイートが終わったらブラウザを終了させることもできます。

モジュール BrowserSupport
def self.browser
ブラウザ ||= TempBrowser.new
終了

def self.configure(config)
config.around(:each)で|example|を実行する。
ブラウザサポート.ブラウザ.kill if example.metadata[:clean]
ブラウザサポート.ブラウザ.get
ブラウザ.クッキー.クリア
ブラウザドライバ.manage.timeouts.implicit_wait = 30
example.実行
終了

config.after(:suite) do
  ブラウザサポート.ブラウザ.kill
終了

終了
終了

プロキシの配線

ブラウザとスペックヘルパーをセットアップし、プロキシへのリクエストのプロキシを開始する準備ができました。しかし、まだセットアップしていない!しかし、ヘルパーメソッドをいくつか用意して、数千のキーストロークを節約する方が良いでしょう。それが プロキシサポート はそうする。

私たちのテストセットアップで使っているのは、もう少し複雑なものだが、一般的なアイデアはこんな感じだ:

frozenstringliteral: true

require 'json'

モジュール ProxySupport
HEADERS = {
'Access-Control-Allow-Methods' => 'GET'、
'Access-Control-Allow-Headers' => 'X-Requested-With, X-Prototype-Version, Content-Type'、
'Access-Control-Allow-Origin' => '*'.
フリーズ

def stubjson(url, file)
Billy.proxy.stub(url).andreturn({」を返します。
body: open(file).read、
code:200,
ヘッダ:HEADERS.dup
})
終了

def stubstatus(url, status)
Billy.proxy.stub(url).andreturn({
body: ''、
code: status、
ヘッダ:HEADERS.dup
})
終了

def stubpage(url, file)
Billy.proxy.stub(url).andreturn(
body: open(file).read、
content_type: 'text/html'、
code:200
)
終了

def stubjs(url, file)
Billy.proxy.stub(url).andreturn(
body: open(file).read、
content_type: 'application/javascript'、
code:200
)
終了
終了

私たちはスタブ(半券)を作ることができる:

  • HTMLページのリクエスト - メインの "プレイグラウンド "ページ、
  • JSリクエスト - バンドルされたライブラリを提供するため、
  • JSONリクエスト - リモートAPIへのリクエストをスタブする、
  • と、特定の200でないHTTPレスポンスを返すことだけを気にする「何でもいい」リクエスト。

簡単な例ならこれで十分だ。例といえば......いくつか設定しておこう!

良い面を試す

まず、プロキシのためにいくつかの "ルート "を配線する必要がある:

let(:pageurl) { 'http://myfancypage.local/index.html' }.
let(:jsurl) { 'http://myfancypage.local/dist/remote-caller-example.js' }.

let(:pagepath) { './dist/index.html' }.
let(:jspath) { './dist/remote-caller-example.js' }.

を行う前に
stubpage pageurl, pagepath
stubjs jsurl, jspath
終了

rspecの観点からは、ここでの相対パスはメインのプロジェクト・ディレクトリを参照していることに注意する価値がある。 ディスト ディレクトリにある。これらの スタブ ヘルパーは便利だ。

また、"偽 "のウェブサイトを ローカル TLD。そうすることで、万が一何か問題が発生しても、暴走したリクエストがローカル環境から逃げ出すことはない。一般的なプラクティスとして、絶対に必要な場合を除き、少なくともスタブでは「本物の」ドメイン名を使わないことをお勧めする。

ここでもう一つ注意すべきことは、同じことを繰り返さないということである。プロキシのルーティングがより複雑になり、より多くのパスとURLを持つようになると、このセットアップを共有コンテキストに抽出し、必要に応じて単純に含めることに本当の価値があるようになるだろう。

これで、"良い "パスがどのようにあるべきかを特定することができる:

コンテキスト '正しい応答で' do
やる前に
stubjson %r{http://poloniex.com/public(.*)}, './spec/fixtures/remote.json'
goto pageurl
Watir::Wait.until { browser.execute_script('return window.logs.length === 2') } を実行する。
終了

it '適切なデータをログに記録' do
expect(browser.execute_script('return window.logs')).to(
eq(['[EXAMPLE] Remote fetch successful', '[EXAMPLE] BTC to ETH: 0.03619999'])
)
終了
終了

とてもシンプルでしょう?リモートAPIからのJSONレスポンスをフィクスチャでスタブし、メインURLに移動し、そして...待つ。

最も長い待ち時間

例えばJavaScriptのイベントを確実に待つことはできないので、スクリプトが私たちがアクセスできるオブジェクトを私たちが興味を持つ状態に移動させるまで、少しズルをして "待つ "必要がある。欠点は、(バグなどで)そのステートが来なかった場合、watir waiterがタイムアウトするのを待つ必要があることだ。このため、スペックにかかる時間が少し長くなる。それでもスペックは確実に失敗する。

ページが興味のある状態で「安定」した後、ページのコンテキストでさらにいくつかのJavaScriptを実行することができる。ここでは、public配列に書き込まれたログを呼び出し、それらが期待したものであるかどうかをチェックする。

余談だが、リモート・リクエストをスタブ化することが本当に有効なのはここだ。コンソールにログされるレスポンスは、リモートAPIから返される為替レートに依存しているため、ログの内容が変化し続けると、確実にテストすることができない。もちろん、それを回避する方法はありますが、あまりエレガントではありません。

不良ブランチのテスト

もうひとつ、"失敗 "ブランチをテストしてみよう。

コンテキスト 'with failed response' do
する前に
スタブステータス %r{http://poloniex.com/public(.*)}, 404
goto pageurl
Watir::Wait.until { browser.execute_script('return window.logs.length === 1') } }.
終了

it 'logs failure' do
expect(browser.execute_script('return window.logs')).to(
eq(['[EXAMPLE]リモート取得に失敗しました'])
)
終了
終了

上記とよく似ているが、異なるのは、レスポンスが404のHTTPステータスコードを返すようにスタブし、異なるログを期待する点である。

それではスペックを確認してみよう。

% バンドル exec rspec
シード63792でランダム化
I, [2017-12-21T14:26:08.680953 #7303] INFO -- : コマンド :: npm run build

リモート呼び出し
正しいレスポンスで
適切なデータを記録
失敗したレスポンス
失敗のログ

23.56秒で終了(ファイルのロードに0.86547秒かかった)
2例、失敗0

うっほー!

結論

JavaScriptをRubyと統合テストする方法について簡単に説明した。当初はその場しのぎ的なものだと考えていたが、今では小さなプロトタイプにかなり満足している。もちろん、まだ純粋なJavaScriptソリューションを検討していますが、その間に、私たちが実際に遭遇した非常に複雑な状況を再現し、テストするためのシンプルで実用的な方法を手に入れました。

同じようなものを自分で作ろうと考えているのであれば、制限がないわけではないことに注意すべきである。例えば、あなたがテストしているものが本当にAJAXを多用するものである場合、Puffing Billyは応答するのに長い時間がかかります。また、いくつかのSSLソースをスタブする必要がある場合は、より多くの微調整が必要になります。私たち独自のユースケースに対処するための最良の方法を探求し、探し続けます。

関連記事

ソフトウェア開発

将来を見据えたウェブ・アプリケーションの構築:The Codestのエキスパート・チームによる洞察

The Codestが、最先端技術を駆使してスケーラブルでインタラクティブなウェブアプリケーションを作成し、あらゆるプラットフォームでシームレスなユーザー体験を提供することにどのように秀でているかをご覧ください。The Codestの専門知識がどのようにデジタルトランスフォーメーションとビジネス...

ザ・コデスト
ソフトウェア開発

ラトビアを拠点とするソフトウェア開発企業トップ10社

ラトビアのトップソフトウェア開発企業とその革新的なソリューションについて、最新記事でご紹介します。ラトビアの技術リーダーたちがあなたのビジネスをどのように向上させるかをご覧ください。

thecodest
エンタープライズ&スケールアップ・ソリューション

Javaソフトウェア開発の要点:アウトソーシングを成功させるためのガイド

outsourcingのJavaソフトウェア開発を成功させるために不可欠なこのガイドを読んで、The Codestで効率性を高め、専門知識にアクセスし、プロジェクトを成功に導きましょう。

thecodest
ソフトウェア開発

ポーランドにおけるアウトソーシングの究極ガイド

ポーランドのoutsourcingの急増は、経済、教育、技術の進歩がITの成長とビジネス・フレンドリーな環境を促進していることによる。

ザ・コデスト
エンタープライズ&スケールアップ・ソリューション

IT監査ツール&テクニック完全ガイド

IT監査は、安全かつ効率的で、コンプライアンスに準拠したシステムを保証します。その重要性については、記事全文をお読みください。

The Codest
ヤクブ・ヤクボヴィッチ CTO & 共同創設者

ナレッジベースを購読して、IT部門の専門知識を常に最新の状態に保ちましょう。

    会社概要

    The Codest - ポーランドに技術拠点を持つ国際的なソフトウェア開発会社。

    イギリス - 本社

    • オフィス 303B, 182-184 High Street North E6 2JA
      イギリス、ロンドン

    ポーランド - ローカル・テック・ハブ

    • ファブリチュナ・オフィスパーク、アレハ
      ポコジュ18、31-564クラクフ
    • ブレイン・エンバシー, コンストルクトースカ
      11, 02-673 Warsaw, Poland

      The Codest

    • ホーム
    • 会社概要
    • サービス
    • Case Studies
    • ノウハウ
    • 採用情報
    • 辞書

      サービス

    • アドバイザリー
    • ソフトウェア開発
    • バックエンド開発
    • フロントエンド開発
    • Staff Augmentation
    • バックエンド開発者
    • クラウドエンジニア
    • データエンジニア
    • その他
    • QAエンジニア

      リソース

    • 外部ソフトウェア開発パートナーとの協力に関する事実と神話
    • 米国から欧州へ:アメリカの新興企業がヨーロッパへの移転を決断する理由
    • テックオフショア開発ハブの比較:テックオフショア ヨーロッパ(ポーランド)、ASEAN(フィリピン)、ユーラシア(トルコ)
    • CTOとCIOの課題は?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • ウェブサイト利用規約

    著作権 © 2025 by The Codest。無断複写・転載を禁じます。

    jaJapanese
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek jaJapanese