静的は単体テストでは普遍的に「悪」であり、そうである場合、Resharperがそれを推奨するのはなぜですか?[閉まっている]


85

私は、C#.NETで静的であるユニットテスト(モック/スタブ)依存関係に3つの方法しかないことを発見しました。

これらの2つが無料ではなく、1つがリリース1.0に達していないことを考えると、静的なものをモックするのは簡単ではありません。

それは静的な方法とそのような「悪」(ユニットテストの意味で)を作りますか?もしそうなら、なぜ再シャーパーは私に静的なもの、静的なものを作ることを望んでいますか?(再シャーパーも「悪」ではないと仮定します。)

明確化: メソッドのユニットテストを行い、そのメソッドが別のユニット/クラスで静的メソッドを呼び出す場合のシナリオについて説明しています。単体テストのほとんどの定義では、テスト対象のメソッドが他の単体/クラスの静的メソッドを呼び出すようにした場合、単体テストではなく統合テストになります。(便利ですが、単体テストではありません。)


3
TheLQ:できます。彼は静的メソッドに触れることが多いため、静的メソッドをテストできないことについて話していると思います。したがって、テスト後およびテスト間で状態を変更します。

26
個人的には、「ユニット」の定義を取りすぎていると思います。「ユニット」は、「単独でテストするのに意味のある最小のユニット」でなければなりません。それは方法かもしれないし、それ以上かもしれない。静的メソッドに状態がなく、十分にテストされている場合、2番目の単体テスト呼び出しがあれば(IMO)問題ではありません。
mlk

10
「個人的には、「ユニット」の定義を取りすぎていると思います。」いいえ、彼が標準的な使用法で行っており、あなたがあなた自身の定義を作っているというだけです。

7
「なぜreshaperは静的、静的になり得るものを作ってほしいのですか?」Resharperはあなたに何もさせたくありません。それは単にあなたは変更が可能であることを意識して作っているかもしれないコード分析POVから望ましいです。Resharperは、あなた自身の判断に代わるものではありません!
アダムネイラー

4
@ acidzombie24。通常のメソッドも静的状態を変更できるため、静的メソッドと同じくらい「悪い」ことになります。彼らはまた、より短いライフサイクルで状態を変更できるという事実は、それらをさらに危険にします。(私は、静的メソッドを支持してないんだけど、状態の変更についてのポイントは、それ以上に、あまりにも定期的な方法への打撃である)
mike30

回答:


105

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

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


44
静的メソッドは単体テスト可能ですが、静的メソッドを呼び出すメソッドについてはどうでしょうか?呼び出し元が別のクラスに属している場合、ユニットテストを実行するために分離する必要がある依存関係があります。
ヴァッカーノ

22
@Vaccano:ただし、状態を保持せず、副作用のない静的メソッドを作成する場合は、いずれにしてもスタブとほぼ機能的に同等です。
ロバートハーベイ

20
たぶんシンプルなもの。しかし、より複雑なものは、例外をスローし、予期しない出力を開始する可能性があります(静的メソッドの呼び出し元の単体テストではなく、静的メソッドの単体テストでキャッチする必要があります)、または少なくともそれが私です私が読んだ文献を信じるようになっています。
ヴァッカーノ

21
@Vaccano:出力またはランダムな例外を持つ静的メソッドには副作用があります。
ティコンジャービス

10
@TikhonJelvis Robert 出力について話していました。「ランダムな」例外は副作用ではなく、本質的に出力の形式です。重要なのは、静的メソッドを呼び出すメソッドをテストするときはいつでも、そのメソッドとその潜在的な出力のすべての順列をラップすることであり、メソッドを単独でテストすることはできません。
ニコール

26

静的データと静的メソッドを混同しているようです。Resharperは、正しく覚えていればprivate、クラス内のメソッドを静的にできる場合は静的にすることをお勧めします-これによりパフォーマンスが少し向上すると思います。それはしない静的な「することができ何かを」作るお勧めします!

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

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

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


4
この静的メソッドを呼び出す別のクラスがある場合はどうなりますか?この場合は単純に見えますが、上記の3つのツールのいずれかを除いて分離できない依存関係です。分離しないと、「ユニットテスト」ではなく「統合テスト」になります(異なるユニットがどれだけうまく「統合」するかをテストしているためです。
Vaccano

3
何も起こりません。なぜだろうか?.NETフレームワークには静的メソッドがたくさんあります。単体テストを行う方法でもこれらを使用できないと言っていますか?
ダン・ディプロ

3
さて、コードが.NET Frameworkの製品レベル/品質であれば、先に進んでください。ただし、単体テストのポイントは、単体で単体をテストすることです。他のユニット(静的またはその他)のメソッドも呼び出す場合は、ユニットとその依存関係をテストしています。有用なテストではないが、ほとんどの定義では「単体テスト」ではないと言っているわけではありません。(現在、テスト対象ユニットと静的メソッドを持つユニットをテストしているため)。
ヴァッカーノ

10
おそらく、あなた(または他の人)はすでに静的メソッドをテストし、そのメソッドがあまりにも多くのコードを記述する前に(少なくとも予想どおりに)動作することを示しています。次にテストする部分で何かが壊れている場合は、すでにテストしたものではなく、最初に確認する必要があります。
cHao

6
@Vaccanoでは、Microsoftはどのように.NET Frameworkをテストしますか?フレームワークのクラスの多くは、他のクラスの静的メソッド(などSystem.Math)を参照しますが、静的ファクトリーメソッドなどの豊富さは言うまでもありません。さらに、拡張メソッドなども使用できません。実際、このような単純な関数は現代言語の基礎。これらを単独でテストして(通常は決定論的であるため)、クラスで心配なく使用できます。問題ありません!
ダン・ディプロ

18

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

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();
    }
}

9
幸いなことに、コードの可読性とSOのKISSに関する質問もあります:)
gbjbaanb

デリゲートは簡単ではなく、同じことをしますか?
jk。

@jkは可能ですが、IoCコンテナを使用するのは難しいです。
晴れ

15

Staticsは必ずしも悪ではありませんが、偽物/モック/スタブを使用した単体テストに関しては、選択肢が制限される可能性があります。

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

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

2番目(TypeMock、JustMock、Moleで実装されたモック)は、.NETのプロファイリングAPIに依存しています。CIL命令をインターセプトし、コードのチャンクを偽物に置き換えることができます。これにより、このキャンプ内のTypeMockおよびその他の製品は、静的、シールドクラス、プライベートメソッド-テスト可能に設計されていないもの-をモックできます。

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


14

これをチェックしてください:「静的メソッドは、テスト容易に死です」。引数の短い要約:

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


32
私は静的メソッドでグローバルな状態を維持したり、副作用を引き起こしたりしないため、この議論は私には無関係のようです。リンクした記事は、静的メソッドが単純な手続き型コードに限定され、「一般的な」方法(数学関数など)で動作する場合、根拠のない滑りやすい勾配引数を作成します。
ロバートハーベイ

4
@Robert Harvey-別のクラスの静的メソッドを使用するメソッドをどのように単体テストします(つまり、静的メソッドに依存します)。単にそれを呼び出すようにすると、「ユニットテスト」ではなく、「統合テスト」になります
Vaccano

10
ブログの記事を読むだけでなく、意見に同意しない多くのコメントを読んでください。ブログは単なる意見であり、事実ではありません。
ダン・ディプロ

8
@Vaccano:副作用や外部状態のない静的メソッドは、機能的に値と同等です。それを知っていることは、整数を作成するメソッドの単体テストと同じです。これは、関数型プログラミングが提供する重要な洞察の1つです。
スティーブンエバーズ

3
組み込みシステムや、バグが国際的なインシデントを引き起こす可能性のあるアプリ、IMOを除き、「テスト容易性」がアーキテクチャを促進する場合、最初から最後までテストする方法論を採用する前にそれをひねりました。また、私はすべての会話を支配するすべてのもののXPバージョンにうんざりしています。XPは権威ではなく、業界です。ユニットテストの元の賢明な定義は次のとおり
エリックReppen

5

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

ただし、テストのプロパティを定義するキーはありません。物事が間違っている場合は失敗し、正しい場合は通過します。

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

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

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

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

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


3
単体テストが単一のクラスのテストであると言うものは何もありません。これは、単一ユニットのテストです。単体テストの定義プロパティは、高速で繰り返し可能で独立しているということです。Math.Piメソッドを参照しても、合理的な定義では統合テストになりません。
サラ

すなわち、オプション1。これが最良のアプローチであることに同意しますが、他の人が用語を異なる方法で使用する(合理的またはそうでない)ことを知っておくと便利です。
-soru

4

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

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

前:

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

後:

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

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

次に、Unit-Testコードで、この呼び出しを次のようにスタブできます(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つの理由です。パフォーマンスが低下する場合があります。そのため、そもそも非仮想メソッドが存在します。


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

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

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


インターフェイスで静的メソッド/オブジェクトを宣言できますか?(そうは思わない)。エーテルの方法、私はあなたの静的メソッドがテスト中のメソッドによって呼び出されるときに言及しています。呼び出し元が別のユニットにいる場合、静的メソッドを分離する必要があります。上記の3つのツールのいずれかを使用してこれを行うのは非常に困難です。
ヴァッカーノ

1
いいえ、インターフェイスで静的を宣言することはできません。意味がありません。
MIA

3

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


2
同意しません。ほとんどの場合、Resharperが静的なものを作成できると言ったとき、それはクラス内の2つ以上のメソッドに共通する少しのコードであるため、独自のプロシージャに抽出しました。それをヘルパーに移動することは、意味のない複雑さです。
ローレンペクテル

2
ドメインが非常に単純で、将来の変更に適さない場合にのみ、あなたの意見を見ることができます。別の言い方をすれば、「意味のない複雑さ」と呼ぶものは、人間にとって読みやすいデザインです。存在する単純で明確な理由を持つヘルパークラスを持つことは、何らかの形でSoCおよび単一責任の原則の「マントラ」です。さらに、この新しいクラスがメインクラスの依存関係になることを考えると、いくつかのパブリックメンバーを公開する必要があり、当然の結果として、依存関係として機能する場合は単独でテスト可能で、モックが簡単になります。
g1ga

1
質問には関係ありません。
イグビーラージマン14年

2

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

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


0

Resharperはあなたにガイダンスを提供し、セットアップされたコーディングガイドラインを適用すると信じています。Resharperを使用して、メソッドは静的である必要があることを教えてくれたとき、インスタンス変数に作用しないプライベートメソッドにバインドされます。

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

パブリックな静的メソッドのテスト容易性に関しては、静的メソッドが静的状態に触れるとユニットテストが難しくなります。個人的には、これを最小限に抑え、静的メソッドを可能な限り純粋な関数として使用し、テストフィクスチャを介して制御できるメソッドに依存関係が渡されるようにします。ただし、これは設計上の決定です。


別のクラスの静的メソッドを呼び出すテスト対象のメソッド(静的ではない)がある場合に言及しています。その依存関係は、上記の3つのツールのいずれかでのみ分離できます。分離しない場合は、単体テストではなく統合テストです。
ヴァッカーノ

インスタンス変数へのアクセスは、本質的に静的ではないことを意味します。静的メソッドが取り得る唯一の状態はクラス変数であり、発生する唯一の理由は、シングルトンを扱っているため選択の余地がない場合です。
ローレンペクテル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.