昨天有提到我們在 Console 執行應用程式時,可以依序輸入 ID、名字、數學數值、文學數值與 isCountry 布林值,而後會執行計算平均值的方法,並給出相對應的平均值又或是提供錯誤訊息;然而,該方法裡面牽涉的商業邏輯,各參數檢驗是否為合理彼此並沒有相對的關係,但以被綁在同一隻方法,這樣對於系統的維護困難(假設隨著功能的擴大,裡面有 20-30 個參數要檢驗),對於測試碼的撰寫也是極其複雜的。因此,我們今天先重構第一版的商業邏輯與測試碼。
昨天 Day-27 流程圖有提到,我們在重構完程式碼後需要跑測試碼來驗證重構後的程式碼是符合預期的結果,那在 Day 19 Roy Osherove 曾提到重構是「在不改變程式碼功能的前提下,修改程式碼的動作。」;因此,我們要先撰寫第一版的測試,以驗證目前的行為在測試下的結果,如下:
public class DemoCalculateTests_Ver1
{
[Test]
public void NormalCaseTest()
{
// Arrange
string txtID = "Day-28";
string txtName = "SunShineYen";
string txtMath = "8";
string txtLiteral = "4";
string isCountry = "true";
DemoCalculate testObject
= new DemoCalculate(txtID, txtName, txtMath, txtLiteral, isCountry);
// Act
string result = testObject.Calculate();
// Assert
Assert.AreEqual(result, "Day-28 SunShineYen 6.5");
}
[Test]
public void FailCaseTest_MathIsNotNumeric()
{
// Arrange
string txtID = "Day-28";
string txtName = "SunShineYen";
string txtMath = "Haha";
string txtLiteral = "4";
string isCountry = "true";
DemoCalculate testObject
= new DemoCalculate(txtID, txtName, txtMath, txtLiteral, isCountry);
// Act
string result = testObject.Calculate();
// Assert
Assert.AreEqual(result, "發生錯誤!數學數值應該是個 numeric!");
}
[Test]
public void FailCaseTest_MathIsInZeroToTen()
{
// Arrange
string txtID = "Day-28";
string txtName = "SunShineYen";
string txtMath = "12";
string txtLiteral = "4";
string isCountry = "true";
DemoCalculate testObject
= new DemoCalculate(txtID, txtName, txtMath, txtLiteral, isCountry);
// Act
string result = testObject.Calculate();
// Assert
Assert.AreEqual(result, "發生錯誤!數學數值應該介於 0 ~ 10 之間!");
}
// ...
}
執行結果:
上面測試碼為示意,測試的情境還可以撰寫數個測試方法如文學數值是否為 Numeric、文學數值是否介於 0-10 等。而接下來要探討的是撰寫重構程式碼,而大致上要做的項目有幾點,如下:
因此,程式碼如下:
namespace HelloRefactor
{
class Program
{
// ...
public class DemoCalculate_Ver2
{
private string txtID;
private string txtName;
private string txtMath;
private string txtLiteral;
private string isCountry;
public DemoCalculate_Ver2(string inTxtID, string inTxtName, string inTxtMath, string inTxtLiteral, string inIsCountry)
{
txtID = inTxtID;
txtName = inTxtName;
txtMath = inTxtMath;
txtLiteral = inTxtLiteral;
isCountry = inIsCountry;
}
public string Calculate()
{
string MathInputValidResult = MathInputValid(txtMath);
string LiteralInputValidResult = LiteralInputValid(txtLiteral);
if (MathInputValidResult != "沒錯誤")
{
return MathInputValidResult;
}
if (LiteralInputValidResult != "沒錯誤")
{
return LiteralInputValidResult;
}
if (Convert.ToBoolean(isCountry))
{
return txtID + " " + txtName + " " + CountryAvg(Convert.ToDouble(txtMath), Convert.ToDouble(txtLiteral)).ToString();
}
else
{
return txtID + " " + txtName + " " + CityAvg(Convert.ToDouble(txtMath), Convert.ToDouble(txtLiteral)).ToString();
}
}
private string MathInputValid(string txtMath)
{
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(string txtLiteral)
{
double result;
if (!double.TryParse(txtLiteral, out result))
{
return "發生錯誤!文學數值應該是個 numeric!";
}
if (((Convert.ToDouble(txtLiteral) > 10)) || (Convert.ToDouble(txtLiteral) < 0))
{
return "發生錯誤!文學數值應該介於 0 ~ 10 之間!";
}
return "沒錯誤";
}
private double CountryAvg(double dbMath, double dbLiteral)
{
return (dbMath + dbLiteral + 1) / 2;
}
private double CityAvg(double dbMath, double dbLiteral)
{
return (dbMath + dbLiteral) / 2;
}
}
}
因此,把一開始所撰寫的測試碼,裡面的 DemoCalculate 改成 DemoCalculate_Ver2,跑出來的測試結果如下:
而這樣抽出檢驗參數的好處是,我們在撰寫新的測試方法時,可以單獨針對每個參數的檢驗方式寫相對應的測試,若參數多達 20-30 種時,出錯也可以找出相對應的問題並修改程式碼,明天會在繼續探討如何更精進程式碼。