印刷用ページ       送信     
クリックして評価とフィードバックをお寄せください
 テストの実行: LINQ を使用して SQL ストアド プロシージャを...
Related Articles

今月は、Windows Presentation Foundation アプリケーションで UI テストを自動化する手法を探ります。

James McCaffrey

MSDN Magazine March 2009

...

Read more!

フロー ドキュメントでは、テキストのレイアウトや改ページの調整をきわめて柔軟に行うことができますが、データ バインドをサポートしないため、コンテンツを動的に変更できません。この記事では、この問題を解決するコンポーネントの作成方法を説明します。

Vincent Van Den Berghe

MSDN Magazine April 2009

...

Read more!

Entity Framework を使用して n 層アプリケーションを構築する際に注意すべきいくつかのアンチパターンについて、Danny Simmons が解説します。

Daniel Simmons

MSDN Magazine June 2009

...

Read more!

今月は、Dino が引き続き動的な Silverlight コンテンツの管理を取り上げ、キャッシュと分離ストレージについて説明します。

Dino Esposito

MSDN Magazine February 2009

...

Read more!

Charles Petzold が、ItemsControls のパフォーマンスを強化するためのさまざまな技法を紹介します。

Charles Petzold

MSDN Magazine March 2009

...

Read more!

Also by this Author

複数のオプションから最良のものを 1 つ選択する集団意思決定の分析手法については、幅広い研究が行われてきました。Dr. James McCaffrey が 5 つの方式を取り上げて要点を説明します。

Dr. James McCaffrey

MSDN Magazine November 2008

...

Read more!

今回の「テストの実行」コラムでは、Windows PowerShell を使用して超軽量の UI テスト自動化を実行する方法を説明します。

Dr. James McCaffrey

MSDN Magazine December 2007

...

Read more!

今月は、Windows Presentation Foundation アプリケーションで UI テストを自動化する手法を探ります。

James McCaffrey

MSDN Magazine March 2009

...

Read more!

今月は、別のストリームから読み取ったり、別のストリームに書き込んだりするデータの変換に使用できる重要な手法を紹介します。

Dr. James McCaffrey

MSDN Magazine March 2007

...

Read more!

Dr. James McCaffrey

MSDN Magazine May 2007

...

Read more!

Popular Articles

When incorporating the ASP.NET DataGrid control into your Web apps, common operations such as paging, sorting, editing, and deleting data require more effort than you might like to expend. But all that is about to change. The GridView control--the successor to the DataGrid-- extends the DataGrid's functionality it in a number of ways. First, it fully supports data source components and can automatically handle data operations, such as paging, sorting, and editing, as long as its bound data source object supports these capabilities. In addition, ...

Read more!

Microsoft patterns & practices の Composite Application Guidance for WPF で複合アプリケーションを作成する利点を紹介します。

Glenn Block

MSDN Magazine September 2008

...

Read more!

Paul DiLascia

MSDN Magazine August 2002

...

Read more!

Kenny Kerr 氏は、Visual C++ を現代的かつ便利に使用できるようになる Visual C++ 2008 Feature Pack を絶賛しています。

Kenny Kerr

MSDN Magazine May 2008

...

Read more!

James Avery does it again with his popular list of developer tools. This time he covers the best Visual Studio add-ins available today that you can download for free.

James Avery

MSDN Magazine December 2005

...

Read more!

テストの実行
LINQ を使用して SQL ストアド プロシージャをテストする
Dr. James McCaffrey

バックエンド SQL Server® データベースにアクセスして操作するプログラムのテストが必要になることは、ごく一般的です。そのようなケースの多くでは、アプリケーションが SQL ストアド プロシージャを使用してバックエンド データと対話します。この種のシナリオにおいて、ストアド プロシージャをテスト対象システムの補助メソッドと見なすことができます。したがって、システムの他のモジュールと同様に、ストアド プロシージャをテストする必要があります。
SQL ストアド プロシージャのテストを実行するにはいくつかのアプローチがありますが、私は、LINQ を使用することでテストの自動化が飛躍的に容易になることを発見しました。今月のコラムでは、LINQ (具体的には LINQ to SQL プロバイダ) を使用して SQL ストアド プロシージャをテストする方法について説明します。この記事の内容は、C# および SQL に関して中級レベルのスキルを持ちながら LINQ の経験を持たない読者を対象にしています。
図 1 は、SQL ストアド プロシージャをテストするときに実行する必要のある主要なアクティビティをデモンストレーションするために私が作成した、小さな C# コンソール アプリケーション テスト ハーネスを示しています。ここでは、usp_DeleteMovie という名前の単純かつ代表的なストアド プロシージャをテストします。このストアド プロシージャは、dbMovies という名前の映画関連データのデータベースに含まれています。
Figure 1 Testing a Stored Procedure Using LINQ (画像を拡大するには、ここをクリックします)
それぞれのテスト ケースについて、テスト ベッド データベースを初期化し、特定の映画 ID を使って usp_DeleteMovie を呼び出し、最後に dbMovies の結果の状態が適切であるかどうかをチェックします。図 1 を見てもおわかりにならないと思いますが、私は LINQ を使用することで、ADO.NET と T-SQL を使用した場合よりも簡単にテスト ハーネスを作成しています。以降のセクションでは、ダミーの dbMovies データベースとテスト対象の usp_DeleteMovie ストアド プロシージャについて説明します。続いて、コードを示し、使用されている LINQ 手法を説明します。最後に、このコラムで示した例を各自の要件に応じて変更および拡張する方法についても説明します。

テスト対象システム
ハーネスについて掘り下げる前に、テスト対象システムのサンプル データベースを見て、テストの自動化を理解しておきましょう。一般的な開発環境において、開発者、テスト担当者、およびデータベース アーキテクトは、通常、各自が持つバックエンド データベースのローカル コピーを操作します。ここで、テスト対象データベースの現在のバージョンを作成する SQL スクリプトを使用できると仮定しましょう。実際のデータベース スクリプトはサイズも大きく複雑になると思いますが、図 2 に示すサンプルのデータベース作成スクリプトは、主要概念を明確にするために単純化してあります。
use master
go

if exists(select * from sys.sysdatabases where name='dbMovies')
  drop database dbMovies
go

create database dbMovies
go

use dbMovies
go

create table tblMain
(
  movID char(3) primary key,
  movTitle varchar(35) null,
  movRunTime int null
)
go

create table tblPrices
(
  movID char(3) primary key,
  movPrice money
)
go

create procedure usp_DeleteMovie
  @movID char(3)
as
  delete from tblMain where movID = @movID
  delete from tblPrices where movID = @movID
go

create procedure usp_GetMovieDataByPrice
  @movPrice money
as
  select m.movID, m.movTitle, p.movPrice, m.movRunTime
  from tblMain as m
  inner join tblPrices as p
    on m.movID = p.movID
  where p.movPrice = @movPrice
go

create procedure usp_GetMoviePrice
  @movID char(3),
  @price money out
as
  declare @ct int
  select @price = movPrice from tblPrices where movID = @movID
  select @ct = count(*) from tblPrices where movID = @movID
  return @ct
go

データベースの名前は dbMovies で、このデータベースには tblMain と tblPrices の 2 つのテーブルが含まれています。tblMain テーブルには、(主キーである) 映画 ID 列、タイトル列、上映時間 (分数) が格納される列があります。tblPrices テーブルには、映画 ID 列と、映画料金が格納される列があります。
このデータベースには、usp_DeleteMovie、usp_GetMovieDataByPrice、および usp_GetMoviePrice の 3 つのストアド プロシージャが含まれています。これらがユーザー定義ストアド プロシージャであることを示し、システム ストアド プロシージャ (sp_) や拡張ストアド プロシージャ (xp_) と区別するために、ストアド プロシージャ名を usp_ で開始しています。ここで、1) usp_DeleteMovie はデータベースの状態を変更するが明示的に値を返さないこと、2) usp_GetMovieDataByPrice は 1 つまたは複数の SQL 行セットを返すが dbMovies の状態を変更しないこと、3) usp_GetMoviePrice は 2 つのスカラ値 (1 つは戻り値、1 つは out 型パラメータ) を返すがデータベースの状態を変更しないことに注意してください。
これらは、SQL ストアド プロシージャをテストするときの 3 つの最も一般的な状況 (戻り値を返さずに状態を変更する、行セットを返すが状態を変更しない、スカラ値を返すが状態を変更しない) を表しています。この 3 つのシナリオについては後で簡単に説明します。ここで、図 1 のテストの対象となっている usp_DeleteMovie ストアド プロシージャを詳しく見てみましょう。このプロシージャは、次に示すように、映画 ID を入力パラメータとして受け取り、tblMain テーブルと tblPrices テーブル内の対応する行を削除します。
create procedure usp_DeleteMovie
  @movID char(3)
