モック、偽物、およびスタブ-単体テストの3つの柱。 もちろん、誰もがそれが何であるか、どのように塩をし、いつ食べるべきかを知っています。 私も正直に思いました。少し曲げなければならない現実に出会うまで。
それはすべて非常に簡単に始まりました。仕事を変えました。新しいコードベースで最初に目にしたのはテストでした。コード以上のことがありました。 そして、これらのテストの最中に奇妙なデザインがありました
Component = proxyquire.noCallThru().load('../Component', { '../../core/selectors/common': { getData } }).default;
参照用:
proxyquireは、最も人気のあるものの1つであり、依存症を除去するための最も古いライブラリの1つです。 彼女に加えて、注入ローダー、再配線、m笑などが聞こえるようになりました。 「古い」とは、「古い」node.jsコード用に設計されていることを意味します。 es6インポートなし。
戦車に乗っており、ここで何が起こっているのか完全に理解していない人のために-すべてが非常に簡単です。 Proxyquireは、変更された../Componentをロードします。このインポートでは、インポートの1つがスタブによってブロックされます。
なんで? これは、ユニットをもう少しユニットテストし、制御されない外部依存関係の数をゼロに減らす素晴らしい方法です。 または、アプリケーションから複雑な、または不必要なロジックを一時的に削除して、テストを簡単にします。
そして、すべてがうまくいきますが、特にこのコードは機能しませんでした。 ファイル名にエラーが発生しました。もう1つディレクトリを照らす必要がありました。 そしてさらにミスが発生しました-プロジェクト自体はwebpack-aliasを使用して、ルートからのすべてのファイルをアドレス指定し、proxyquireはすべてのパスが相対するbabelを通過した後にファイルを操作しました。
あなたがコードを持っていると想像してください
import something from 'something/else';
それは非常にシンプルで簡単なようで、実際には何かが2.defaultであり、ファイル自体は好きなものにすることができます-ダミーには多くの魔法が含まれています。
それがこの叙事詩の始まりです。 proxyquireにエイリアスサポートを追加しようとしています。
1.決定番号1。 最初の会議
頭に浮かんだ最初の決定は、古いproxyquireに頭脳を追加することでした。 彼の問題は一般的に非常に単純です
Proxyquire.prototype._require = function(module, stubs, path) { //…. if (hasOwnProperty.call(stubs, path)) { var stub = stubs[path];
プラグイン名とオーバーロードリストのエントリが完全に一致する必要があります。
./foo、./foo.js、../common/foo-これは1つのファイルですが、3つの異なる行にすることができます。
5分でこの場所に小さな脳が追加され、さらに5分後にgithubでもう1つのPRが行われました。 さらに5分後、彼は無慈悲に閉じられ、ここに不必要な脳を追加するのではなく、彼らのために別の場所を見つけるよう求めました。
2.決定番号2。 本当のヒーローはいつも回っています
原則として、Proxyquireのメンバーだけが決定について熱心ではなかっただけではありません。 私は、問題の代替解決策について、当面の環境からいくつかの提案を受け取りました。
要するに、意味は単純でした-1つはfileName1からfileName2を作成する単純な関数を書くだけで、1つ目はエイリアスを持ち、2つ目はすでにproxyquireの「正しい」形式になります。
proxyquire自体を変更しようとしないでくださいが、
proxyquire.load('../Component', addSomeMagic({ 'something/with/alias':{} }))
エイリアスなしで、ソースファイルに関連する正しい名前がproxyquire自体に届くようにします。
ここでは、ちょっとした魔法のプロキシに注目する必要があります。 なぜ機能するのか疑問に思う人はほとんどいません。
Component = proxyquire.noCallThru().load('../Component', {
proxyquireは現在のディレクトリをどのように認識し、技術的にそれに関連するファイルをダウンロードできますか? また、テストでproxyquireを直接接続する必要があるのはなぜですか?
解決策は簡単です-proxyquireは、node.jsがモジュール変数を介して渡す情報を使用します。 module.parentには、このファイルを最初に開いたユーザーに関する情報が格納されます。 モジュラーシステムからキャッシュを消去することを忘れないことだけが残っているため、毎回初めて...のようになります。
一般に、proxyquireの変更を必要としないソリューションの開発により、node.jsの多くの新機能が紹介されました。
結局、私は何もできませんでした。通常の解決策では、元のライブラリを変更する必要がありました。 行き止まり。
3.決定番号3。 世界は静止していません
同時に、仕事は本格的でした。 そして、自分自身の生活を少し楽にするために、元のproxyquireの改善を試み続けました。 私は再びプール要求を生成し始めました。
- それらの最初のものは 、原則として、少なくとも何らかの形でライブラリを拡張することを許可しました(通常は継承を含む)が、予想外に迅速にアップロードされました。
- 2番目は 、「nonProjectFiles」がキャッシュから消去されないように保護するため、スマートに閉じられました。
- 3番目は 、いわゆる「分離モード」を追加し、すべてのスタブを使用する(名前のエラーから保護する)か、すべての依存関係をブロックする(真空での理想的な単体テスト)必要がある場合、すでに2週間移動せずに停止しています:(
これと並行して、実際にソリューションをテストし、一般にその有効性を証明するために、次の作業が行われました。
- proxyqure-2-ソースライブラリのパーソナルフォーク。単に必要な修正がすべてマージされます。
- proxyquire-webpack-alias-proxyquireのドロップイン置換。webpackエイリアスを完全にサポートします。 一つ悪いことです-仕事のために、私のフォークは上記の行を必要とします。
- resolveQuireはソリューション#2のまさにライブラリであり、それでも私は最後まで完成し、元のproxyquireで動作でき、「機能的」な美しさにもかかわらず、いくつかの面で依然として上位2つのソリューションを失います。
私見、私はどちらもproxyquireの代わりにこのリストから何かを使用することを推奨するか、または問題のより良い理解のために、実装の違いに精通してください。
4.回顧
同僚の前で自分の選択肢を擁護し、同時に私のものだけでなく彼らの長年の痛みも治したとすぐに、疑問が生じました-次は何ですか? 尋ねる方が良いとはいえ、以前は何でしたか?
私は以前の職場で中毒をびしょぬれにした方法を思い出そうとしました。 彼らは濡れなかったので、それは困難でした。 そして実際、周囲のデルタの調査は、世界の非常に単純な絵を示しました。
誰も濡れていません。 sinon、fetch-mockは考慮されていません。依存関係ではなく、ローカル変数またはグローバル変数のインターセプトです。
普通の人は普通のDIです。
それから、以前の職場でjsコードランタイムの依存関係を取得する方法を思い出そうとしました。
元々Yandex.Mapsのこの非常に興味深いCommonJS / AMD互換ソリューションに慣れていない人のために、それについての プレゼンテーションがいくつかあります。
一般に、すべてが非常に簡単でした。
// , module.define('a',['b'], (provide,b) => {}); // , module.require('a');
依存関係をブロックする必要がある場合
module.define('b',[], (provide) => {}); // , module.define('a',['b'], (provide,b) => {}); // , , module.require('a');
シンプルで、あなたも言うことができます-当然。
node.jsにネイティブなモジュールシステムで、ファイルを滴下するためのさまざまなソリューションはどのように機能しますか?
- inject-loaderのように-必要に応じてソースファイルを物理的に変更するwebpackローダー。 (+ 再配線 )
- module._loadオーバーロードによるm笑のような。 すぐ後に続く最初のシステムメソッドが必要です。
- proxyquireのように-require.extensionsハンドラーのオーバーロードを通じて。 技術的には最低レベル。
違いは非常に簡単です。
- inject-loaderは、実際のファイルを生成しますが、わずかに異なります。 node.jsのパッチはありません
- mockeryの実行は「非常に高い」ため、出力はキャッシュされません。
- ただし、proxyquireはキャッシュの下で動作します。
最初にファイルをロックしてから再度要求する場合は、mokを入手してください。 noPreserveCacheで回復しますが、だれが推測しますか?
そしてどこにでもニュアンス、実装を少し複雑にする実装があります:
- inject-loader-直接の依存関係でのみ機能します。
- ock笑-毎回キャッシュ全体をきれいにこするため、速度に悪影響を及ぼします。
- proxyquire-多くの驚きが含まれています。
5.最終決定
だから-私は自分のproxyquire、他の多くのライブラリの知識、さまざまな角度からの問題分析、技術的な問題の十分な理解を持っていました。 目標はシンプルでした-それらをすべて支配する1つのリング。
最初の選択肢として、最も満足のいく美意識はm笑でした。
理由:
- インターセプトの定義とインターセプト自体の分離。 registerMock /有効にします。
- 分離モードの存在。 warnOnUnregistered / registerAllowable
- 置換モードの存在。 registerAllowable
その結果、 rewiremockライブラリが完成しました。これは私のすべてのウィッシュリストを満たし、すぐにすべての問題を解決できます。
import rewiremock from 'rewiremock'; ... // totaly mock `fs` with your stub rewiremock('fs') .with({ readFile: yourFunction }); // replace path, by other module rewiremock('path') .by('path-mock'); // replace default export of ES6 module rewiremock('reactComponent') .withDefault(MockedComponent) // replace only part of some library and keep the rest rewiremock('someLibrary') .callThought() .with({ onlyOneMethod }) … rewiremock.enable(); rewiremock.isolation(); rewiremock.passBy(/node_modules/); // use native require const module = require('../core/somemodule'); // or add some magic…. const module = require(rewiremock.resolve('core/module'));
mokiを個別のファイルで定義し、重複ファイル(およびそれらを使用するファイル)のみのキャッシュを巧みにクリアし、passBy分離セットアップでRegExをサポートし、非常にシンプルなAPIを持ち、必要に応じて常に動作します。
完全な秘密は、適切な場所でライブラリの動作を制御できるプラグインにあります。
実装の詳細を説明する価値はないと思います。誰もがコードを掘り下げたり、自分でテストしたり、同時にコメントで何かを表現したりできます。
またはプルクエスト、私は聞くことを約束します。
→ https://github.com/theKashey/rewiremock
そのため、1つの小さな破片は、積極的に使用されるツールの「曲率」を受け入れないだけでなく、4つの新しいライブラリに対する問題のより深い理解であるmokのマイクロフィニッシュの発見につながりました。 そして、おそらく少し良い世界に。
PS:proxyquireの作者に対する苦情はありません。 なんで? https://habrahabr.ru/post/328412/