voidメソッドの単体テスト?


170

何も返さないメソッドを単体テストする最良の方法は何ですか?具体的にはc#です。

私が実際にテストしようとしているのは、ログファイルを取得して特定の文字列について解析するメソッドです。次に、文字列がデータベースに挿入されます。これまでに行われていないことはありませんが、TDDが非常に新しいので、これをテストできるのか、それとも実際にはテストされないのかと思います。


55
「TDD」という用語を使用していない場合は、使用しないでください。TDDではなく単体テストを行っています。TDDを実行している場合、「メソッドをテストする方法」のような質問は決してありません。最初にテストが存在し、次に問題は「このテストに合格する方法は?」しかし、TDDを行っている場合、コードはテスト用に作成され(逆ではありません)、基本的に自分の質問に答えることになります。TDDの結果、コードの形式が異なり、この問題が発生することはありません。ただ明確にします。
Suamere 2014年

回答:


151

メソッドが何も返さない場合、次のいずれかです。

  • 必須 -あなたはオブジェクトにそれ自体に何かをするように頼んでいる..例えば状態を変更する(確認を期待することなく..それが行われると想定される)
  • 情報提供 -何かが起こったことを誰かに通知するだけです(アクションまたは応答を期待せずに)。

命令型メソッド-タスクが実際に実行されたかどうかを確認できます。状態変更が実際に行われたかどうかを確認します。例えば

void DeductFromBalance( dAmount ) 

バランスがこのメッセージを投稿したかどうかを確認することにより、dAmountによる初期値よりも実際にテストできます。

情報メソッド-オブジェクトのパブリックインターフェースのメンバーとしてはまれです...したがって、通常は単体テストされていません。ただし、必要な場合は、通知に対して行われる処理が行われたかどうかを確認できます。例えば

void OnAccountDebit( dAmount )  // emails account holder with info

メールが送信されているかどうかを確認することでテストできます

実際の方法の詳細を投稿してください。そうすれば、人々はよりよく答えることができます。
更新:メソッドは2つのことを実行しています。実際には、独立してテストできる2つの方法に分割しました。

string[] ExamineLogFileForX( string sFileName );
void InsertStringsIntoDatabase( string[] );

String []は、最初のメソッドにダミーファイルと期待される文字列を提供することで簡単に検証できます。2つ目は少し注意が必要です。モック(モックフレームワークでグーグルまたは検索スタックオーバーフロー)を使用してDBを模倣するか、実際のDBにアクセスして、文字列が正しい場所に挿入されているかどうかを確認できます。このスレッドをチェックして良い本を探してください...もしあなたが危機に瀕しているなら、私はPragmatic Unit Testingを推薦します。
コードでは次のように使用されます

InsertStringsIntoDatabase( ExamineLogFileForX( "c:\OMG.log" ) );

1
ねえ、紀州、いい答え。あなたが与えた例...彼らはより多くの統合テストではないですか...?もしそうなら、問題は残っています、実際にVoidメソッドをどのようにテストするのですか...多分それは不可能ですか?
アンディ

2
@andy-「統合テスト」の定義に依存します。命令型メソッドは通常状態を変更するため、オブジェクトの状態を問い合わせる単体テストで検証できます。情報メソッドは、モックリスナー/コラボレーターをプラグインする単体テストによって検証され、テストの対象者が正しい通知を発行することを確認できます。どちらも単体テストで合理的にテストできると思います。
岐阜

@andyデータベースは、アクセサーインターフェイスによってモック/分離できるため、モックオブジェクトに渡されたデータによってアクションをテストできます。
Peter Geiger

62