as
  delete from tblMain where movID = @movID
  delete from tblPrices where movID = @movID
テストの自動化という観点から、ストアド プロシージャに関連付けられた dbMovies データベースを既知の状態に設定することで準備する方法を理解しておく必要があります。さらに、ストアド プロシージャを呼び出す方法と、ストアド プロシージャによってデータベースの状態が適切に変更されたかどうかを調べる方法についても理解しておく必要があります。後でわかりますが、これらのタスクは、LINQ を使用することで、以前のアプローチを使用した場合よりもはるかに簡単に実行できます。

テスト ハーネス
それでは、図 1 に示す出力を生成したテスト ハーネスのコードを見ていきましょう。全体の構造は、図 3 に示してあります。ここで説明するすべての例の完全なソース コードが、MSDN® Magazine Web サイトのこのコラムに付属するコード サンプルに含まれています。
using System;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Security.Cryptography;

namespace Run
{
  class Program
  {
    static void Main(string[] args)
    {
      try
      {
        Console.WriteLine("\nBegin test automation using LINQ");
        string connString =
          "server=(local);database=dbMovies;Trusted_Connection=true";
        Console.WriteLine   
          ("\nStored procedure under test = usp_DeleteMovie");
        DbMovies db = new DbMovies(connString);

        string[] testCaseData = new string[] {
          "TC001:002:85-A0-D9-28-7B-AA-8D-5F-4C-43-B5-B6-A5-99-F4-63",
          "TC002:003:44-FA-D2-38-49-98-F0-BD-16-BD-9E-B6-0C-F1-73-35" };

        foreach (string testCase in testCaseData)
        {
          // parse inputs, expected state
          // call ResetDatabase()
          // call usp_DeleteMovie
          // call GetDatabaseState()
          // determine pass/fail
        } 
        Console.WriteLine("\n====================================");
        Console.WriteLine("\nEnd test run");
      }
      catch (Exception ex)
      {
        Console.WriteLine("Fatal: " + ex.Message);
      }
    } 

    static void ResetDatabase(string connString) { . . . }
    static string GetDatabaseState(string connString) { . . . }
  } 
}

LINQ は、Visual Studio® 2008 に含まれている Microsoft® .NET Framework 3.5 の一部です。私は、Visual Studio 2008 を使用して、.NET Framework 3.5 をターゲットとする C# コンソール アプリケーションを作成しました。このとき、Visual Studio によって生成された "using System.Collections.Generic" ステートメントを削除しました。その理由は、私のハーネスではこの名前空間に含まれるメソッドを使用していないからです。Visual Studio では、既定により、System.Linq 名前空間に対する using ステートメントが追加されていることに注意してください。ここでは、System.Data.Linq アセンブリへの参照を追加し、対応する using ステートメントを追加してあります。
LINQ は、実際は、LINQ to SQL および LINQ to XML を含むテクノロジのコレクションを示すために使用される用語です。LINQ to SQL は、以前に DLINQ と呼ばれていたもので、今でもこの古い名称が多くのオンライン リファレンスで使用されています。LINQ を使用してストアド プロシージャをテストするために、System.Linq に含まれる基本 LINQ 機能と System.Data.Linq 名前空間に指定されている LINQ to SQL 機能の組み合わせを使用しています。さらに、データベースの状態を確認するために暗号化ハッシュ メソッドを使用するので、System.Security.Cryptography 名前空間を参照する using ステートメントも追加しました。先頭部分の WriteLine ステートメントの後には、テスト ベッド データベースに対する接続文字列を設定しました。
string connString = 
  "server=(local);database=dbMovies;Trusted_Connection=true";
後ほど説明しますが、接続文字列をハーネスの補助ヘルパー メソッドに渡すようにすると便利です。その次にあるのが、重要な LINQ to SQL の概念です。ここでは、SQL データベースを表すオブジェクトをインスタンス化しています。
DbMovies db = new DbMovies(connString);
LINQ to SQL はマジックではないので、私のデータベースを自動的に理解してはくれません。テスト ハーネスの舞台裏として、DbMovies クラスを含む dbMovies データベースへのアクセスおよび操作に必要なすべての情報をカプセル化する C# コードを作成しました。では、このコードはどこで使用するのでしょう。
LINQ to SQL マッピング コードを入手するには、主に 3 つのアプローチがあります。sqlmetal.exe コマンド ライン ツールを使用する方法、Visual Studio 2008 オブジェクト リレーショナル デザイナ (O/R デザイナ) GUI ツールを使用する方法、およびマッピング コードを最初から作成する方法です。私自身は、柔軟性があり自動化に対応する sqlmetal.exe ツールを好んで使用します。ここでは、C# ラッピング コードを生成するために、Visual Studio 2008 コマンド シェルを起動し、テスト ハーネス プロジェクトのルート ディレクトリに移動し、次のコマンドを入力しています。
> sqlmetal.exe /server:(local) /database:dbMovies /sprocs
 /code:mapping.cs
ここでは、既定の Windows® 認証モードを使用して dbMovies データベースに接続していますが、sqlmetal.exe は必要に応じてユーザーおよびパスワード情報を受け取ることができます。さらに、/sprocs コマンド ライン スイッチを指定して、sqlmetal.exe でストアド プロシージャを dbMovies にラップするコードを生成することを指定し、/code スイッチを使用して、O/R デザイナ ツールによって生成されるような中間 .dbml (データベース マークアップ言語) ファイルではなくソース コードを出力することを指定しています。
mapping.cs ファイルを生成した後、ファイルを Visual Studio プロジェクトに追加します。mapping.cs ファイルには、SQL データベースにアクセスするために必要なすべてのラッピング コード (接続文字列を受け取るコンストラクタを含む DbMovies クラスなど) が格納されています。
public partial class DbMovies : System.Data.Linq.DataContext
{
  public DbMovies(string connection) : 
    base(connection, mappingSource)
  {
    //... 
  }
  // wrappers to tables, stored procedures, etc.
}
図 4 は、テスト ハーネスとテスト ベッド データベースの関係を簡単に示した図です。テスト ハーネスで DbMovies オブジェクトをインスタンス化した後、usp_DeleteMovie ストアド プロシージャをテストするためのテスト ケース データをいくつか設定しています。
Figure 4 Relation between Database and Test Harness (画像を拡大するには、ここをクリックします)
string[] testCaseData = new string[] {
  "TC001:002:85-A0-D9-28-7B-AA-8D-5F-4C-43-B5-B6-A5-99-F4-63",
  "TC002:003:44-FA-D2-38-49-98-F0-BD-16-BD-9E-B6-0C-F1-73-35" };
