SkyNetをテストする方法は? (JSで記述されている場合)

むかしむかし、ターミネーター1を見ました。 彼は少し成長し、ターミネーター2を少しリファクタリングしたように見えました。3番目の統合はもう少し待たなければなりませんでした。



そして、貧しいSkyNetは幸運ではなく、すべてが間違っていたたびに、そしてこの不運の秘密は非常に単純です-SkyNetは被試験者( SUT )ではありませんでした。



言い換えれば、SkyNet podzabitはテスト用であり、戦闘で偵察を実施しました。 そして、通常、それが生産に投入された直後に、地殻に落ちました(プレスの下、溶岩に、私はそれ以上覚えていません)。



そして、全体の問題は、この計算の前に何かをテストして、手遅れになるまで遅らせる方法です。 もちろん、すべての人を殺します。







TDD



まず、TDD(テスト駆動開発)手法を使用して、何をする必要があるのか​​、そしてその理由を判断します。 映画を見た人は知っている-すべてが非常に簡単です:



  1. 審判の日が来たら、ミサイルを発射し、すべての人を殺します。
  2. ジョン・コナーが生まれたとき-それについてダンクして映画を作る。


問題は1つだけです-これらのイベントは両方とも、あたかもそれらが単一であるかのように-ロケットは最初に打ち上げられなければなりません、そしてジョンも最初に強打しなければなりません、そうでなければ彼は成長して抵抗をリードします。 一般に、タイムマシンがない場合、テストを作成して繰り返し可能にするのは非常に困難です。



そして今何をすべきか?



そのため、SkyNetにはロケットを発射するチャンスは1つしかなく、ターミネーターを過去に送り返すチャンスは1つしかありません。そこで、彼は最初の試行で仕事をしなければなりません。 さて-このために、テストが存在します。



そして、問題を解決する唯一の方法は、何らかの方法で実際のミサイルを偽物に置き換えることです。



科学的に、これはモッキングと呼ばれます。



唯一の質問は方法です!



しかし、この質問に答えるには、最初にSkyNet自体のコードを調べる必要があります。



// dooms-day.js import {theDay} from './doom-scheduler' import {Launch} from './rocket-silo'; theDay .then(Launch) .then( () => alert(' :) '), () => alert(' :( next time, you know...'); // -------------------- // rocket-silo.js import SkyNetCore from './core'; export const Launch = () => SkyNetCore.hackRockets().then(rocket => rocket.launch)
      
      





コードによると、SkyNetをテストするには、中央ファイル「dooms-day.js」の依存関係を管理対象エンティティに置き換える必要があることがわかりました。



どうやってロックするの?



聞いてくれてうれしいです。 簡単な答えはありませんが、さらに簡単なものが5つあります。 多数の異なるライブラリと、適切に軽くたたく方法とひどく軽くたたく方法に関するアンチパターンを備えたいくつかのパターン。



ただし、依存関係を削除するためのさまざまなインターフェースを提供する一般的なライブラリの概要から始めることをお勧めします。



オプション1-テストで「テスト」モジュールオプションを使用する



シンプルに聞こえますが、本当に本当に。 「置換」と「置換」を作成するだけです。



 //__mocks__ /doom-scheduler.js ->  doom-scheduler.js export const theDay = Promise.resolve(); //__mocks__/rocket-silo.js ->  rocket-silo.js export const Launch = jest.fn(); //this is "replacement" code //    import {Launch} from './rocket-silo'; //  silo    import {theDay} from './doom-scheduler'; //     import './dooms-day.js'; //   dooms-day expect(Launch).toHaveBeenCalled();
      
      





一般に、「__ mock__」は非常にシンプルで便利なメカニズムであり、マイナス記号が1つしかありません-Jest。 この機能はJestでのみ動作し、Skynetがmocha、ava、またはkarmaを使用してテストを実行する場合、他のインターフェイスを探す必要があります。



オプション2-テストでは、実際のモジュールを単に「テスト」に置き換えます



