SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

C#で始めるテスト駆動開発入門

TDDでデータベースと付き合う方法

C#で始めるテスト駆動開発入門(7)

ダウンロード CsTdd07.zip (474.5 KB)

とりあえず作ってみたコードの問題点

 この製品コードは、分岐構造が比較的単純で一気に書くことができたとはいうものの、ゴチャゴチャしていて読み辛いですね。また、次のような問題を抱えています。

  • 接続文字列が埋め込まれているため、データベースを切り替えるためには何か所も修正が必要になる。
  • ロジックが、SqlServerCe名前空間に依存している(SqlCeConnectionSqlCeCommand)。ロジックは、できるだけ環境に依存しないコードにしたい。
  • 前項と関連するが、モックやダミーに置き換えようと思うと、SqlServerCe名前空間のクラスをいくつも置き換えなければならない。
  • データベースの内容が変わると、テストが失敗してしまう。

このあとのストーリー

 まずリファクタリングしていきます。接続文字列の部分を分離するところから始めて、モックやダミーへの置き換えを困難にしているSqlServerCe名前空間のクラスを使っている部分を、ビジネスロジックから順番に切り離していきます。

 それから、テスト実行時にデータベースの内容を初期化する例と、トランザクションを利用してテスト中の変更をキャンセルする方法を紹介します。

接続オブジェクトの生成を分離する

 まず、接続オブジェクトを生成するコードを、ロジックから分離します。この部分は、サンプルコードでは1か所だけですが、実際にはデータベースにアクセスするコードすべてに存在するでしょう。データベースにアクセスするメソッドを2つ書いたら、分離して重複を排除しなければならないことに気づくはずです。

テストコード: [改1] 接続オブジェクトの生成だけを行うテスト
[TestCase]
public void GetConnectionTest() {
  string 接続文字列 = @"Data Source=C:\Program Files (x86)\Microsoft SQL Server Compact Edition\v3.5\Samples\Northwind.sdf";

  SqlCeConnection conn = DbConnectionProvider.Connection;
  Assert.AreEqual(接続文字列, conn.ConnectionString);
}
製品コード: [改1] 接続オブジェクトの生成を行うクラス
internal class DbConnectionProvider {
  // 注) AssemblyInfo.cs に [assembly: InternalsVisibleTo("CsTdd07Test")] が必要

  public static SqlCeConnection Connection {
    get {
      return new SqlCeConnection(@"Data Source=C:\Program Files (x86)\Microsoft SQL Server Compact Edition\v3.5\Samples\Northwind.sdf"); 
    }
  }
}

 この実装は、実際にはテストと本番で切り替えできるように作ります。その方法は、次のように何通りも考えられます。

  • config ファイルから読み込む
  • テスト実装を組み込む (連載第6回を参照)
  • DIを使う
      …etc.

 このようにして作ったDbConnectionProviderを使うと、接続文字列を使わないようにロジックをリファクタリングできます。

製品コード: [改1] ロジックから接続文字列を排除
//using (SqlCeConnection conn = new SqlCeConnection(@"Data Source= …
//   ↓
using (SqlCeConnection conn = DbConnectionProvider.Connection) {

SqlServerCeへの依存を分離する

 ロジックのコードでは、SqlServerCe名前空間のオブジェクトとしてSqlCeConnectionSqlCeCommandを使っています。こういう特定の環境に依存したコードは、ロジックから分離しておきたいものです。

SqlCeCommandの生成を分離する

 SqlCeCommandは、ロジックの中では汎用のDbCommandとして扱っています。まずSqlCeCommandの生成部分を分離するところから始めてみましょう。新たにDbCommandHelperクラスを作ることにして、そのインスタンスにSqlCeCommandを作ってもらうことにします。

テストコード: [改2] SqlCeCommandを作ってもらうテスト
string 接続文字列 =
@"Data Source=C:\Program Files (x86)\Microsoft SQL Server Compact Edition\v3.5\Samples\Northwind.sdf";
string cmdText =
@"SELECT  Country, [Company Name], [Customer ID], [Contact Name]
FROM      Customers
WHERE     ([Contact Name] LIKE @p1) OR ([Contact Name] LIKE @p2)";

[TestCase]  //製品コード: CreateCommand()メソッドを実装
public void CreateCommandTest() {
  DbCommand cmd = (new DbCommandHelper()).CreateCommand(cmdText);

  Assert.IsInstanceOf<SqlCeCommand>(cmd);
  Assert.AreEqual(cmdText, cmd.CommandText);

  // Connectionも、自動的に付けてくれるとありがたい
  Assert.AreEqual(接続文字列, cmd.Connection.ConnectionString);
  Assert.AreEqual(ConnectionState.Open, cmd.Connection.State);
}

 ここで、Connectionも自動的に付けてくれるようにしてしまいました。作ってもらったSqlCeCommandは、呼び出し側でDispose()すればよいのですが、自動的に付けてくれたConnectionのDispose()(あるいはClose())は、どこで実行すれば良いでしょう?いろんな方法が考えられますが、DbCommandHelperDispose()するときに実行してやると便利そうです。

 実際には、ここまでを実装したDbCommandHelperクラスを使って製品コードをリファクタリングしてから先に進みますが、ここでは、テストコードだけをまとめてお見せします。

テストコード: [改2] DbCommandHelperIDisposableを実装するテスト
[TestCase]  //製品コード: IDisposable インターフェースと Connection プロパティを実装
public void DisposeTest() {
  DbCommandHelper helper = new DbCommandHelper();

  Assert.IsInstanceOf<IDisposable>(helper);
  var conn = helper.Connection;
  Assert.AreEqual(ConnectionState.Open, conn.State);

  helper.Dispose();

  Assert.AreEqual(ConnectionState.Closed, conn.State);
  Assert.IsNull(helper.Connection);
}

[TestCase]  //製品コード: if (_connection != null) を追加
public void DisposeTest_Connection生成前にDispose() {
  DbCommandHelper helper = new DbCommandHelper();

  helper.Dispose();  //最初はnull参照でREDになる。

  Assert.IsNull(helper.Connection);
}

[TestCase]  //製品コード: CreateCommand()内の cmd.Connection = … を修正
public void DisposeTest_CreateCommandメソッドでも同じ接続オブジェクトを使う() {
  using (DbCommandHelper helper = new DbCommandHelper()) 
  using(DbCommand cmd = helper.CreateCommand(cmdText)){

    Assert.AreSame(helper.Connection, cmd.Connection);
  }
}

次のページ
DbCommandにパラメーターをセットする手続きを分離する

この記事は参考になりましたか?

C#で始めるテスト駆動開発入門連載記事一覧

もっと読む

この記事の著者

biac(ばいあっく)

HONDA R&Dで自動車の設計をやっていた機械屋さんが、技術の進化スピードに魅かれてプログラマーに。以来30年ほど、より良いコードをどうやったら作れるか、模索の人生。わんくま同盟の勉強会(名古屋)で、よく喋ってたりする。2014/10~2019/6 Microsoft MVP (Windows Devel...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/6783 2012/10/30 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー