VS2005環境でのNAgile - 自動生成したTableAdapterのユニットテスト (-.-)

自動生成したTableAdapterのユニットテストを書くにあたってTableAdapterをテスト用DBに接続させるために接続文字列をテストコード内で置き換えてやる必要がある。しかしTableAdapterのConnectionプロパティがinternalなので少し知恵を絞る必要があった。

まずは自動生成したTableAdapterのパーシャルクラスを作成してそちらに書込み専用のConnectionStringプロパティを書いてみたりしたが、どう考えてもこれはよろしくない。結局テスト対象アセンブリのAssemblyInfoにInternalsVisibleToAttributeを付加する方法に落ち着く。

[assembly: InternalsVisibleTo("Library.Tests")]

自動生成されたコードに対するテストは不要かなと思っていたが、やはり必要。例えばNULL許容のフィールドはウィザードで自動生成しただけでは、Fillを呼び出した際にDBからNULLを取得しちゃうと例外をスローする。フィールドがきちんとnullを返すようにするためにはデータセットデザイナでDataColumnのNullValueプロパティを(Throw exception)から(Null)に変更しなくてはいけない。こんなカスタマイズがうまく動作しているかどうかってのも確認するといいんじゃないかな。

#ただし現状ではStringフィールド以外はNullを設定できない。せっかくのNullable後方互換性のために有効活用できないという罠。

以下は自動生成したTableAdapterに対するユニットテスト

using System;
using System.Text;
using System.Collections.Generic;
using System.Data;
using System.Transactions; 
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Library.DataAccessLayer;
using Library.DataAccessLayer.LibraryDataSetTableAdapters;

namespace Library.Tests.DataAccessLayer
{
 [TestClass]
 public class BooksTableAdapterTest
 {
  private BooksTableAdapter target_; 
  private TransactionScope ts_;
  private LibraryDataSet ds_; 

  [TestInitialize]
  public void CreateTransactionScope()
  {
   ts_ = new TransactionScope();
   ds_ = new LibraryDataSet();
   target_ = new BooksTableAdapter();
   target_.Connection.ConnectionString =
    Properties.Settings.Default.LibraryConnectionString;
   target_.Fill(ds_.Books);
  }

  [TestCleanup]
  public void DisposeTransactionScope()
  {
   ts_.Dispose();
  }
  
  [TestMethod]
  public void Insertができるべき()
  {
   LibraryDataSet.BooksRow book = 
    ds_.Books.AddBooksRow("4-7741-2728-0", "LifeHacks PRESS", 
     "百式の中の人ほか", "なし", 3);
   target_.Update(book);

   LibraryDataSet.BooksDataTable insertedBooks = 
    target_.GetDataByIsbn("4-7741-2728-0");
   Assert.AreEqual<int>(1, insertedBooks.Count);

   LibraryDataSet.BooksRow insertedBook = insertedBooks[0];
   Assert.AreEqual<string>("4-7741-2728-0", insertedBook.Isbn);
   Assert.AreEqual<string>("LifeHacks PRESS", insertedBook.Title);
   Assert.AreEqual<string>("百式の中の人ほか", insertedBook.Author);
   Assert.AreEqual<string>("なし", insertedBook.Translator);
   Assert.AreEqual<int>(3, insertedBook.CategoryId);
  }

  [TestMethod]
  [ExpectedException(typeof(ConstraintException))]
  public void 主キーが設定されたデータテーブルに同じ主キーのデータをInsertすると例外が発生すべき()
  {
   LibraryDataSet.BooksRow book =
    ds_.Books.AddBooksRow("4-7741-2728-0", "LifeHacks PRESS", 
     "百式の中の人ほか", "なし", 3);

   LibraryDataSet.BooksRow sameBook =
    ds_.Books.AddBooksRow("4-7741-2728-0", "LifeHacks PRESS", 
     "百式の中の人ほか", "なし", 3);
  }

  [TestMethod]
  public void Null許容のフィールドにはNullを設定できるべき()
  {
   LibraryDataSet.BooksRow book =
    ds_.Books.AddBooksRow("4-7741-2728-0", "LifeHacks PRESS", 
     "百式の中の人ほか", null, 3);
   target_.Update(book);

   LibraryDataSet.BooksDataTable insertedBooks = 
    target_.GetDataByIsbn("4-7741-2728-0");
   LibraryDataSet.BooksRow insertedBook = insertedBooks[0];
    
   Assert.IsNull(insertedBook.Translator);
  }

  [TestMethod]
  public void Updateができるべき()
  {
   LibraryDataSet.BooksRow book =
    ds_.Books.AddBooksRow("4-7741-2728-0", "LifeHacks PRESS", 
     "百式の中の人ほか", "なし", 3);
   target_.Update(book);

   book.CategoryId = 2;
    target_.Update(book);

   LibraryDataSet.BooksDataTable updatetedBooks = 
    target_.GetDataByIsbn("4-7741-2728-0");
   LibraryDataSet.BooksRow updatedBook = updatetedBooks[0];
    
   Assert.AreEqual<int>(2, updatedBook.CategoryId);
  }

  [TestMethod]
  public void Deleteができるべき()
  {
   LibraryDataSet.BooksRow book =
    ds_.Books.AddBooksRow("4-7741-2728-0", "LifeHacks PRESS", 
     "百式の中の人ほか", "なし", 3);
   target_.Update(book);

   book.Delete();
   target_.Update(book);

   LibraryDataSet.BooksDataTable deletedBooks = target_.GetData();
   Assert.AreEqual<int>(0, deletedBooks.Count);
  }

  [TestMethod]
  [ExpectedException(typeof(DBConcurrencyException))]
  public void すでにDeleteされた行をDeleteしたら同時実行違反が発生すべき()
  {
   LibraryDataSet.BooksRow book =
    ds_.Books.AddBooksRow("4-7741-2728-0", "LifeHacks PRESS", 
     "百式の中の人ほか", "なし", 3);
   target_.Update(book);

   LibraryDataSet.BooksDataTable insertedBooks = 
    target_.GetDataByIsbn("4-7741-2728-0");
   LibraryDataSet.BooksRow insertedBook = insertedBooks[0];

   book.Delete();
   target_.Update(book);

   insertedBook.Delete();
   target_.Update(insertedBook);
  }

  [TestMethod]
  [ExpectedException(typeof(DBConcurrencyException))]
  public void すでにDeleteされた行をUpdateしたら同時実行違反が発生すべき()
  {
   LibraryDataSet.BooksRow book =
    ds_.Books.AddBooksRow("4-7741-2728-0", "LifeHacks PRESS", 
     "百式の中の人ほか", "なし", 3);
   target_.Update(book);

   LibraryDataSet.BooksDataTable insertedBooks = 
    target_.GetDataByIsbn("4-7741-2728-0");
   LibraryDataSet.BooksRow insertedBook = insertedBooks[0];

   book.Delete();
   target_.Update(book);

   insertedBook.CategoryId = 2;
   target_.Update(insertedBook);
  }
 }
}

#これは.NETを始めて1週間足らずのうちのチームのメンバーが社内向けプロジェクトにおいてペアプロで作成したテストコード。よく頑張った。まだテーブル間にリレーションとか設定してないからあとでごっそり書き直すことになるだろうけど(笑)