ObjectMotherを使用したTableModuleのテスト(^o^)

ObjectMotherを使用すれば、テストデータに意味を持たせて開発者間で共有することができる。

今回は型なしDataSetでのTableModuleのテストにObjectMotherを使用してみた。
まずはOrderDetailというTableModuleのテストコードから。本来のObjectMotherパターンであればOrderDetail自体のインスタンスを返すCreation Methodを記述するところだが、今回はDataSetのインスタンスを返した方が他のTableModuleでも利用できるので、このようにした。

using System;
using System.Data;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
using TableModuleSample.Core;

namespace TableModuleSample.Tests
{
    [TestFixture]
    public class OrderDetailTest
    {
        private OrderDetail target_;

        [SetUp]
        public void OrderDetailのインスタンス生成()
        {
            target_ = new OrderDetail(ObjectMother.CreateOrderByTsune());
        }

        [Test]
        public void キーオブジェクトを指定して注文明細を特定できるべき()
        {
            DataRow orderDetail = target_[new OrderDetailKey(1, 2)];

            Assert.That(orderDetail["OrderId"], Is.EqualTo(1));
            Assert.That(orderDetail["ProductId"], Is.EqualTo(2));
            Assert.That(orderDetail["UnitPrice"], Is.EqualTo(11000000m));
            Assert.That(orderDetail["Quantity"], Is.EqualTo(20m));
            Assert.That(orderDetail["Discount"], Is.EqualTo(0.3m));
        }

        ...
    }
}

次にObjectMotherのコード。Staticコンストラクタでデータセットスキーマを作成し、あとはクローンでスキーマをコピーしたデータセットにテストデータを追加して最後にAcceptChangesを呼び出している点がポイント。

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 CreateOrderByTsune()
        {
            DataSet ds = ds_.Clone();
            ds.Tables["Products"].Rows.Add(1, "ハマー");
            ds.Tables["Products"].Rows.Add(2, "ファイアートラック");
            ds.Tables["Products"].Rows.Add(3, "マスタング");
            ds.Tables["Orders"].Rows.Add(1, "中西庸文", new DateTime(2007, 9, 1), new DateTime(2007, 9, 15));
            ds.Tables["Orders"].Rows.Add(2, "中西庸文", new DateTime(2007, 9, 2), new DateTime(2007, 9, 16));
            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;
        }
    }
}

続いてTableModuleのスーパークラスのコード。

using System;
using System.Data;

namespace TableModuleSample.Core
{
    public abstract class TableModule<KeyType>
    {
        protected DataTable table_;

        public TableModule(DataSet ds, string tableName)
        {
            this.table_ = ds.Tables[tableName];
        }

        public DataRow this[KeyType key]
        {
            get
            {
                DataRow[] foundRows = table_.Select(FilterExpressionFrom(key));
                if (foundRows.Length == 0)
                    return null;
                else
                    return foundRows[0];
            }
        }

        public abstract string FilterExpressionFrom(KeyType key);

        public bool IsModified()
        {
            return table_.GetChanges() != null;
        }
    }
}

最後にテスト対象のTableModuleクラスのコード。

using System;
using System.Data;

namespace TableModuleSample.Core
{
    public class OrderDetail : TableModule<OrderDetailKey>
    {
        public OrderDetail(DataSet ds)
            : base(ds, "OrderDetails") 
        { }

        public DataRow this[int orderId, int productId]
        {
            get { return this[new OrderDetailKey(orderId, productId)];  }
        }

        public override string FilterExpressionFrom(OrderDetailKey key)
        {
            return string.Format(
                "OrderId = {0} AND ProductId = {1}", key.OrderId, key.ProductId);
        }

        public void Insert(int orderId, int productId, decimal unitPrice, 
            decimal quantity, decimal discount)
        {
            DataRow orderDetail = table_.NewRow();
            orderDetail["OrderId"] = orderId;
            orderDetail["ProductId"] = productId;
            orderDetail["UnitPrice"] = unitPrice;
            orderDetail["Quantity"] = quantity;
            orderDetail["Discount"] = discount;

            table_.Rows.Add(orderDetail);
        }

        public void Update(int orderId, int productId, decimal unitPrice, 
            decimal quantity, decimal discount)
        {
            DataRow orderDetail = this[orderId, productId];
            orderDetail["UnitPrice"] = unitPrice;
            orderDetail["Quantity"] = quantity;
            orderDetail["Discount"] = discount;
        }

        public void DeleteGroupBy(int orderId)
        {
            DataRow[] foundOrderDetails = table_.Select(
                string.Format("OrderId = {0}", orderId));

            foreach (DataRow foundOrderDatail in foundOrderDetails)
            {
                foundOrderDatail.Delete();
            }
        }
    }

    public class OrderDetailKey
    {
        private int orderId_;
        private int productId_;

        public OrderDetailKey(int orderId, int productId)
        {
            this.orderId_ = orderId;
            this.productId_ = productId;
        }

        public int OrderId
        {
            get { return orderId_; }
        }

        public int ProductId
        {
            get { return productId_; }
        }
    }
}