__mock__マジックが利用できない場合、モックはどうですか?



 import {Launch} from './rocket-silo'; //    import {theDay} from './doom-scheduler'; //    import './dooms-day.js'; jest.mock('./rocket-silo'); //     -   jest.mock('./doom-scheduler'); theDay.mockResolvedValue("comming!"); //      expect(Launch).toHaveBeenCalled();
      
      





繰り返しますが、テストファイルの依存関係を置き換える非常に簡単な方法で、Jestのみです。



実際、Jestの可能性はそこで終わり、これら2つのアプローチは非常に原始的(かつ単純)であるという事実にもかかわらず、これはおおよそ必要なものです。 他のすべてが可能です-請求されていない再配置。



オプション3-StalonのSchwarzneigerを置き換えたい



ファイルをアップロードできるが、「変更された」部分がある場合はどうなりますか?



 import proxyquire from 'proxyquire'; import sinon from 'sinon'; const Launch= sinon.stub() const case = proxyquire.load('./dooms-day.js',{ './rocket-silo', { Launch }, './doom-scheduler', { theDay: Promise.resolve() }); expect(Launch).toHaveBeenCalled();
      
      





これがproxyquireの仕組みです-このビジネスで最も古い人気のあるライブラリの1つです。 このファイルから直接要求された依存関係が重複しているファイルをダウンロードできます。



jest.mockに対する主な利点は、1つのテストで、100500の異なるproxyquiredの異なるオーバーラップファイルを持つことができることです。 さらに、.callThoughなどのさまざまなコマンドは、部分的に重複する依存関係のみを許可します(これは一般にアンチパターンです)。



オプション4-同じこと、より宣言的なもの



Proxyquireは、少し面倒な場合があります。また、「_ mock__」などの「標準」モックの存在が必要な場合もあります。 一般的に、m笑!



 import mockery from 'mockery'; import sinon from 'sinon'; mockery.registerMock('./rocket-silo', { Launch: sinon.stub() }); mockery.registerMock('doom-scheduler', { theDay: Promise.resolve() }); mockery.enable(); const {Launch} = require('./rocket-silo'); // import   mockery.disable(); expect(Launch).toHaveBeenCalled();
      
      





Mockeryには強力なAPIもありますが、proxyquireとは少し方向が異なります。 たとえば、「許可されていない」ファイルを接続するときにm笑が宣誓を開始する「アイソレーション」モードがあり、依存関係とロジックの偶発的な変更を防ぐことができます。



オプション5-マジカンの最後



TestDouble.jsは同名の会社のライブラリであり、1つまたは別のアプローチの「正確さ」に多くのエネルギーを費やしています。 技術的には、これはjest.mock、側面図です。



 import td from 'testdouble'; const {Launch} = td.replace('./rocket-silo'); // automock const scheduler = td.replace('./doom-scheduler', { theDay: Promise.resolve() }) require('./dooms-day.js'); td.verify(Launch());
      
      





同時に、 非常に長い議論の後に、任意のモジュールを置き換える可能性が現れました

一般に、TDは知識とベストプラクティスの貯蔵庫です。 mokiに関するセクションを読むことをお勧めします。



しかし、問題があります





これらのライブラリはすべてキャッシュを不適切に処理し、テストの速度が大幅に低下することもあります。



大きなプロジェクトの場合、nodejs(およびbumblebee)のキャッシュ全体が消去される前後に、約1秒で実行する必要があります。


それでは、SkyNetをテストする方法は?



忘れないでください-問題はすべて、SkyNetが完璧を求めていたことです。 そして、分解されたツールはすべて完璧ではありません。 それらを使用してテストの記述を開始すると、SkyNetは最初からターミネーターを送信します。



カウンターに着かないようにするために、私はjestのようにavaの下でのみmokyを望んでいました。 Proxyquire構文は、多くの場合、moki jest.mock / td.replaceと同様に自動的に便利です。 たくさんのものが欲しかったので、仕事からタイムマシンを借りて、1年前に上記のすべてを実行できるライブラリをレイアウトしました。



jest.mockを置き換える

 import rewiremock from 'rewiremock'; import {Launch} from './rocket-silo'; import {theDay} from './doom-scheduler' import './dooms-day.js'; // prev jest.mock('doom-scheduler'); rewiremock('./rocket-silo').mockThrough(); rewiremock('doom-scheduler').mockThrough(); theDay.resolves("comming!"); // sinon expect(Launch).toHaveBeenCalled();
      
      





ock笑を置き換える



 import rewiremock from 'rewiremock'; import sinon from 'sinon'; // mockery.registerMock('./rocket-silo', { // Launch: sinon.stub() // }); rewiremock('./rocket-silo').with({ Launch: sinon.stub() }); rewiremock('doom-scheduler').with({ theDay: Promise.resolve() }); rewiremock.enable(); require('./dooms-day.js'); rewiremock.disable(); expect(Launch).toHaveBeenCalled();
      
      





proxyquireを置き換えます



 import rewiremock from 'rewiremock'; import sinon from 'sinon'; const Launch = sinon.stub() // const case = proxyquire.load('./dooms-day.js',{ const case = rewiremock.proxy('./dooms-day.js',{ './rocket-silo': { Launch }, 'doom-scheduler': { theDay: Promise.resolve()} }); expect(Launch).toHaveBeenCalled();
      
      





TDを置き換えます



 import rewiremock from 'rewiremock'; const {Launch} = rewiremock('./rocket-silo').mockThrough(); // automock const scheduler = rewiremock('doom-scheduler').with({ theDay: Promise.resolve() }) rewiremock.proxy('./dooms-day.js'); //   expect(Launch).toHaveBeenCalled();
      
      





さらに、それはどこでも動作します-モカ、ava、node.jsまたはwebpackの下のカルマ。 同時に、キャッシュとはまったく異なる方法で動作し、テストの影響を受けないものは削除しません(時には100倍高速)。 たとえば、mokaが正しく使用されることを確認するために、常にAPIがあります。



 import rewiremock from 'rewiremock'; import sinon from 'sinon'; rewiremock('./rocket-silo') .with({ Launch: sinon.stub()}); rewiremock('./rockets') .toBeUsed(); // .... rewiremock.enable(); require('./dooms-day'); rewiremock.disable(); // will throw an Error - "rockets were not used", as long we mock out Silo.
      
      





または、1つのモジュールを別のモジュールに置き換える(m笑できるように)



 import rewiremock from 'rewiremock'; import sinon from 'sinon'; const Launch = sinon.stub() const case = rewiremock.proxy('./dooms-day.js',{ './rocket-silo': rewiremock.with({Launch}).toBeUsed().directChildOnly(), // "real" proxyquire 'doom-scheduler': rewiremock.by('mocked-doom-scheduler') }); expect(Launch).toHaveBeenCalled();
      
      





または、requireの代わりにimportを使用します。これは、名前解決とタイプセーフの観点から便利です。



 rewiremock(() => import('doom-scheduler')).with({ theDay: Promise.resolve() }); // rewiremock async API. await rewiremock.module('./dooms-day.js');
      
      





一般に、rewiremockはSkyNetが行う依存関係変更ライブラリです。 特に、パフォーマンスを改善し、新しいバージョンをリリースするためにタイムマシンを絶えず使用していることを考えると。



そして、最も重要なことは、これが現在のライブラリです。 インターフェース互換。



そして今、私の使命は、何とか人々をproxyquireとmockery(そして小さな製品)からもっと使いやすいものに移すことです。 古いアーニーが2015年に「古くなったが時代遅れではない」ため、それ以降アップデートを受け取っていないからです。 proxyquireのように、m笑のように。



参照用:



ジェストモック

ock笑

Proxyquire

TD.js (wikiページを読むのを忘れる)



まあ、最も重要なこと: rewiremock



PS:そして、これはこのライブラリについての最初の記事ではありません- 残りも役に立つかもしれません。



All Articles