どうやってプライベート関数をangle 2でテストするのですか?
class FooBar {
private _status: number;
constructor( private foo : Bar ) {
this.initFooBar();
}
private initFooBar(){
this.foo.bar( "data" );
this._status = this.fooo.foo();
}
public get status(){
return this._status;
}
}
私が見つけた解決策
テストコード自体をクロージャの内側に配置するか、外側のスコープ内の既存のオブジェクトのローカル変数への参照を格納するクロージャの内側にコードを追加します。
後でツールを使ってテストコードを取り除きます。 http://philipwalton.com/articles/how-to-unit-test-private-functions-in-javascript/
あなたが何かをしたならば、私にこの問題を解決するより良い方法を提案してください?
P.S
このような同じタイプの質問に対する答えの大部分は問題の解決策を提供していません。そのため、私はこの質問をします。
ほとんどの開発者はあなたにプライベート機能をテストしてはいけないと言っていますが、それらが間違っているとか正しいとは言っていませんが、プライベートをテストする必要があります。
「公開APIのみを単体テストする」ことが良い目標ではありますが、それほど単純ではないと思われ、APIと単体テストのどちらを妥協するかを選択していると感じる場合があります。それはまさにあなたが望んでいることなので、あなたはすでにこれを知っています、それで私はそれに入りません。 :)
TypeScriptでは、単体テストのためにプライベートメンバーにアクセスする方法をいくつか発見しました。このクラスを考えます:
class MyThing {
private _name:string;
private _count:number;
constructor() {
this.init("Test", 123);
}
private init(name:string, count:number){
this._name = name;
this._count = count;
}
public get name(){ return this._name; }
public get count(){ return this._count; }
}
TSではprivate
、protected
、public
を使用してクラスメンバーへのアクセスを制限していますが、コンパイルされたJSにはプライベートメンバーはありません。これはJSでは行われないためです。これは純粋にTSコンパイラに使用されています。そのために:
any
にアサートして、アクセス制限について警告しないようにコンパイラを回避することができます。
(thing as any)._name = "Unit Test";
(thing as any)._count = 123;
(thing as any).init("Unit Test", 123);
このアプローチの問題点は、コンパイラがany
のすぐそばで何をしているのかわからないことです。そのため、必要な型エラーが発生することはありません。
(thing as any)._name = 123; // wrong, but no error
(thing as any)._count = "Unit Test"; // wrong, but no error
(thing as any).init(0, "123"); // wrong, but no error
プライベートメンバにアクセスするには、配列アクセス([]
)を使用できます。
thing["_name"] = "Unit Test";
thing["_count"] = 123;
thing["init"]("Unit Test", 123);
ファンキーに見えますが、TSCは実際に型に直接アクセスしたかのように型を検証します。
thing["_name"] = 123; // type error
thing["_count"] = "Unit Test"; // type error
thing["init"](0, "123"); // argument error
正直に言うと、なぜこれが機能するのかわかりません。 これはどうやらあなたが型の安全性を失うことなくあなたに個人的なメンバーへのアクセスを与えるための 意図的な "脱出ハッチ" です。これはまさに私があなたの単体テストに欲しいと思うものです。
これが TypeScriptプレイグラウンドでの実用的な例 です。
開発者のほとんどがプライベート関数のテストは推奨しません、なぜそれをテストしないのですか?.
例えば。
YourClass.ts
export class FooBar {
private _status: number;
constructor( private foo : Bar ) {
this.initFooBar({});
}
private initFooBar(data){
this.foo.bar( data );
this._status = this.foo.foo();
}
}
TestYourClass.spec.ts
describe("Testing foo bar for status being set", function() {
...
//Variable with type any
let fooBar;
fooBar = new FooBar();
...
//Method 1
//Now this will be visible
fooBar.initFooBar();
//Method 2
//This doesn't require variable with any type
fooBar['initFooBar']();
...
}
@Aaron、@ Thierry Templierに感謝します。
プライベートメソッドのテストを書かないでください。これは単体テストのポイントを破ります。
例
class SomeClass {
public addNumber(a: number, b: number) {
return a + b;
}
}
実装が後で変更されてもパブリックAPIのbehaviour
が同じままである場合、このメソッドのテストを変更する必要はありません。
class SomeClass {
public addNumber(a: number, b: number) {
return this.add(a, b);
}
private add(a: number, b: number) {
return a + b;
}
}
テストするためだけにメソッドやプロパティを公開しないでください。これは通常、次のいずれかを意味します。
「プライベートメソッドをテストしない」のポイントは、実際にはそれを使う人のようにクラスをテストすることです。
あなたが5つのメソッドを持つパブリックAPIを持っているなら、あなたのクラスのどのコンシューマもこれらを使うことができるので、あなたはそれらをテストするべきです。コンシューマはあなたのクラスのプライベートメソッド/プロパティにアクセスしてはいけません。つまり、公開されているパブリック機能が同じであればプライベートメンバを変更できるということです。
拡張可能な内部機能に依存している場合は、protected
ではなくprivate
を使用してください。protected
はまだpublic API(!)であり、使用方法が異なることに注意してください。
class OverlyComplicatedCalculator {
public add(...numbers: number[]): number {
return this.calculate((a, b) => a + b, numbers);
}
// can't be used or tested via ".calculate()", but it is still part of your public API!
protected calculate(operation, operands) {
let result = operands[0];
for (let i = 1; i < operands.length; operands++) {
result = operation(result, operands[i]);
}
return result;
}
}
サブクラス化を介して、消費者がそれらを使用するのと同じ方法で保護されたプロパティをユニットテストします。
it('should be extensible via calculate()', () => {
class TestCalculator extends OverlyComplicatedCalculator {
public testWithArrays(array: any[]): any[] {
const concat = (a, b) => [].concat(a, b);
// tests the protected method
return this.calculate(concat, array);
}
}
let testCalc = new TestCalculator();
let result = testCalc.testWithArrays([1, 'two', 3]);
expect(result).toEqual([1, 'two', 3]);
});
この投稿のネクロには申し訳ありませんが、私は触れられていないようないくつかの事柄を検討することを余儀なくされています。
まず第一に - 私たちがユニットテスト中にクラスのプライベートメンバーにアクセスする必要があることに気付いたとき、それは私たちの戦略的または戦術的アプローチにふざけ、誤ってプッシュして単一責任主体に違反したことです。属していない場合の動作実際には構築手続きの独立したサブルーチンにすぎないメソッドにアクセスする必要性を感じることは、これの最も一般的な発生の1つです。しかし、それはあなたの上司がすぐに仕事に出ることを期待していて、その状態にあなたを連れて行くためにあなたが何の朝のルーチンを通過したかを知る必要があります。
このことが起こる他の最も一般的な例は、あなたが自分自身がことわざのある「神の階級」を試すことを試みているのを見つけた時です。それはそれ自体特別な種類の問題ですが、手順の詳細を知る必要があるという同じ基本的な問題を抱えています - しかしそれは話題から外れています。
この特定の例では、Barオブジェクトを完全に初期化する責任をFooBarクラスのコンストラクタに効果的に割り当てました。オブジェクト指向プログラミングでは、コンストラクタは「神聖」であり、それ自体の内部状態を無効にし、下流のどこかで失敗する可能性がある無効なデータから守られるべきです(非常に深い可能性があります)。パイプライン。)
FooBarが構築された時点では準備ができていないBarをFooBarオブジェクトが受け入れることを許可することによってこれを行うことはできませんでした。そして、FooBarオブジェクトをある種の「ハッキング」して問題を解決しました。手。
これは、オブジェクト指向プログラミングの別の目的(Barの場合)に従わなかった結果です。つまり、オブジェクトの状態は完全に初期化され、作成直後にそのパブリックメンバーへの着信呼び出しを処理する準備ができているはずです。現在、これは、コンストラクタがすべてのインスタンスで呼び出された直後を意味するのではありません。複雑な構築シナリオが多数あるオブジェクトがある場合は、作成者の設計パターン(Factory、Builderなど)に従って実装されているオブジェクトに、そのオプションメンバーのセッターを公開することをお勧めします。後者の場合は、目的のオブジェクトの初期化を別のオブジェクトグラフにプッシュすることになります。その目的の1つは、要求しているインスタンスの有効なインスタンスを取得できるようにトラフィックを誘導することです。この作成オブジェクトがそれを提供した後まで "準備ができている"と見なされる。
あなたの例では、Barの "status"プロパティはFooBarがそれを受け入れることができる有効な状態にはないようです - そのためFooBarはその問題を修正するために何かをします。
私が目にしている2番目の問題は、テスト駆動開発を実践するのではなく、コードをテストしようとしているように見えることです。現時点でこれは私自身の意見です。しかし、この種のテストは本当にアンチパターンです。あなたがやろうとしているのは、必要なテストを書いてその後そのテストにプログラミングするのではなく、事後にコードをテストすることができないというコア設計上の問題があることに気付くことです。どちらの方法で問題が発生したとしても、SOLIDの実装を本当に達成したならば、まだ同じ数のテストとコード行になるはずです。それで - あなたが開発努力の開始時に問題に対処することができるのになぜなぜテスト可能なコードにあなたの方法をリバースエンジニアリングするのですか?
あなたがそうしていたなら、あなたはあなたのデザインに対してテストするためにいくらか風変わりなコードを書かなければならないであろうということにずっと早く気づいていたでしょう。簡単にテスト可能です。
私は@toskvに同意します。:-)することを推奨しません
しかし、あなたが本当にあなたのプライベートメソッドをテストしたいのであれば、TypeScriptに対応するコードはコンストラクタ関数のプロトタイプのメソッドに対応していることに気付くでしょう。これは、実行時に使用できることを意味します(ただし、コンパイルエラーが発生する可能性があります)。
例えば:
export class FooBar {
private _status: number;
constructor( private foo : Bar ) {
this.initFooBar({});
}
private initFooBar(data){
this.foo.bar( data );
this._status = this.foo.foo();
}
}
次のように書き換えられます:
(function(System) {(function(__moduleName){System.register([], function(exports_1, context_1) {
"use strict";
var __moduleName = context_1 && context_1.id;
var FooBar;
return {
setters:[],
execute: function() {
FooBar = (function () {
function FooBar(foo) {
this.foo = foo;
this.initFooBar({});
}
FooBar.prototype.initFooBar = function (data) {
this.foo.bar(data);
this._status = this.foo.foo();
};
return FooBar;
}());
exports_1("FooBar", FooBar);
}
}
})(System);
このplunkrを参照してください。 https://plnkr.co/edit/calJCF?p=preview 。
エラーが発生したときにプライベートメソッドやを呼び出すことができます。
expect(new FooBar(/*...*/).initFooBar()).toEqual(/*...*/);
// TS2341: Property 'initFooBar' is private and only accessible within class 'FooBar'
単に// @ts-ignore
を使う:
// @ts-ignore
expect(new FooBar(/*...*/).initFooBar()).toEqual(/*...*/);
このルートでは、クラス外で関数を作成し、その関数を自分のプライベートメソッドに割り当てます。
export class MyClass {
private _myPrivateFunction = someFunctionThatCanBeTested;
}
function someFunctionThatCanBeTested() {
//This Is Testable
}
今、私はどのタイプのOOPルールを破っているのかわかりませんが、質問に答えるために、これがプライベートメソッドのテスト方法です。私はこれの賛否両論について助言することをだれでも歓迎します。
Aaron による答えが私のために働いています:)私はそれを投票するでしょうが、残念ながら私はできません(評判を失う)。
プライベートメソッドをテストすることがそれらを使用し、反対側にきれいなコードを持っている唯一の方法であると私は言います。
例えば:
class Something {
save(){
const data = this.getAllUserData()
if (this.validate(data))
this.sendRequest(data)
}
private getAllUserData () {...}
private validate(data) {...}
private sendRequest(data) {...}
}
これらのプライベートメソッドをモックアウトする必要があるため、これらすべてのメソッドを一度にテストしないことは非常に理にかなっています。つまり、これを全体としてテストするには、単体テストのために多くの設定が必要になります。
ここでは統合テストが必要ですが、TDD(テスト駆動開発)を実践しているのであればE2Eテストは役に立ちませんが、すべての依存関係で上記の方法をテストする最善の方法はエンドツーエンドテストです。どの方法でも構いません。
オプションとして、privateの代わりにprotectedを使用し、yout testで "test"の子を宣言できます。
class FooBar {
private _status: number;
constructor( private foo : Bar ) {
this.initFooBar();
}
//private initFooBar(){
protected initFooBar(){
this.foo.bar( "data" );
this._status = this.fooo.foo();
}
public get status(){
return this._status;
}
}
テスト中:
export class FooBarTest extends FooBar {
...
initFooBar() {
return super.initFooBar();
}
...
}
...
it('initFooBar test', () => {
const fooBarTest = new FooBarTest();
fooBarTest.initFooBar();
expect(fooBarTest.status).toEqual('data matata');
});
...