web-dev-qa-db-ja.com

データベース更新関数の呼び出しには、どのような単体テストを記述する必要がありますか?

以下のような多くの関数を扱っているので、どのようなユニットテストを記述する必要があるのか​​わかりません。

public void UpdateEmployeeClockIn(int employeeId)
{
var sql = string.format("Update Employee set ClockIn = GETUTCDATE() where employeeId={0}", employeeId);

db.ExecuteNonQuery(sql);
}

このメソッドをSQLステートメントを返す可能性があるメソッドにリファクタリングできると考えています。SQLステートメントでいくつかのテストを実行して、正しいwhere句などがあることを確認できます。このようなコードを実行し続けると、テストの方法や目的がわからないだけです。

6
Ben

それは、このコードとデータベースの間の抽象化のレイヤーに依存します。

抽象化があり、実際に実際のデータベースにアクセスするオブジェクトの代わりにモックを置くことができる場合、ユニットテストは、メソッドが任意の引数で呼び出されたときに、正しいSQLクエリがモックに送信されることを確認します。 123と言います。実際のコードとユニットテストがホワイトボックステストであることを考えると、ここでテストするものは他にありません。入力をサニタイズしている場合、たとえばemployeeIdが負またはゼロにならないことを確認することにより、状況が異なる場合があります。

抽象化がない場合、最初にリファクタリングしないと、このメソッドの単体テストを作成できません。自動テストを作成できますが、それらは統合、システム、機能、または受け入れテストであり、単体テストではありません。この場合、以下を実行します。

  • 既存の従業員のIDを渡し、データベースエントリが変更されたことを確認する同様のテスト。

  • 存在しないIDを使用したテスト。

  • 読み取り専用モードで設定されたデータベース、または到達不能なデータベースで実行するテスト(クラスターがある場合はどちらも特に重要です)。


ご了承ください:

  • 実際のコードではSQLインジェクションは不可能ですが、これはデータベースを使用するための間違った方法です。パラメータ化されたクエリを使用すると、データベースエンジンは実行プランをキャッシュできます。あなたの場合、あなたは事実上これをするのを妨げています。

  • 入力をサニタイズしていません。データベースに無効な値でエラーを返させたい場合、これは意図的なものである可能性がありますが、通常、これは開発者が単に実行するのを忘れたことを示しています。これにより、データベースに対してDOS攻撃を実行しやすくなります。

  • 例外をキャッチしていません。それは意図的ですか?たとえば、ビジネスレイヤーまたはプレゼンテーションレイヤーが例外を処理するために、例外をスタックに渡すことを好む場合があります。ただし、現在、いくつかの例外に対処しなければならない場合があります。たとえば、クラスターにアクセスする場合、読み取り専用のインスタンスや単純な接続の喪失に対処する必要があるかもしれません。このメソッドは、単にユーザーにエラーを表示する代わりに、クエリを再試行するか、特定の状況で意味のあることを行うことによって、例外を適切に処理できます。

8

@Arseniの回答に加えて。

メソッドはデータベースによって異なります。 db変数も、データベースタイプやデータベースバージョンなどに依存するクエリを渡すインターフェイスです。

ユニットテストは、IO操作(ファイルシステム、Webサービス、またはデータベース)から切り離されたテストです。ユニットテストは、開発者が1分ごとに実行する必要があるテストであり、テストの実行時間は、非常に短く(秒)、関連するIO操作でのテストには長い時間がかかります。

IOの操作に依存するテストは存在する必要がありますが、「統合テスト」と呼ばれていました。

したがって、メソッドがデータレイヤーの一部である場合は、別のSELECTクエリを実行して、更新後にClockIn列がNULLではないことをアサートできる統合テストを作成できます。

ただし、メソッドがビジネスロジックの一部である場合、適切な単体テストを行うには、このメソッドをインターフェイスなどに抽象化する必要があります。次に、UpdateEmployeeClockInを使用するメソッドのテスト中に、UpdateEmployeeClockInメソッドが正しいパラメーターで呼び出されたことをアサートできます。

あなたの方法には、テストをほとんど不可能にする別のケースがあります。システムの日時関数ClockIn = GETUTCDATE()を使用しています。関数GETUTCDATE()は常に異なる値を返します。結果をアサートできる期待値を作成することはできません。
データベースの時間を使用しているので、時間メソッドをインターフェースの専用関数に移動することをお勧めします。モックアップして、テスト用に定義された期待値を返すことができるもの

public interface IDataService
{
    void UpdateEmployeeClockIn(int employeeId, DateTime timeIn);
    DateTime GetCurrentTime();       
}

次に、ビジネスロジックレイヤーで

public class EmployerManager
{
    private readonly IDataService _dataService;

    public Employer(IDataService dataService)
    {
        _dataService = dataService;
    }

    public void UpdateClockinWithCurrentTime(int employerId)
    {
        DateTime currentTime = _dataService.GetCurrentTime();
        _dataService.UpdateEmployeeClockIn(employerId, currentTime);
    }
}

この場合、UpdateClockinWithCurrentTimeのモックを介してIDataServiceが正しいパラメーターで実行されたことをテストできるメソッドUpdateEmployeeClockInの単体テストを作成できます。

2
Fabio