web-dev-qa-db-ja.com

staticは単体テストで普遍的に「悪」であり、そうであればなぜResharperはそれを推奨するのですか?

C#.NETで静的である依存関係を単体テスト(モック/スタブ)する方法は3つしかないことがわかりました。

これらのうち2つは無料ではなく、1つはリリース1.0に達していないため、静的なもののモックはそれほど簡単ではありません。

それは静的メソッドやそのような「悪」(単体テストの意味で)ですか?もしそうなら、なぜresharperは私に静的なもの、静的なものを作ることを望んでいるのですか? (リシャーパーが「悪」でもないと仮定します。)

説明:メソッドの単体テストを行い、そのメソッドがで静的メソッドを呼び出すシナリオについて話している異なるユニット/クラス。ユニットテストのほとんどの定義では、テスト中のメソッドに他のユニット/クラスの静的メソッドを呼び出させるだけの場合、ユニットテストはでない、あなたはintegrationテストです。 (便利ですが、単体テストではありません。)

87
Vaccano

ここで他の答えを見ると、静的な状態を保持したり副作用を引き起こしたりする静的メソッド(本当に悪い考えのように聞こえます)と、値を返すだけの静的メソッドの間には混乱があるかもしれません。

状態を保持せず、副作用を引き起こさない静的メソッドは、簡単に単体テストできます。実際、私はそのような方法を「貧乏人」形式の関数型プログラミングと見なしています。メソッドにオブジェクトまたは値を渡すと、オブジェクトまたは値が返されます。これ以上何もない。そのような方法が単体テストに悪影響を及ぼすことはまったくわかりません。

108
Robert Harvey

Static dataとstatic methodsを混同しているようです。私が正しく覚えている場合、Resharperは、クラス内でprivateメソッドを静的にできる場合はそれを静的にすることをお勧めします。これにより、パフォーマンスが少し向上すると思います。 しないでください「できるすべてのもの」を静的にすることをお勧めします!

静的メソッドに問題はなく、テストは簡単です(静的データを変更しない限り)。たとえば、数学ライブラリを考えてみてください。これは、静的メソッドを持つ静的クラスに適した候補です。あなたがこのような(工夫された)メソッドを持っているなら:

public static long Square(int x)
{
    return x * x;
}

これは非常にテスト可能であり、副作用はありません。たとえば、20を渡すと400が返されることを確認するだけです。問題ありません。

27
Dan Diplo

ここでの実際の質問が「このコードをテストするにはどうすればよいですか?」

public class MyClass
{
   public void MethodToTest()
   {
       //... do something
       MyStaticClass.StaticMethod();
       //...more
   }
}

次に、コードをリファクタリングして、通常のように静的クラスの呼び出しを次のように挿入します。

public class MyClass
{
   private readonly IExecutor _externalExecutor;
   public MyClass(_IExecutor executor)
   {
       _exeternalExecutor = executor;
   }

   public void MethodToTest()
   {
       //... do something
       _exetrnalExecutor.DoWork();
       //...more
   }
}

public class MyStaticClassExecutor : IExecutor
{
    public void DoWork()
    {
        MyStaticClass.StaticMethod();
    }
}
18
Sunny

Staticsは必ずしも悪であるとは限りませんが、Fakes/Mocks/Stubsを使用した単体テストに関しては、選択肢が制限される可能性があります。

モックには2つの一般的なアプローチがあります。

最初の1つ(従来-RhinoMocks、Moq、NMock2によって実装され、手動のモックとスタブもこのキャンプにあります)は、テストシームと依存性注入に依存しています。いくつかの静的コードを単体テストしていて、依存関係があるとします。このように設計されたコードで頻繁に発生するのは、staticが独自の依存関係を作成し、dependency inversionを反転させることです。この方法で設計されたテスト対象のコードにモックインターフェイスを挿入できないことがすぐにわかります。

2つ目(TypeMock、JustMock、およびMolesによって実装されたすべてのものをモックする)は、.NETの Profiling API に依存しています。それはあなたのCIL命令のいずれかを傍受し、あなたのコードのチャンクを偽物で置き換えることができます。これにより、このキャンプのTypeMockおよびその他の製品は、静的、シールされたクラス、プライベートメソッドなど、テスト可能に設計されていないものをモックできます。