その副作用をテストします。これも:

  • 例外をスローしますか?(もしそうなら、それがそうであることを確認してください。そうでなければ、あなたが注意していないかもしれないいくつかのコーナーケースを試してください-null引数が最も明白なものです。)
  • パラメータでうまく機能しますか?(それらが変更可能である場合、それがすべきでないときにそれらを変異させますか?
  • 呼び出しているオブジェクト/タイプの状態に適切な影響がありますか?

もちろん、テストできる量には制限があります。たとえば、すべての可能な入力でテストすることは通常できません。実用的にテストします。コードが適切に設計され、正しく実装されていることを確信するのに十分です。また、呼び出し元が期待するものの補足ドキュメントとして機能するのに十分です。


31

いつものように:メソッドが何をすることになっているかをテストしてください!

グローバルな状態(うーん、コードのにおい!)をどこかで変更する必要がありますか?

インターフェイスを呼び出す必要がありますか?

間違ったパラメーターで呼び出されたときに例外をスローする必要がありますか?

正しいパラメーターで呼び出されても例外はスローされませんか?

それは...?


11

Voidの戻り値の型/サブルーチンは古いニュースです。(私が非常に怠惰でなかった場合を除いて)私はVoid戻り型を作成していません(この回答の時から、この質問がされる少し前)。

次のような方法の代わりに:

public void SendEmailToCustomer()

Microsoftのint.TryParse()パラダイムに従うメソッドを作成します。

public bool TrySendEmailToCustomer()

おそらく、長期的に使用するためにメソッドが返す必要のある情報がないかもしれませんが、ジョブの実行後にメソッドの状態を返すことは、呼び出し元にとって非常に役立ちます。

また、boolだけが状態タイプではありません。以前に作成されたサブルーチンが実際に3つ以上の異なる状態(Good、Normal、Badなど)を返すことができる場合がいくつかあります。そのような場合は、

public StateEnum TrySendEmailToCustomer()

ただし、Try-Paradigmは、ボイドリターンをテストする方法に関するこの質問にいくぶん答えますが、他の考慮事項もあります。たとえば、「TDD」サイクルの最中/後に、「リファクタリング」を行い、メソッドで2つのことを実行していることに気づくと、「単一の責任の原則」が破られます。最初にそれを処理する必要があります。次に、依存関係を無効にしている可能性があります...「永続的」データに触れています。

問題のメソッドでデータアクセスを行う場合は、n層またはn層のアーキテクチャにリファクタリングする必要があります。しかし、 "文字列がデータベースに挿入される"と言った場合、実際にはビジネスロジックレイヤーなどを呼び出すことを意味していると想定できます。そうだと思います。

オブジェクトがインスタンス化されると、オブジェクトに依存関係があることがわかります。これは、オブジェクトまたはメソッドに対して依存性注入を行うかどうかを決定する必要がある場合です。つまり、コンストラクタまたは問題のメソッドには新しいパラメータが必要です。

public <Constructor/MethodName> (IBusinessDataEtc otherLayerOrTierObject, string[] stuffToInsert)

ビジネス/データ層オブジェクトのインターフェースを受け入れることができるようになったので、単体テスト中にそれをモックアウトすることができ、依存関係や「偶発的な」統合テストの心配がありません。

したがって、ライブコードでは、REAL IBusinessDataEtcオブジェクトを渡します。ただし、単体テストでは、MOCK IBusinessDataEtcオブジェクトを渡します。そのモックint XMethodWasCalledCountには、インターフェースメソッドが呼び出されたときに状態が更新されるような非インターフェースプロパティを含めることができます。

したがって、ユニットテストは、問題のメソッドを通過し、それらが持つロジックを実行し、IBusinessDataEtcオブジェクト内の1つまたは2つ、または選択されたメソッドのセットを呼び出します。単体テストの最後にアサーションを実行すると、今テストすることがいくつかあります。

  1. 現在はTry-Paradigmメソッドである「サブルーチン」の状態。
  2. Mock IBusinessDataEtcオブジェクトの状態。

構築レベルでの依存性注入のアイデアの詳細については、ユニットテストに関係するため、ビルダーの設計パターンを調べてください。現在のインターフェース/クラスごとにインターフェースとクラスが1つ追加されますが、これらは非常に小さく、ユニットテストを改善するために機能が大幅に向上しています。


この素晴らしい答えの最初のチャンクは、すべての初心者/中級プログラマ向けの驚くべき一般的なアドバイスとして役立ちます。
pimbrouwers 2018

それは次のようなものではありません public void sendEmailToCustomer() throws UndeliveredMailExceptionか?
A.Emad

1
@ A.Emad良い質問ですが、違います。例外のスローに依存してコードのフローを制御することは、よく知られている悪い習慣です。しかし、中にvoid方法、特にオブジェクト指向言語では、それが唯一の現実的な選択肢となっています。マイクロソフトによる最も古い代替手段は、私が議論するTry-Paradigmと、Monads / Maybesのような関数型のパラダイムです。したがって、コマンド(CQS内)は、スローに依存する代わりに、貴重なステータス情報を返すことができGOTOます。スロー(およびgoto)は遅く、デバッグが難しく、適切な方法ではありません。
Suamere

これを片付けていただきありがとうございます。JavaやC ++などの言語でも例外をスローするのはC#固有ですか、それとも一般に悪い習慣ですか?
A.Emad

9

これを試して:

[TestMethod]
public void TestSomething()
{
    try
    {
        YourMethodCall();
        Assert.IsTrue(true);
    }
    catch {
        Assert.IsTrue(false);
    }
}

1
これは必要ではないはずですが、そのように行うことができます
Nathan Alard

StackOverflowへようこそ!コードに説明を追加することを検討してください。ありがとうございました。
オーラスフェア2016年

2
ExpectedAttributeより明確にこのテストを行うために設計されています。
Martin Liversage、2016年

8

次のように試すこともできます。

[TestMethod]
public void ReadFiles()
{
    try
    {
        Read();
        return; // indicates success
    }
    catch (Exception ex)
    {
        Assert.Fail(ex.Message);
    }
}

1
これは私が考える最も簡単な方法です。
Navin Pandit

5

これはオブジェクトに何らかの影響を与えます。効果の結果を問い合わせます。目に見える効果がない場合は、単体テストの価値はありません。


4

おそらくメソッドは何かをして、単純に戻りませんか?

これが事実であると仮定すると、次のようになります。

  1. 所有者オブジェクトの状態を変更する場合は、状態が正しく変更されたことをテストする必要があります。
  2. 一部のオブジェクトをパラメーターとして受け取り、そのオブジェクトを変更する場合は、オブジェクトが正しく変更されていることをテストする必要があります。
  3. 特定のケースで例外がスローされる場合は、それらの例外が正しくスローされることをテストしてください。
  4. 自身のオブジェクトまたはその他のオブジェクトの状態に基づいてその動作が変化する場合は、状態を事前設定し、メソッドが上記の3つのテストメソッドのいずれかで正しいIthroughをテストします。

その方法が何であるかを教えていただければ、もっと具体的にすることができます。


3

Rhino Mocksを使用して、予想される呼び出し、アクション、および例外を設定します。メソッドの一部をモックまたはスタブできると仮定します。メソッドやコンテキストについての詳細をここで知らずに知ることは困難です。


1
単体テストの方法に対する答えは、それを実行するサードパーティのツールを入手することではありません。ただし、単体テストの方法を知っている人は、サードパーティのツールを使用してそれを簡単にすることは許容されます。
Suamere 2014年

2

それが何をしているかによります。パラメータがある場合は、モックを渡して、後でパラメータの正しいセットを使用して呼び出されたかどうかを確認します。


同意-メソッドをテストするモックの動作を確認することは1つの方法です。
ジェフシューマッハ、

0

voidメソッドを呼び出すために使用しているインスタンスは何ですか?Verfiy

例えば:

私の場合、それ_LogはインスタンスでLogMessageあり、テストされるメソッドです。

try
{
    this._log.Verify(x => x.LogMessage(Logger.WillisLogLevel.Info, Logger.WillisLogger.Usage, "Created the Student with name as"), "Failure");
}
Catch 
{
    Assert.IsFalse(ex is Moq.MockException);
}

Verifyテストが失敗するメソッドの失敗が原因で例外がスローされますか?

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.