iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
Software Development

單元測試從入門到進階之路 (以 C# NUnit 3 X NSubstitute 為例)系列 第 29

Day 29-Unit Test 應用於使用重構與測試手法優化 C# Code-3 (情境及應用-9)

  • 分享至 

  • xImage
  •  

Unit Test 應用於使用重構與測試手法優化 C# Code-3-前言

昨天已經把 DemoCalculate 中的 Calculate 方法檢驗參數的部分抽離出來,寫成一隻隻的個別方法,這樣的好處是可以為這些方法單獨寫測試,若而後要修改這些方法時,彼此之間是互不影響,所以找問題相較緊耦合的第一版,第二版的寫法更符合 SOLID 原則。不過若要再進一步探討的話,還可以持續精進程式碼,所以今天就來探討如何更近一步精進。


Unit Test 應用於使用重構與測試手法優化 C# Code-3-測試碼

那一樣,我們先來撰寫更新一版的測試碼,如下:

public class DemoCalculateTests_Ver3
{
    [Test]
    public void NormalCaseTest()
    {
        // 與 Day-28 一樣
    }

    [Test]
    public void FailCaseTest_MathIsNotNumeric()
    {
        // 與 Day-28 一樣
    }

    [Test]
    public void FailCaseTest_MathIsInZeroToTen()
    {
        // 與 Day-28 一樣
    }

    [Test]
    public void AvgCaseTest_Country()
    {
        // Arrange
        string txtID = "Day-29";

        string txtName = "SunShineYen";

        string txtMath = "8";

        string txtLiteral = "4";

        string isCountry = "true";

        DemoCalculate_Ver2 testObject 
              = new DemoCalculate_Ver2(txtID, txtName, txtMath, txtLiteral, isCountry);

        // Act
        string result = testObject.Calculate();

        // Assert
        Assert.AreEqual(result, "Day-29 SunShineYen 6.5");
    }

    [Test]
    public void AvgCaseTest_City()
    {
        // Arrange
        string txtID = "Day-29";

        string txtName = "SunShineYen";

        string txtMath = "8";

        string txtLiteral = "4";

        string isCountry = "false";

        DemoCalculate_Ver2 testObject 
              = new DemoCalculate_Ver2(txtID, txtName, txtMath, txtLiteral, isCountry);

        // Act
        string result = testObject.Calculate();

        // Assert
        Assert.AreEqual(result, "Day-29 SunShineYen 6");
    }
}

這當中我們新增了檢驗平均值是否符合預期,基於平均的回傳是一串字串,所以我們也撰寫相對應的字串做驗證。


Unit Test 應用於使用重構與測試手法優化 C# Code-3-重構後的程式碼(第二版)

這當中是可以修改方法以利擴充,因 Calculate 是公開的方法,因此不論是檢驗參數,或驗證平均都需要經過 Calculate 方法,但倘若全部公開,則一些極機密的方法就會被公開,所以可撰寫針對檢驗參數與驗證平均分別寫獨立的方法,這樣不但可以做測試,也可以再擴充其他方法時使用,如下:

public class DemoCalculate_Ver3
{
    private string txtID;

    private string txtName;

    private string txtMath;

    private string txtLiteral;

    private string isCountry;

    public DemoCalculate_Ver3(string inTxtID, string inTxtName, 
                              string inTxtMath, string inTxtLiteral, string inIsCountry)
    {
        txtID = inTxtID;

        txtName = inTxtName;

        txtMath = inTxtMath;

        txtLiteral = inTxtLiteral;

        isCountry = inIsCountry;
    }

    public string Calculate()
    {
        if (CheckInitPara() != "沒錯誤")
        {
            return CheckInitPara();
        }

        return StudentAvg();
    }

    // 檢驗參數
    public string CheckInitPara()
    {
        if (MathInputValid() != "沒錯誤")
        {
            return MathInputValid();
        }

        if (LiteralInputValid() != "沒錯誤")
        {
            return LiteralInputValid();
        }

        return "沒錯誤";
    }

    private string MathInputValid()
    {
        double result;

        if (!double.TryParse(txtMath, out result))
        {
            return "發生錯誤!數學數值應該是個 numeric!";
        }

        if (((Convert.ToDouble(txtMath) > 10)) || (Convert.ToDouble(txtMath) < 0))
        {
            return "發生錯誤!數學數值應該介於 0 ~ 10 之間!";
        }

        return "沒錯誤";
    }

    private string LiteralInputValid()
    {
        double result;

        if (!double.TryParse(txtLiteral, out result))
        {
            return "發生錯誤!文學數值應該是個 numeric!";
        }

        if (((Convert.ToDouble(txtLiteral) > 10)) || (Convert.ToDouble(txtLiteral) < 0))
        {
            return "發生錯誤!文學數值應該介於 0 ~ 10 之間!";
        }

        return "沒錯誤";
    }

    // 計算數值
    public string StudentAvg()
    {
        double douMath = Convert.ToDouble(txtMath);
        double douLite = Convert.ToDouble(txtLiteral);

        if (Convert.ToBoolean(isCountry))
        {
            return txtID + " " + txtName + " " 
                         + CountryAvg(douMath, douLite).ToString();
        }
        else
        {
            return txtID + " " + txtName + "  " + CityAvg(douMath, douLite).ToString();
        }
    }

    private double CountryAvg(double dbMath, double dbLiteral)
    {
        return (dbMath + dbLiteral + 1) / 2;
    }

    private double CityAvg(double dbMath, double dbLiteral)
    {
        return (dbMath + dbLiteral) / 2;
    }
}

因此,我們的測試碼撰寫就可以變成如下:

public class DemoCalculateTests_Ver3
{
    [Test]
    public void NormalCaseTest()
    {
        // Arrange
        string txtID = "Day-29";

        string txtName = "SunShineYen";

        string txtMath = "8";

        string txtLiteral = "4";

        string isCountry = "true";

        DemoCalculate_Ver3 testObject 
              = new DemoCalculate_Ver3(txtID, txtName, txtMath, txtLiteral, isCountry);

        // Act
        string result = testObject.Calculate();

        // Assert
        Assert.AreEqual(result, "Day-29 SunShineYen 6.5");
    }

    [Test]
    public void FailCaseTest_MathIsNotNumeric()
    {
        // Arrange
        string txtID = "Day-29";

        string txtName = "SunShineYen";

        string txtMath = "Haha";

        string txtLiteral = "4";

        string isCountry = "not Important";

        DemoCalculate_Ver3 testObject 
              = new DemoCalculate_Ver3(txtID, txtName, txtMath, txtLiteral, isCountry);

        // Act
        string result = testObject.CheckInitPara();

        // Assert
        Assert.AreEqual(result, "發生錯誤!數學數值應該是個 numeric!");
    }

    [Test]
    public void FailCaseTest_MathIsInZeroToTen()
    {
        // Arrange
        string txtID = "Day-29";

        string txtName = "SunShineYen";

        string txtMath = "12";

        string txtLiteral = "4";

        string isCountry = "not Important";

        DemoCalculate_Ver3 testObject 
              = new DemoCalculate_Ver3(txtID, txtName, txtMath, txtLiteral, isCountry);

        // Act
        string result = testObject.CheckInitPara();

        // Assert
        Assert.AreEqual(result, "發生錯誤!數學數值應該介於 0 ~ 10 之間!");
    }

    [Test]
    public void AvgCaseTest_Country()
    {
        // Arrange
        string txtID = "Day-29";

        string txtName = "SunShineYen";

        string txtMath = "8";

        string txtLiteral = "4";

        string isCountry = "true";

        DemoCalculate_Ver3 testObject 
              = new DemoCalculate_Ver3(txtID, txtName, txtMath, txtLiteral, isCountry);

        // Act
        string result = testObject.StudentAvg();

        // Assert
        Assert.AreEqual(result, "Day-29 SunShineYen 6.5");
    }

    [Test]
    public void AvgCaseTest_City()
    {
        // Arrange
        string txtID = "Day-29";

        string txtName = "SunShineYen";

        string txtMath = "8";

        string txtLiteral = "4";

        string isCountry = "false";

        DemoCalculate_Ver3 testObject 
              = new DemoCalculate_Ver3(txtID, txtName, txtMath, txtLiteral, isCountry);

        // Act
        string result = testObject.StudentAvg();

        // Assert
        Assert.AreEqual(result, "Day-29 SunShineYen 6");
    }
}

重構的手法百百種,在此示範的也不是說可適用於每種情況,只要在重構前與後的程式碼都能符合測試碼就算是合格的重構。不過經由重構的程式碼,通常其擴充性較佳,較能做後續的維護。


上一篇
Day 28-Unit Test 應用於使用重構與測試手法優化 C# Code-2 (情境及應用-8)
下一篇
Day 30-單元測試(結尾)
系列文
單元測試從入門到進階之路 (以 C# NUnit 3 X NSubstitute 為例)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言