ここでは 2 つのテスト ケースしか使用していませんが、現実のテスト シナリオでは、数百または数千のテスト ケースを使用する状況も考えられます。この例では、わかりやすくするために、テスト ケース データをテスト ハーネスに直接埋め込んでいます。フラット テキスト ファイル、XML ファイル、SQL テーブル、またはその他のデータ ストアの形式で外部に保存したデータを使用することもできます。一般に、外部テスト ケース データには、内部テスト ケース データよりも変更や共有が簡単であるという利点があります。
このテスト ケース データは、コロン文字で区切られた 3 つのフィールドから構成されています。最初のフィールドはテスト ケース ID です。2 つ目のフィールドは映画 ID を表し、テスト対象の usp_DeleteMovie ストアド プロシージャへの入力として使用されます。3 つ目のフィールドは、16 バイトの MD5 暗号化ハッシュ値を表す文字列であり、テスト対象のストアド プロシージャが呼び出された後のデータベースの予想される状態を表します。これについて、簡単に説明します。ここでは、テスト ケース データを使用して、メインの処理ループを制御しています。
foreach (string testCase in testCaseData)
{
  // parse inputs, expected state
  // call ResetDatabase()
  // call usp_DeleteMovie
  // call GetDatabaseState()
  // determine pass/fail
} 
メインの処理ループの内部で、String.Split メソッドを使用して、テスト ケース データ文字列を個々の文字列変数に解析します。
Console.WriteLine("\n====================================\n");
string[] data = testCase.Split(':');
string caseID = data[0];
string movieID = data[1];
string expectedState = data[2];
Console.WriteLine("Test case ID = " + caseID);
Console.WriteLine("\nID of movie to delete = " + movieID);
ここで、テスト対象のストアド プロシージャを呼び出す前に、データベースを既知の状態に設定する必要があります。これは、SQL ストアド プロシージャ (および一般にデータベース) をテストする場合の基本的な原則です。多くのストアド プロシージャは、1 つまたは複数のテーブルのデータを挿入、削除、または更新することにより、関連付けられているデータベースの状態を変更します。ストアド プロシージャが適切に動作することを確認するには、テスト対象のデータベースの初期状態を調べて、ストアド プロシージャの実行後にデータベースが予想された状態になったかどうかを検証できるようにしておく必要があります。データベースを既知の状態に設定する処理は、従来のテクノロジと比較したときに LINQ が真価を発揮する処理です。
Console.WriteLine("Setting up test bed database");
ResetDatabase(connString);
Main メソッドを比較的クリーンに保つために、コードをインラインに配置する代わりに、ResetDatabase というヘルパー メソッドを作成して、ここでテスト ベッド データベースを設定しています。
static void ResetDatabase(string connString)
{
  // delete all existing data in TblMain and TblPrices
  // populate TblMain and TblPrices with "rich" test data
}
次に、接続文字列を ResetDatabase メソッドに渡します。最初に、次のようにテーブル内の既存のデータをすべて削除します。
DbMovies dbLocal = new DbMovies(connString);
var allRowsInTblMain = from t in dbLocal.TblMain select t;
var allRowsInTblPrices = from t in dbLocal.TblPrices select t;
dbLocal.TblMain.DeleteAllOnSubmit(allRowsInTblMain);
dbLocal.TblPrices.DeleteAllOnSubmit(allRowsInTblPrices);
dbLocal.SubmitChanges();
DbMovies オブジェクトをインスタンス化します。次に、LINQ コードを使用して、両方のテーブル内のすべての既存のデータを LINQ to SQL エンティティ コレクションとして取得します。LINQ の経験がない読者であれば、var キーワードを使用した暗黙のデータ型宣言メカニズムを見たことがないかもしれません。var は、"ここで割り当てコードに基づいて正確なデータ型を Visual Studio に決定させる" という意味です。言い換えると、次のような暗黙の型指定を使用する方法に対し、
var allRowsInTblMain = from t in dbLocal.TblMain select t;
次の方法も同様に有効です。
System.Linq.IQueryable<TblMain> allRowsInTblMain =
  from t in dbLocal.TblMain select t;
