編寫基於Property-based的單元測試

編寫基於Property-based的單元測試

做爲一個開發者,你可能認爲你的職責就是編寫代碼從而完成需求。我不敢苟同,開發者的工做是經過軟件來解決現實需求,編寫代碼只是軟件開發的其中一個方面,編寫可靠的軟件和產出有價值的代碼更加劇要。而TDD則是前輩經過經驗總結出的一套切實可行的軟件開發實踐,TDD旨在幫助開發者編寫高質量的代碼。
TDD的過程能夠總結爲如下幾個步驟:git

  1. 先添加一個測試用例
  2. 執行測試,查看這個測試的失敗結果
  3. 對代碼作少許修改
  4. 再次執行測試,查看測試結果
  5. 對代碼進行重構,執行測試

單元測試的侷限性

設想你要編寫一個加法功能,接受兩個數字,返回這兩個數字的和。讓咱們來按照TDD的流程走一遍:
1.添加一個測試用例github

[Fact]
public void Given3And1ShouldReturn4()
{
    var result = Add(3, 1);

    result.Should().Be(4);
}

2.執行代碼,發現測試並不能經過,由於咱們尚未實現add方法
3.對代碼作少許修改,讓測試經過dom

public int Add(int a, int b)
{
    if(a==3 && b ==1)
    {
        return 4;
    }

    return 0;
}

4.繼續編寫測試單元測試

[Fact]
public void Given1And2ShouldReturn3()
{
    var result = Add(1, 2);

    result.Should().Be(3);
}

5.修改代碼讓測試經過測試

public int Add(int a, int b)
{
    if(a==3 && b ==1)
    {
        return 4;
    }

    if (a == 1 && b == 2)
    {
        return 3;
    }

    return 0;
}

至此爲止,你一直在遵照TDD的步驟,測試所有變成了綠色,可是你始終沒有獲得正確的Add實現。ui

哪裏出了問題?你也許會以爲,我們實現的Add方法有問題,咱們故意犯了一些顯而易見的錯誤從而給TDD挑毛病。可是我任然能夠反駁,他之因此看起來是顯而易見的錯誤是由於對兩個數字求和這樣的需求是每一個人都明白的道理,因此你才以爲顯而易見,試想這是一個正式的場景,你也許真的就編寫了這樣的代碼從而讓兩個測試用例都能剛好經過。code

若是說咱們並非故意編寫了這樣的代碼,那麼單元測試和TDD這種實踐自己可能就有一些瑕疵。對象

換個角度來講,咱們之因此沒有編寫出完整的業務邏輯,是由於單元測試是用例驅動的,而有限的測試用例漏掉了不少可能性。
若是咱們對a和b分別取100個隨機值,Add方法都可以經過,那麼咱們幾乎很難編寫出上面的Add實現。ip

[Fact]
public void WhenAddTwoNumberShouldGetSum()
{
    for (int i = 0; i < 100; i++)
    {
        var a = GetRandomNumber();
        var b = GetRandomNumber();

        var result = Add(a, b);

        result.Should().Be(a + b);
    }
}

要想保證這樣的測試經過,你只能編寫出正確的Add實現:開發

public int Add(int a, int b)
{
    return a + b;
}

這個測試看起來不錯,經過產生大量隨機的輸入來驅動代碼實現,可是這個代碼存在一個致命的問題,測試代碼和被測試代碼使用了相同的業務邏輯。

//咱們指望的數字是a + b
result.Should().Be(a + b);

//而被測對象也是a + b
public int Add(int a, int b)
{
    return a + b;
}

若是a + b這個邏輯自己就有問題,可是由於你在測試代碼裏重複了這一有問題的邏輯,實際上你的測試並無發現任何問題。

Property-based測試

若是你不在測試代碼裏重複a + b這個邏輯,你如何經過這100個隨機輸入來斷言測試的準確性?什麼樣的斷言能被用在這100個隨機輸入的測試用例中?
答案是斷言Add這一能力的屬性,某種可以適用於全部測試用例的屬性。
舉個例子:a + b = b + a

[Fact]
public void A_Add_B_Should_EqualTo_B_Add_A()
{
    for (int i = 0; i < 100; i++)
    {
        var a = GetRandomNumber();
        var b = GetRandomNumber();

        var result1 = Add(a, b);
        var result2 = Add(b, a);

        result1.Should().Be(result2);
    }

}

這一特性正好是加法交換律,若是隻是測試交換律仍是不可以保證Add方法的準確性,由於你能夠把Add方法實現爲a * b。
咱們還能夠斷言起結合律,即a + b + c = a + (b + c)

[Fact]
public void A_Add_B_Add_C_Should_EqualTo_B_Add_C_Add_A()
{
    for (int i = 0; i < 100; i++)
    {
        var a = GetRandomNumber();
        var b = GetRandomNumber();
        var c = GetRandomNumber();

        var result1 = Add(Add(a, b), c);
        var result2 = Add(a, Add(b, c));

        result1.Should().Be(result2);
    }
}

如何實踐Property-based測試

因此什麼是Property-based測試?從上面的分析可以看出Property-based測試實際上提出了兩個策略來保證測試的有效性:

  1. 隨機產生輸入值,保證足夠多的測試用例
  2. 找出並斷言功能具備的廣泛適應性的屬性

在.NET領域,FsCheck用來進行Property-based測試,Property-based是從Haskell移植過來的,幾乎全部的主流語言都有其移植版本。 下篇咱們將介紹如何經過FsCheck來作Property-based測試。

相關文章
相關標籤/搜索