2つの学派の間で議論が続いています。 1つは、SOLIDの原則に従い、テストしやすいように設計することです(多くの場合、静力学を容易にすることを含みます)。もう1つはTypeMockを購入すれば心配しないでください。

15
azheglov

これをチェックしてください: "Static Methods are Death to Testability" 。議論の短い要約:

単体テストを行うには、コードの小さな部分を取り、その依存関係を再配線し、分離してテストする必要があります。これは、静的メソッドではグローバル状態にアクセスする場合だけでなく、他の静的メソッドを呼び出すだけの場合でも困難です。

14
Rafał Dowgird

単純な真実がめったに認められないのは、クラスに別のクラスへのコンパイラから見える依存関係が含まれている場合、そのクラスから分離してcannotがテストされるということです。テストのように見せかけることができ、レポートにテストのように表示されます。

ただし、テストの主要な定義プロパティはありません。物事が間違っているときに失敗し、正しいときに合格する。

これは、静的呼び出し、コンストラクター呼び出し、および基本クラスまたはインターフェイスから継承されていないメソッドまたはフィールドへの参照に適用されます。クラス名がコードに表示される場合、それはコンパイラから見える依存関係であり、それなしでは有効にテストできません。小さなチャンクは単に有効なテスト可能な単位ではありませんです。あたかもそれをあたかも扱ったとしても、テストフレームワークが「テスト合格」と言うために使用するXMLを出力する小さなユーティリティを書くよりも意味のない結果になります。

その場合、3つのオプションがあります。

  1. ユニットテストを定義して、クラスとそのハードコードされた依存関係で構成されるユニットのテストを行います。これは機能し、循環依存関係を回避します。

  2. テストを担当するクラス間にコンパイル時の依存関係を作成しないでください。結果として得られるコードスタイルを気にしない限り、これは機能します。

  3. 単体テストではなく、統合テストを実行してください。これは機能しますが、統合テストという用語を使用する必要がある他のものと競合しない場合に限ります。

5
soru

それについて二つの方法はありません。 ReSharperの提案とC#のいくつかの便利な機能は、すべてのコードに対して分離されたアトミックユニットテストを作成している場合は、それほど頻繁には使用されません。

たとえば、静的メソッドがあり、それをスタブ化する必要がある場合は、プロファイルベースの分離フレームワークを使用しない限りできません。呼び出し互換の回避策は、メソッドの先頭を変更してラムダ表記を使用することです。例えば:

前:

    public static DBConnection ConnectToDB( string dbName, string connectionInfo ) {
    }

後:

    public static Func<string, string, DBConnection> ConnectToDB (dbName, connectionInfo ) {
    };

2つは呼び出し互換です。呼び出し元を変更する必要はありません。関数の本体は同じままです。

次に、ユニットテストコードで、この呼び出しを次のようにスタブできます(Databaseと呼ばれるクラスにあると想定)。

        Database.ConnectToDB = (dbName, connectionInfo) => { return null|whatever; }

完了したら、元の値に置き換えてください。これは、try/finallyを使用して行うか、ユニットテストのクリーンアップで、すべてのテストの後に呼び出されるものを使用して、次のようなコードを記述できます。

    [TestCleanup]
    public void Cleanup()
    {
        typeof(Database).TypeInitializer.Invoke(null, null);
    }

これにより、クラスの静的初期化子が再度呼び出されます。

Lambda Funcsは通常の静的メソッドほどサポートが豊富ではないため、このアプローチには次の望ましくない副作用があります。

  1. 静的メソッドが拡張メソッドである場合は、まずそれを非拡張メソッドに変更する必要があります。 Resharperはこれを自動的に実行できます。
  2. 静的メソッドのデータ型のいずれかがOffice用などの組み込み相互運用アセンブリである場合は、メソッドをラップするか、タイプをラップするか、タイプを「オブジェクト」に変更する必要があります。
  3. Resharperの署名変更リファクタリングツールは使用できなくなりました。