私は、LINQ に関して暗黙のデータ型指定を特に気に入っています。ResetDatabase コードを作成しているときにある問題に遭遇したのですが、この問題について触れておきます。最初、私は、ResetDatabase ヘルパー メソッドにローカル オブジェクトを作成するのではなく、1 つの静的なグローバル DbMovies オブジェクトをテスト ハーネスで使用しようとしました。しかし、その方法では、"既に使用されているキーを持つエンティティは追加できません" という例外が発生しました。これは、LINQ to SQL ではパフォーマンス上の理由からたくさんの情報がキャッシュされることを示しています。ストアド プロシージャを呼び出すと、LINQ to SQL は、関連するエンティティに関する情報を保持します。このとき、これらのエンティティに関連するデータを操作しようとすると、このエラーが発生します。簡単な対策は、LINQ to SQL を使用するときにグローバル オブジェクトの使用を避けることです。
エンティティ コレクションを取得したら、各テーブルに関連付けられている DeleteOnSubmit メソッドに渡します。この操作は、tblMain および tblPrices 内のすべての行に削除用のマークを付けるものですが、実際の削除操作は行いません。削除操作を実行するために、SubmitChanges ステートメントを発行します。その結果、削除要求を含むすべての未処理の変更が処理され、DbMovies オブジェクトによって表される実際の dbMovies データベースに反映されます。
これで、リッチなデータをテーブルに設定する準備が整いました。バックエンド データベースを持つアプリケーションを作成する場合、一般的には、開発プロセスにおいて使用する目的で通常のデータをデータベースに格納します。ただし、ストアド プロシージャを徹底的にテストするには、潜在的な問題を発見するために特別に用意されたデータが必要になります。そこで私は、いくつかのテスト ベッド データをハードコードしました。このやり方は、テスト用という点では特に優れた方法ではありませんが、アイデアを明瞭に保つことができます。
string[] data = new string[] {
  "001:Active Assembly:101:11.11",
  "002:Bug Bashing Bit:102:22.22",
  "003:Computing in C#:103:33.33",
  "004:Data is Digital:104:22.22"
};
現実的なシナリオでは、Null 値、重複する値、不正な値、境界値などを含めてさらに多くのデータを追加することになるでしょう。このリッチなテスト ベッド データを外部データ ストアから読み取る方法もありますが、実際のテスト ケース データを使用する場合に比べて、この設定データに関してそうする合理性はあまりありません。次に、図 5 に示すように、テスト ベッド データを解析し、挿入要求を設定します。
foreach (string entry in data)
{
  string[] tokens = entry.Split(':');
  TblMain tm = new TblMain();
  tm.MovID = tokens[0];
  tm.MovTitle = tokens[1];
  tm.MovRunTime = int.Parse(tokens[2]);
  TblPrices tp = new TblPrices();
  tp.MovID = tokens[0];
  tp.MovPrice = decimal.Parse(tokens[3]);
  dbLocal.TblMain.InsertOnSubmit(tm);
  dbLocal.TblPrices.InsertOnSubmit(tp);
}
dbLocal.SubmitChanges();  

TblMain オブジェクトと TblPrices オブジェクトをインスタンス化します。これらのクラスは、sqlmetal.exe ツールによって生成されたコードに定義されています。LINQ は .NET Framework の単なる拡張機能なので、IntelliSense® コード補完機能や設計時の構文チェック機能などの、.NET 環境に関連する優れた機能をすべて利用できます。ここで、テスト ベッド データベースにリッチなデータを設定するときに回避する必要がある、テスト設計上の落とし穴について触れておきます。現実的なシナリオでは、テスト対象のデータベースは、データを挿入するストアド プロシージャを含みます。テスト ベッドにデータを設定するためにこのようなプロシージャを使用しないでください。テスト対象システムの一部を使用してテスト対象システムを効率的に使用します。ここで、テスト ハーネスのメインの処理ループに戻ります。データベースが既知の状態に設定されたら、テスト対象のストアド プロシージャを呼び出す準備が整います。ここでも、LINQ to SQL を使用することでこの操作が大幅に簡略化されます。
Console.WriteLine("Calling usp_DeleteMovie");
db.Usp_DeleteMovie(movieID);
Usp_DeleteMovie C# メソッドは、基底の usp_DeleteMovie ストアド プロシージャの使いやすい代用メソッドです。私を信じてください。これは、多くのパラメータがあるときは特に、ADO.NET を使用してストアド プロシージャを呼び出すよりもはるかに簡単です。この段階で、テスト対象のストアド プロシージャによって関連付けられているデータベースが適切に変更されたかどうかを判定する準備が整いました。たとえばデータベース状態を既知の構成にリセットするなど、データベースの状態を判定する操作は、ストアド プロシージャをテストする場合のもう 1 つの基本的な作業です。
Console.WriteLine("Checking resulting state of test bed database"); 
string actualState = GetDatabaseState(connString);
Console.WriteLine("Expected state = " + expectedState);
Console.WriteLine("Actual state   = " + actualState);
ここでは、予測されるデータベース状態と実際のデータベース状態を取得するために、GetDatabaseState というもう 1 つのヘルパー メソッドを使用します。
static string GetDatabaseState(string connString)
{
  // combine data from tblMain and tblPrices into an aggregate string
  // convert aggregate to an MD5 byte array
  // return string image of byte array
}
最初に、LINQ を使用して、テーブル内のすべてのデータを取得します。
DbMovies db = new DbMovies(connString);
var allRowsInTblMain = from t in db.TblMain select t;
var allRowsInTblPrices = from t in db.TblPrices select t;
次に、それぞれの列値の文字列表現を連結して集計文字列を作成します。
string s = "";
foreach (var record in allRowsInTblMain)
  s += record.MovID + record.MovTitle + record.MovRunTime;
foreach (var record in allRowsInTblPrices)
  s += record.MovID + record.MovPrice;
このとき、この集計文字列を実際の状態として使用し、この文字列を予測される文字列と比較することができます。ただし、テスト ベッド データベース内の値の文字列表現は非常に大きくなる可能性があります。また、一部の SQL データ型 (たとえばバイナリ型) は、文字列表現に適切にマップされません。多くの状況においてより良いアプローチは、データベース内の値のハッシュを使用する方法です。
MD5CryptoServiceProvider md5 =
  new MD5CryptoServiceProvider();
byte[] ba = md5.ComputeHash(Encoding.Unicode.GetBytes(s));
string cryptoHash = BitConverter.ToString(ba);
return cryptoHash;
次に、集計文字列の MD5 (メッセージ ダイジェスト バージョン 5) の 16 バイト値を計算します。このとき、SHA-1 (セキュア ハッシュ アルゴリズム バージョン 1) などの別のハッシュ アルゴリズムも簡単に使用できます。MD5 のような暗号化ハッシュは、任意のサイズのバイト配列を受け取り、常にサイズが固定されている指紋のような識別値を生成します。ここでは読みやすくするために、静的 System.BitConverter クラスを使用して、結果のバイト配列を文字列に変換しています。この段階で、テスト対象のストアド プロシージャによって dbMovies の状態が予想どおりに変更されたかどうかを判定できます。
if (expectedState == actualState)
  Console.WriteLine("\nTest case result = Pass");
else
  Console.WriteLine("\nTest case result = **FAIL**");
ここでは、テスト結果をシェルに表示するだけにしています。運用環境では、結果を外部ストレージに保存することもできます。ここで、テスト ケース データには予測されるデータベース状態 (たとえば 85-A0-D9-28-7B-AA-8D-5F-4C-43-B5-B6-A5-99-F4-63) が格納されていたことを思い出してください。これらの予測値はどこから来ているのでしょうか。予測される状態を調べるのは、ストアド プロシージャをテストする場合に最も時間のかかる作業です。ストアド プロシージャが呼び出されて初期状態が与えられた後のテスト ベッド データベースの予測される状態を手動で調べ、次にユーティリティ プログラムを使用して、予測される状態の値のすべてまたは一部の MD5 (または類似の) 暗号化ハッシュを計算する必要があります。
予測される状態を調べるために使用される一般的なアプローチのうち、誤ったアプローチがあります。予測される値を与えずにテスト ハーネスを実行し、データベースの結果の状態のハッシュ値を取得するやり方です。この方法では、単にテスト対象のストアド プロシージャが同じ (場合によっては誤った) 状態を生成することを確認するだけのテスト ケース データを作成することになります。

ハーネスを拡張する
では、SQL 行セットを返すのみで、関連付けられているデータベース内のデータには作用しない、テスト対象のストアド プロシージャを見ていきましょう。usp_GetMoveDataByPrice は、映画料金を受け取り、指定された料金のすべての映画について、映画 ID、タイトル、上映時間、および料金データを返します。usp_GetMovieDataByPrice を呼び出すには、次のようにコードを記述します。
DbMovies db = new DbMovies(connString); 
decimal moviePrice = (decimal)22.22;
var results = dbc.Usp_GetMovieDataByPrice(moviePrice);
非常に簡潔です。ここでは、オブジェクトの結果に対して暗黙のデータ型を使用していますが、次のように結果に対して明示的なデータ型を使用することもできます。
System.Data.Linq.ISingleResult<Usp_GetMovieDataByPriceResult> results =
  db.Usp_GetMovieDataByPrice(moviePrice);
次に、オブジェクトの結果に返された行セットの正確さを検証するために、返された行セットの列値から構成される集計文字列を最初に取得します。
string s = "";
foreach (var r in results)
  s += r.MovID + r.MovTitle + r.MovRunTime + r.MovPrice;
この集計文字列は実際の戻り値として使用できます。また、前のセクションで説明したように、文字列のハッシュを計算することもできます。usp_GetMovieDataByPrice は tblMain および tblPrice のデータを変更しないので dbMovies の状態に明示的に作用しないことに注意してください。これは、テスト ハーネスにおいて、必要に応じて ResetDatabase ヘルパー メソッドの呼び出しを省略できることを意味します。
一般に、スカラ値を返すストアド プロシージャのテストは、予測される結果が 1 つなので、最も簡単なテスト シナリオです。例として、usp_GetMoviePrice を見てみましょう。このストアド プロシージャは次のように定義されています。
create procedure usp_GetMoviePrice
  @movID char(3),
  @price money out
as
  declare @ct int
  select @price = movPrice from tblPrices where movID = @movID
  select @ct = count(*) from tblPrices where movID = @movID
  return @ct
このストアド プロシージャは、事実上、2 つのスカラ値を返します。指定された ID を持つ映画の料金は out パラメータに格納され、指定された ID を持つ映画の数は明示的な戻り値となります。LINQ to SQL を使用して usp_GetMoviePrice を呼び出すには、次のようにコードを記述します。
DbMovies db = new DbMovies(connString); 
string movieID = "002";
decimal? actualPrice = null;
int actualCount = dbc.Usp_GetMoviePrice(movieID, ref actualPrice);
decimal キーワードの後に書かれている疑問符に、馴染みのない読者もいることでしょう。これは、Null 許容型の例です。すべての SQL データ値に Null を設定できるので、sqlmetal.exe および O/R デザイナ ツールによって生成されるラッピング コードは、ほとんどのデータ メンバを Null 許容型として生成します。図 6 は、ADO.NET を使用して uspGetMoviePrice を呼び出す場合の一般的なコードです。これで、LINQ アプローチの方がはるかに単純であることに納得してもらえることでしょう。

より高速、よりコンパクト、より優れたデバッグ
LINQ および LINQ to SQL に関する私の最初の体験に基づいた計算では、LINQ を使用した場合、ストアド プロシージャのテストの自動化を作成するための時間と労力を少なくとも 50% は削減できると推定できます。LINQ は .NET Framework の一部なので、任意の .NET Framework 準拠の言語 (たとえば C#) を直接使用できるというメリットがあります。引用符とコンマの場所に誤りがないことを願いながら、次のようなコードを記述する必要はありません。
SqlCommand sc = new SqlCommand("INSERT INTO tblFoo VALUES('" + 
someValue + "', '" + otherValue + "')"; 
LINQ を使用した場合、通常の C# スタイルでコードを記述でき、構文エラーがあれば即座に報告されます。
LINQ to SQL は、本質的に優れた設計の下位レベル コード (たとえば SqlDataReader などのクラス) のラッパーなので、テスト コードはよりサイズが小さくクリーンになり、作成、変更、管理が容易になります。LINQ は .NET 環境の一部であるため、Visual Studio のデバッガを使用して、テスト ハーネス内のエラーを発見できます。これは、LINQ 以前のアプローチでは、多くのシナリオにおいて面倒であったり不可能であったりしたことです。おそらく最も重要なのは、LINQ は使って満足できることです。
このコラムを研究している間、私自身、実験プロセスをおおいに楽しみました。LINQ は、開発者またはテスト担当者としての作業を簡単にするので、研究活動を助長します。このような特徴を持つ LINQ を使用することで、より徹底的なテストが可能になり、その結果、より優れたソフトウェア製品が生まれることになります。

ご意見やご質問は、James まで英語でお送りください。 testrun@microsoft.com.


Dr. James McCaffrey は Volt Information Sciences, Inc. に勤務し、ワシントン州レドモンドにあるマイクロソフト本社で働くソフトウェア エンジニアの技術トレーニングを管理しています。これまでに、Internet Explorer、MSN サーチなどの複数のマイクロソフト製品にも携わってきました。また、『.NET Test Automation Recipes』の著者でもあります。James の連絡先は jmccaffrey@volt.com または v-jammc@microsoft.com (英語) です。

Page view tracker