Day 29-Unit Test 应用於使用重构与测试手法优化 C# Code-3 (情境及应用-9)

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");
    }
}

重构的手法百百种,在此示范的也不是说可适用於每种情况,只要在重构前与後的程序码都能符合测试码就算是合格的重构。不过经由重构的程序码,通常其扩充性较佳,较能做後续的维护。


<<:  【第14天】训练模型-Xception

>>:  Day14 用React Component去规划整个画面

C# 入门之字符串处理

在很多情况下,我们需要通过程序去处理一些文本,文本都是以字符串表示的,所以我们今天来看一看,使用 C...

Day11-Kubernetes 那些事 - Ingress 篇(三)

前言 昨天的文章提到 Ingress 其实也可以用来做负载平衡,只是要利用其他种方式来实现,所以接下...

Day16 JavaScript基本教学(一)

JavaScript 语言 (JavaScript Programming Language) Ja...

Day 10:v-for 注定绑个 key

承上篇,谈到 v-for,就要说说它的最佳良伴——key。 v-for 必须绑定代表唯一值的 key...

Day 23: Behavioral patterns - Memento

目的 当系统需要提供「复原功能」、「取消复原功能」、「回复到上一个步骤」等需要将这些资料暂时存放在记...