しかし、静的を完全に避け、これをインスタンスメソッドに変換するとします。メソッドが仮想であるか、インターフェースの一部として実装されていない限り、それはまだモック可能ではありません。

したがって、実際には、静的メソッドをスタブ化するための救済策を提案する人は、それらをインスタンスメソッドにすることです。また、仮想メソッドやインターフェイスの一部ではないインスタンスメソッドにも反対します。

では、なぜC#に静的メソッドがあるのでしょうか。非仮想インスタンスメソッドを使用できるのはなぜですか?

これらの「機能」のいずれかを使用する場合、分離されたメソッドを作成することはできません。

では、いつそれらを使用しますか?

誰もがスタブしたいと思わないコードにそれらを使用してください。いくつかの例:StringクラスのFormat()メソッド、ConsoleクラスのWriteLine()メソッド、MathクラスのCosh()メソッド

そしてもう1つ..ほとんどの人はこれを気にしませんが、間接呼び出しのパフォーマンスについて可能であれば、インスタンスメソッドを回避するもう1つの理由です。パフォーマンスが低下する場合があります。そもそも非仮想メソッドが存在するのはそのためです。

4
zumalifeguard

長い間、誰もまだ本当に簡単な事実を述べていないことがわかります。メソッドを静的にすることができるとresharperが言った場合、それは私にとって大きなことを意味します、彼の声が私に言うのを聞くことができます。ヘルパークラスか何かで」。

3
g1ga
  1. これは、静的メソッドの方がインスタンスメソッドよりも呼び出しが「速い」ためだと思います。 (これはマイクロ最適化の匂いがあるため引用符で囲みます)参照 http://dotnetperls.com/static-method
  2. それは状態を必要としないため、どこからでも呼び出すことができ、誰かが必要とするものがそれだけである場合は、初期化のオーバーヘッドを取り除きます。
  3. 私がそれをモックしたいのであれば、それは一般的にインターフェイスで宣言されている慣習だと思います。
  4. それがインターフェイスで宣言されている場合、R#は静的にすることを提案しません。
  5. それが仮想であると宣言されている場合、R#は静的にすることも提案しません。
  6. 状態(フィールド)を静的に保持することは、常に慎重に検討する必要があるものです。静的な状態とスレッドは、リチウムと水のように混ざります。

この提案を行うツールはR#だけではありません。 FxCop/MSコード分析も同じようにします。

私は一般的に、メソッドが静的である場合、一般に、そのままテスト可能であるべきだと言っています。これは、いくつかの設計上の考慮事項をもたらし、おそらく私が今考えているよりも多くの議論をもたらすので、辛抱強く反対票とコメントを待っています...;)

3
MIA

静的メソッドが他のメソッド内から呼び出された場合、そのような呼び出しを防止または置換することはできません。つまり、これらの2つのメソッドは単一のユニットを作成します。あらゆる種類の単体テストは、両方をテストします。

この静的メソッドがインターネットと通信したり、データベースを接続したり、GUIポップアップを表示したり、ユニットテストを完全な混乱に変換したりする場合、簡単な回避策はありません。このような静的メソッドを呼び出すメソッドは、単体テストの恩恵を受ける純粋な計算コードが多数ある場合でも、リファクタリングなしではテストできません。

2
h22

私は、Resharperがガイダンスを提供し、セットアップ時に使用したコーディングガイドラインを適用していると思います。 Resharperを使用していて、メソッドは静的である必要があると言われている場合、インスタンス変数には作用しないプライベートメソッドにバインドされています。

テスト可能性については、とにかくプライベートメソッドをテストしてはならないので、このシナリオは問題になりません。

公開されている静的メソッドのテスト容易性については、静的メソッドが静的状態に触れると、単体テストが難しくなります。個人的には、これを最小限に抑え、テストフィクスチャを介して制御できる依存関係がメソッドに渡される場合は、静的メソッドをできるだけ純粋な関数として使用します。ただし、これは設計上の決定です。

0
aqwert