ObjectMotherとSqlBulkCopyによるテストデータの一元管理(^o^)
ObjectMotherとSqlBulkCopyクラスをうまく利用すれば、テストデータの一元管理が可能になる。
DBアクセスを伴うテストに使用するテストデータもテストデータファイルを読み込むのではなく、SetUpでObjectMotherでテストデータを設定したDataTableをSqlBulkCopyクラスに食わせてDBに挿入し、TearDownでDBからテストデータを削除するようなDataAccessTestBaseクラスを用意する。
まずはObjectMotherのコード
using System; using System.Data; namespace TableModuleSample.Tests { public class ObjectMother { private static readonly DataSet ds_; static ObjectMother() { ds_ = new DataSet(); SetSchema(); } private static void SetSchema() { DataTable products = new DataTable("Products"); products.Columns.Add("ProductId", typeof(int)); products.Columns.Add("ProductName", typeof(string)); ds_.Tables.Add(products); DataTable orders = new DataTable("Orders"); orders.Columns.Add("OrderId", typeof(int)); orders.Columns.Add("CustomerName", typeof(string)); orders.Columns.Add("OrderDate", typeof(DateTime)); orders.Columns.Add("ShippedDate", typeof(DateTime)); ds_.Tables.Add(orders); DataTable orderDetails = new DataTable("OrderDetails"); orderDetails.Columns.Add("OrderId", typeof(int)); orderDetails.Columns.Add("ProductId", typeof(int)); orderDetails.Columns.Add("UnitPrice", typeof(decimal)); orderDetails.Columns.Add("Quantity", typeof(decimal)); orderDetails.Columns.Add("Discount", typeof(decimal)); ds_.Tables.Add(orderDetails); } ... public static DataSet CreateOrderDetails() { DataSet ds = new DataSet(); DataTable orderDetails = ds_.Tables["OrderDetails"].Clone(); ds.Tables.Add(orderDetails); ds.Tables["OrderDetails"].Rows.Add(1, 1, 10000000m, 10m, 0.2m); ds.Tables["OrderDetails"].Rows.Add(1, 2, 11000000m, 20m, 0.3m); ds.Tables["OrderDetails"].Rows.Add(1, 3, 11500000m, 25m, 0.1m); ds.Tables["OrderDetails"].Rows.Add(2, 1, 10000000m, 10m, 0.2m); ds.Tables["OrderDetails"].Rows.Add(2, 2, 11000000m, 20m, 0.3m); ds.Tables["OrderDetails"].Rows.Add(2, 3, 11500000m, 25m, 0.1m); ds.AcceptChanges(); return ds; } ... } }
次に、DBアクセスを伴うテストのスーパークラスとなるDataAccessTestBaseクラスのコード
using System; using System.Configuration; using System.Data; using System.Data.SqlClient; using NUnit.Framework; namespace TableModuleSample.Tests.Gateway { public abstract class DataAccessTestBase<TargetType> { protected TargetType target_; [SetUp] public void データアクセステストの前処理() { target_ = CreateTarget(); InsertTestData(); } public abstract TargetType CreateTarget(); private void InsertTestData() { using (SqlConnection connection = new SqlConnection( ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString)) { connection.Open(); using (SqlTransaction transaction = connection.BeginTransaction()) { SqlBulkCopy copier = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction); foreach (DataTable table in Data.Tables) { copier.DestinationTableName = table.TableName; copier.WriteToServer(table); } transaction.Commit(); } } } public abstract DataSet Data { get; } [TearDown] public void データアクセステストの後処理() { ClearTestData(); } private void ClearTestData() { using (SqlConnection connection = new SqlConnection( ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString)) { connection.Open(); using (SqlTransaction transaction = connection.BeginTransaction()) { foreach (DataTable table in Data.Tables) { SqlCommand command = connection.CreateCommand(); command.Transaction = transaction; command.CommandType = CommandType.Text; command.CommandText = string.Format("TRUNCATE TABLE {0}", table.TableName); command.ExecuteNonQuery(); } transaction.Commit(); } } } } }
最後にGatewayのテストクラス。スーパークラスであるDataAccessTestBaseクラスの抽象メソッドCreateTargetをオーバーライドしてターゲットクラスを指定し、DataプロパティでDBに流し込む対象のDataSetをObjectMotherから取得している。
using System; using System.Data; using NUnit.Framework; using NUnit.Framework.SyntaxHelpers; using TableModuleSample.Core.Gateway; namespace TableModuleSample.Tests.Gateway { [TestFixture] public class OrderDetailGatewayTest : DataAccessTestBase<OrderDetailGateway> { public override OrderDetailGateway CreateTarget() { return new OrderDetailGateway(); } public override DataSet Data { get { return ObjectMother.CreateOrderDetails(); } } [Test, Category("DBアクセス")] public void すべての注文詳細データをロードできるべき() { target_.LoadAll(); Assert.That(target_.Table.Rows, Has.Count(6)); Assert.That(target_.Table.Rows, Is.Unique); } [Test, Category("DBアクセス")] public void プライマリキーを指定して注文詳細データを抽出できるべき() { target_.LoadByPrimaryKey(1, 2); Assert.That(target_.Table.Rows, Has.Count(1)); Assert.That(target_.Table.Rows[0]["OrderId"], Is.EqualTo(1)); Assert.That(target_.Table.Rows[0]["ProductId"], Is.EqualTo(2)); Assert.That(target_.Table.Rows[0]["UnitPrice"], Is.EqualTo(11000000m)); Assert.That(target_.Table.Rows[0]["Quantity"], Is.EqualTo(20m)); Assert.That(target_.Table.Rows[0]["Discount"], Is.EqualTo(0.3m)); } [Test, Category("DBアクセス")] public void 注文IDを指定してデータを抽出できるべき() { target_.LoadByOrderId(1); Assert.That(target_.Table.Rows, Has.Count(3)); foreach (DataRow orderDetail in target_.Table.Rows) { Assert.That(orderDetail["OrderId"], Is.EqualTo(1)); } } [Test, Category("DBアクセス")] public void 既存のDataSetHolderを利用できるべき() { DataSetHolder holder = new DataSetHolder(); target_ = new OrderDetailGateway(holder); target_.LoadByPrimaryKey(1, 2); DataRow orderDetail = target_.Table.Rows[0]; orderDetail.Delete(); holder.Update(); OrderDetailGateway gateway = new OrderDetailGateway(); gateway.LoadByPrimaryKey(1, 2); Assert.That(gateway.Table.Rows, Has.Count(0)); } } }