黃忠成

風雪之閣- i live,so i writing
文章數 - 133, 回應數 - 125, 引用數 - 0


關於我:



黃忠成

  • 資深.NET 技術顧問
  • Run! PC 雜誌專欄作者
  • 程序員雜誌文章作者
  • PC Magazine 雜誌專欄作者
  • MSDN 專欄作者
  • MSDN 特約專屬講師
  • Microsoft .NET專屬講師
  • 台灣微軟特約技術顧問
  • 台灣微軟最有價值專家


  • 批評,指教,鼓勵, 請 寫信給我
    轉載文章請使用連結模式,
    請勿整篇Copy! 謝謝!


    我所提供的教育訓練:

    Windows Forms
    ASP.NET 2.0
    如有課程需要,請與我聯絡!

  • 我的著作:

  • 文章標籤

    全部標籤

    每月文章

    文章分類

    C# 4.0 New Feature : Dynamic Programming And TDD

    C# 4.0 New Feature : Dynamic Programming And TDD

     

    文/黃忠成

     

    當閱讀了dynamic型別有關的C# 4.0白皮書時,我很自然的想到了TDD(Test Diven Development)TDD原本意圖讓設計師在撰寫真正程式碼前撰寫測試碼,這個立意很好,因為大多數的設計師總是在完成程式後再來考慮撰寫測試碼,結果是測試碼永遠跟不上真正的程式碼,被放棄的機率高的嚇人。但TDD的執行流程中存在著許多困難點,例如如何在沒有真實程式碼的情況下撰寫測試碼?又如何在沒有資料庫的情況下,撰寫相關的資料庫測試程式碼?這使得我在講述TDD後學員們總是聽聽就算了,僅有少數會肯真正的遵循TDD模式,而這些少數,多半也是受命於上面的要求而行之。
     Visual Studio 2010中,dynamic戲劇化的解決了TDD的幾個問題,在此就讓我以一個TDD例子來演示此應用。
     首先透過Visual Studio 2010來建立一個Class Library Project,修改Class1.cs的內容如下。

     

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
     
    namespace ClassLibrary2
    {
        public class Calculator
        {
        }
    }
    接著建立Test Project
    圖1
    圖2
    圖3
    移除預設的CalculatorConstructorTest函式,加入以下函式:

     

    [TestMethod()]
    public void TestCalcSum()
    {
        dynamic o = new ClassLibrary2.Calculator();
        int result = o.Sum(15, 15);
        Assert.AreEqual(result, 30);
    }
    dynamic之協助,這段程式碼是可以編譯的,但執行測試會是紅燈。
    圖4
     
    圖5
    圖6
    這很正常,TDD一開始一定是紅燈,接著我們要讓它變綠燈,先修改成下列這樣。

     

    [TestMethod()]
    public void TestCalcSum()
    {
        ClassLibrary2.Calculator o = new ClassLibrary2.Calculator();
        int result = o.Sum(15, 15);
        Assert.AreEqual(result, 30);
     }
    然後於o.SumSum處按右鍵。
    圖7
    選擇Method Stub後按下,接著可於class1.cs中發現Sum函式的定義,於此添加真正的實作。

     

    public int Sum(int val1, int val2)
    {
        return val1 + val2;
    }
    完成後編譯並重新測試,綠燈就會出現了。
    圖8
    這就是TDDVisual Studio 2010IDEdynamic協助下的實踐,在DynamicObject的協助下,也可以輕易的做出TDDMock Object的寫法。
    OK,就讓我們走更遠一些,以Northwind資料庫為例,在TDD的原則下,於撰寫資料庫相關測試碼時,最完美的時間點是在連資料庫都沒有的時候,dynamic可以幫到我們什麼忙呢?先修改Class1.cs如下:

     

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Dynamic;
     
    namespace ClassLibrary2
    {
        public class Calculator
        {
            public int Sum(int val1, int val2)
            {
                return val1 + val2;
            }
        }
     
        public class DynamicDataContext : DynamicObject
        {
            internal Dictionary<dynamic, dynamic> _bags =
                   new Dictionary<dynamic, dynamic>();
     
            public override bool TryGetMember(GetMemberBinder binder, out object result)
            {
                if (_bags.Keys.Contains(binder.Name))
                    result = _bags[binder.Name];
                else
                {
                    TableObject<dynamic> table = new TableObject<dynamic>();
                    table._context = this;
                    _bags.Add(binder.Name, table);
                    result = _bags[binder.Name];
                }
                return true;
            }
     
            public override bool TryInvokeMember(InvokeMemberBinder binder,
                               object[] args, out object result)
            {
                result = null;
                if (binder.Name.Equals("SubmitChanges"))
                    return true;
                return false;
            }
        }
     
        public class PropertyCollectionObject : DynamicObject
        {
            private Dictionary<dynamic, dynamic> _bags =
                     new Dictionary<dynamic, dynamic>();
     
            public override bool TryGetMember(GetMemberBinder binder, out object result)
            {
                if (_bags.Keys.Contains(binder.Name))
                    result = _bags[binder.Name];
                else
                    result = null;
                return result != null;
            }
     
            public override bool TrySetMember(SetMemberBinder binder, object value)
            {
                if (_bags.Keys.Contains(binder.Name))
                    _bags[binder.Name] = value;
                else
                    _bags.Add(binder.Name, value);
                return true;
            }
        }
     
        public class TableObject<T> : DynamicObject, IList<T>
        {
            internal DynamicDataContext _context = null;
            private List<T> items = new List<T>();
     
            public override bool TryInvokeMember(InvokeMemberBinder binder,
                                          object[] args, out object result)
            {
                result = null;
                if (binder.Name.Equals("InsertOnSubmit"))
                    items.Add((T)args[0]);
                else if (binder.Name.Equals("DeleteOnSubmit"))
                    items.Remove((T)args[0]);
                return true;
            }
     
            public int IndexOf(T item)
            {
                return items.IndexOf(item);
            }
     
            public void Insert(int index, T item)
            {
                items.Insert(index, item);
            }
     
            public void RemoveAt(int index)
            {
                items.RemoveAt(index);
            }
     
            public T this[int index]
            {
                get
                {
                    return items[index];
                }
                set
                {
                    items[index] = value;
                }
            }
     
            public void Add(T item)
            {
                items.Add(item);
            }
     
            public void Clear()
            {
                items.Clear();
            }
     
            public bool Contains(T item)
            {
                return items.Contains(item);
            }
     
            public void CopyTo(T[] array, int arrayIndex)
            {
                items.CopyTo(array, arrayIndex);
            }
     
            public int Count
            {
                get
                {
                    return items.Count;
                }
            }
     
            public bool IsReadOnly
            {
                get
                {
                    return false;
                }
            }
     
            public bool Remove(T item)
            {
                return items.Remove(item);
            }
     
            public IEnumerator<T> GetEnumerator()
            {
                return items.GetEnumerator();
            }
     
            System.Collections.IEnumerator
                     System.Collections.IEnumerable.GetEnumerator()
            {
                return items.GetEnumerator();
            }
        }
     
        public class NorthwindDataContext : DynamicDataContext
        {
        }
     
     
        public class Customer : PropertyCollectionObject
        {
        }
    }
    這段程式碼不難,只是DynamicObject的應用罷了,有趣的是我做出了DynamicDataContextTableObject,一個扮演著LINQ To SQLDataContext角色,一個則扮演著LINQ To SQL中的Entity Class角色,只要有這兩個類別,你便可以變出任何一個DataContext及任何一個Entity Class,差別只是命名而已,接著讓我們來看看測試碼怎麼寫?

     

    using System.Linq;
    .........
    [TestMethod]
    public void TestCustomersAdd()
    {           
       dynamic context = new NorthwindDataContext();
       dynamic c = new Customer();
       IEnumerable<dynamic> table = (IEnumerable<dynamic>)context.Customers;
       c.CustomerID = "A9010";
       c.CompanyName = "GIS";
       context.Customers.InsertOnSubmit(c);
       context.SubmitChanges();           
       dynamic result = (from s1 in table where s1.CustomerID == "A9010" select s1).First();
       Assert.AreEqual(result.CustomerID, "A9010");
    }
    猜猜這結果是什麼?綠燈!
    圖9
    接著讓我們玩真的,註解掉Class1中的NorthwindDataContextCustomers兩個類別,然後加入LINQ To SQL Classes,並把Northwind資料庫的Customers拖進去。
    10
    編譯後會得到一個錯誤訊息,提示我們沒有於Test Project中添加System.Data.LinqReference,加入後重新編譯即可,接著重新執行測試。
    11
    再看看資料庫。
    12
    事情還沒完哦,先手動把這筆資料刪掉,接著進行重構(refactoring)

     

    [TestMethod]
    public void TestCustomersAdd()
    {           
        NorthwindDataContext context = new NorthwindDataContext();
        Customer c = new Customer();
         IEnumerable<Customer> table = (IEnumerable<Customer>)context.Customers;
         c.CustomerID = "A9010";
         c.CompanyName = "GIS";
         context.Customers.InsertOnSubmit(c);
         context.SubmitChanges();           
         dynamic result = (from s1 in table where s1.CustomerID == "A9010" select s1).First();
         Assert.AreEqual(result.CustomerID, "A9010");
    }
    歡迎進入TDD With Visual Studio 2010 And C#的世界。
     
    dynamic是惡魔還是天使?
     
       我想我很難告訴讀者,dynamic的出現到底是好事還是壞事,每個語言做出變革時,總有著擁護者及反對者,的確!dynamic的不定型,會讓不了解dynamic用途的設計師寫出難以維護及除錯的程式碼,但也因為不定型,所以能簡化許多工作,熟優熟劣,端看程式設計師有多了解dynamic設計真正的用途,並謹慎的將其用在刀口上,而不是盲目的使用它。
       最後就讓我下一個定義吧,這只是我初步的結論,遵循與否就看個人了:
    一、將dynamic用在與COMJavaScriptIronPythonIronRuby的互通及整合上。
    二、將dynamic用在TDD的過程中,但實體碼成形後,立即進行refactoring,並移除dynamic
    三、將dynamic用在Framework的撰寫上,以增加Framework的延展性為設計目的。
     
    除了以上三點外,我想我不會把dynamic用在其它的地方,尤其是一般的應用程式。

     


    DotBlogs Tags: Visual Studio

    posted on 2010/1/23 14:35 | 5 人推薦 我要推薦 | 閱讀數 : 911 | 文章分類 [ .NET Framework Visual Studio ] 訂閱

    Feedback

    # re: C# 4.0 New Feature : Dynamic Programming And TDD 回覆

    拿來用在TDD的方式好特別!

    酷!

    2010/1/23 下午 11:16 | 91

    # re: C# 4.0 New Feature : Dynamic Programming And TDD 回覆

    真的!超讚的啦!著眼點就是不一樣!

    這的確也是一個前期開發時的方式呢!

    也不用再去外掛一個 Mxxx  的library 去做一個虛擬的物件了!

    不過,小弟覺得沒有 OO 基礎的話!還是很多人不會去做 TDD (囧)

     

    2010/1/30 下午 12:37 | franma

    回應

    標題
    姓名
    電子郵件 (將不會被顯示)
    個人網頁
    內容 
      登入後使用進階評論  
    Please add 4 and 8 and type the answer here:

    Powered by: