MOQ 撰寫 Mock 的測試框架

May 4, 2009 at 9:45 AM
MOQ The simplest mocking library for .NET 3.5 and Silverlight with deep C# 3.0 integration 看起來還蠻有趣的,想寫 Mock 的人可以參考一下。

我自己先前也有為了測 Exception 而使用了 lambda expression,覺得 lambda expression 是個全新的寫程式方法,一開始看起來會比較難理解,但有時可以減少很多程式碼...減少程式碼又會讓程式碼較易讀...

標準的測試寫法,在待測函式會丟出預期中的例外時,要使用 [ExpetationException],但只能一個 TestMethod 測一個例外。我寫的這個功能,是為了在同一個 TestMethod 中可以測試多個會丟出例外的目標函式。

程式碼很短,貼出來供參考:
正常的連續測試的寫法會這樣:

[TestMethod]
public void TestThrowExceptions()
{
    try
    {
        foo.ThrowArgumentException();
        Assert.Fail();
    }
    catch (Exception ex)
    {
        if (ex.GetType() != typeof(ArgumentException))
            Assert.Fail();
    }

    try
    {
        foo.ThrowArgumentNullException();
        Assert.Fail();
    }
    catch (ArgumentException ex)
    {
        if (ex.GetType() != typeof(ArgumentNullException))
            Assert.Fail();
    }
}

 

 

非常地長...而且重覆的地方很多...但 try/catch 樣式又沒辦法包在共用的子函式中重覆呼叫,因為裡面有 foo.xxx() 會每次都不一樣...這就令我想到: 匿名函式可以上場了...

以下是可以重覆使用的測試函式:

public static void ThrowException(Type exceptionType, Action action)
{
    if (exceptionType == null)
    {
        throw new ArgumentNullException("exceptionType");
    }

    if (exceptionType != typeof(Exception) && !exceptionType.IsSubclassOf(typeof(Exception)))
    {
        throw new ArgumentException("exceptionType 參數必須為一例外型別。", "exceptionType");
    }

    try
    {
        action();
    }
    catch (Exception actualException)
    {
        if (actualException.GetType() != exceptionType)
        {
            Assert.Fail("預期要丟出例外 {0},但卻丟出例外 {1}。此例外之內容為: {2}", exceptionType.ToString(), actualException.GetType(), actualException.ToString());
        }

        return;
    }

    Assert.Fail("預期要丟出例外 {0},但卻完成執行,沒有例外。", exceptionType.ToString());
}  

 

 

測試的寫法為:
AssertExtension.ThrowException(typeof(ArgumentNullException),() => foo.Bar());

後來看到這個 MOQ 後,發現使用 Generic Method 更簡單,於是改寫如下:

public static void ThrowException<T>(Action action) where T : Exception
{
    try
    {
        action();
    }
    catch (Exception actualException)
    {
        if (actualException.GetType() != typeof(T))
        {
            Assert.Fail("預期要丟出例外 {0},但卻丟出例外 {1}。此例外之內容為: {2}", typeof(T).ToString(), actualException.GetType(), actualException.ToString());
        }

        return;
    }

    Assert.Fail("預期要丟出例外 {0},但卻成功完成執行,沒有例外。", typeof(T).ToString());

 

 

測試的寫法為:
AssertExtension.ThrowException<ArgumentNullException>(() => foo.Bar());

因為在原來的寫法,丟入 exceptionType 有可能不是 Exception 類別,因此要在執行時期做檢查。新的寫法,若是不正確的類別,會無法通過編譯...因此就不必做執行期檢查。而且測試的寫法也比較精簡。

所以連續測試丟出例外的 TestMethod 可能是這樣:
[TestMethod]
public void TestFooToInt()
{
    AssertExtension.ThrowException<ArgumentNullException>(() => foo.ToInt(null));
    AssertExtension.ThrowException<ArgumentOutOfRangeException>(() => foo.ToInt("a"));
    Assert.AreEqual(1, foo.ToInt("1"));
    Assert.AreEqual(-1, foo.ToInt("-1"));
}


這樣就可以在同一個 TestMethod 用很簡單的寫法測會丟出例外的函式了。

一開始寫 MOQ,到後面全部是寫我自己的東西,跟 MOQ 沒什麼關係... :-P