程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 《重構》C#版實現(一)構建參考結果的測試用例

《重構》C#版實現(一)構建參考結果的測試用例

編輯:C#入門知識

首先,列出的是第一個“電影租賃”案例的C#版初始代碼:
[csharp]
using System; 
using System.Collections.Generic; 
 
namespace CH01_MovieRentalHouse 

    public class Movie 
    { 
        public const int CHILDRENS = 2; 
        public const int REGULAR = 0; 
        public const int NEW_RELEASE = 1; 
 
        public string Title { get; private set; } 
        public int PriceCode { get; private set; } 
         
        public Movie(string title, int priceCode) 
        { 
            Title = title; 
            PriceCode = priceCode; 
        } 
    } 
 
    public class Rental 
    { 
        public Movie Movie { get; private set; } 
        public int DaysRented { get; private set; } 
 
        public Rental(Movie rented, int days) 
        { 
            Movie = rented; 
            DaysRented = days; 
        } 
    } 
 
    public class Customer 
    { 
        public string Name { get; private set; } 
 
        private List<Rental> Rentals = new List<Rental>(); 
 
        public Customer(string name) 
        { 
            Name = name; 
        } 
 
        public void Add(Rental rental) 
        { 
            Rentals.Add(rental); 
        } 
 
        public string Statement() 
        { 
            double totalAmount = 0; 
            int frequentRenterPoints = 0; 
            string result = "Rental Record for " + Name + "\n"; 
            foreach (Rental rental in Rentals) 
            { 
                double thisAmount = 0; 
                 
                // determine amounts for each line 
                switch (rental.Movie.PriceCode) 
                { 
                    case Movie.REGULAR: 
                        thisAmount += 2; 
                        if (rental.DaysRented > 2) 
                            thisAmount += (rental.DaysRented - 2) * 1.5; 
                        break; 
                    case Movie.NEW_RELEASE: 
                        thisAmount += rental.DaysRented * 3; 
                        break; 
                    case Movie.CHILDRENS: 
                        thisAmount += 1.5; 
                        if (rental.DaysRented > 3) 
                            thisAmount += (rental.DaysRented - 3) * 1.5; 
                        break; 
                } 
 
                // add frequent renter points 
                frequentRenterPoints++; 
                // add bonus for a two day new release rental 
                if (rental.Movie.PriceCode == Movie.NEW_RELEASE && 
                    rental.DaysRented > 1) frequentRenterPoints++; 
                 
                // show figures for this rental 
                result += "\t" + rental.Movie.Title + "\t" + thisAmount.ToString() + "\n"; 
                totalAmount += thisAmount; 
            } 
            // add footer lines 
            result += "Amount owed is " + totalAmount.ToString() + "\n"; 
            result += "You earned " + frequentRenterPoints.ToString() + " frequent renter points"; 
            return result; 
        } 
    } 

  在工作中,經常見到動辄數百,甚至上千行的方法。對於那些程序員們,我實在不知道他們擁有怎樣的技巧,才能工作在復雜如斯的代碼謎團中,同時還能(真的能麼?)保證代碼不出錯。以個人經驗及數據來說,結構良好的代碼中,類方法的長度約有40%為1-5行,50%為5到15行左右,還有10%為15-50行。雖然不能嚴格地說方法的代碼行數越少越好,但簡短且職責更單一的方法確實成為了我衡量代碼的重要標准之一。

  《重構》一書中提到了一個重要概念:“每當我要進行重構的時候,第一個步驟永遠相同:我得為即將修改的代碼建立一組可靠的測試環境”。誠然,沒有測試也能進行重構,但沒有它的保障,大多數重構行為都會以失敗告終。所以,後面所有的重構,都會在C#的單元測試框架基礎上完成,而這也是《重構》中沒有給出的部分(相當重要的部分)。
  首先,必須確定的是初始代碼的正確性,這是重構的基礎之一,如果原始代碼不正確,我們也就無法用它來得到可供參考的結果。那麼,第一步工作不是立刻大刀闊斧地對現有代碼進行重構,而是先使用正確的初始代碼運行幾分測試數據,得到相應的結果。為此,添加如下代碼:
[csharp] 
class Program 

    static void Main(string[] args) 
    { 
        Movie braveHeart = new Movie("BraveHeart", Movie.NEW_RELEASE); 
        Movie godFather = new Movie("GodFather", Movie.REGULAR); 
        Movie wallE = new Movie("Wall-E", Movie.CHILDRENS); 
 
        Rental bh4DayRental = new Rental(braveHeart, 4); 
        Rental bh2DRental = new Rental(braveHeart, 2); 
        Rental we7DRental = new Rental(wallE, 7); 
        Rental gfRental = new Rental(godFather, 5); 
        Rental we3DRental = new Rental(wallE, 3); 
 
        Customer Charles = new Customer("Charles"); 
        Charles.Add(bh4DayRental); 
        Charles.Add(gfRental); 
 
        Customer Jess = new Customer("Jess"); 
        Jess.Add(bh4DayRental); 
        Jess.Add(we7DRental); 
        Jess.Add(bh2DRental); 
 
        Customer Rebecca = new Customer("Rebecca"); 
        Rebecca.Add(we7DRental); 
        Rebecca.Add(we3DRental); 
 
        Customer Lucas = new Customer("Lucas"); 
        Lucas.Add(bh2DRental); 
        Lucas.Add(we7DRental); 
        Lucas.Add(we3DRental); 
 
        Console.WriteLine(Charles.Statement()); 
        Console.WriteLine(Jess.Statement()); 
        Console.WriteLine(Rebecca.Statement()); 
        Console.WriteLine(Lucas.Statement()); 
    } 

   運行後,控制台輸出如下:

  接著,我們要做的就是在這個數據的基礎上編寫測試用例。鑒於有些同學可能還不知道怎麼使用C#的單元測試框架,所以演示一次,後面若無特殊情況,一概略過。關於C#單元測試的更多使用方法,參見微軟的教程,或在google上搜索。
http://msdn.microsoft.com/zh-cn/library/ms379625(v=vs.80).aspx
  1.在Statement方法上點擊鼠標右鍵,會出現如下菜單(因為我裝了VA插件,所以菜單可能不完全一致)

  2.選中其中的創建單元測試,會出現下述對話框

 

  3.選中待測試的Statement()方法後,點擊確定

  4.在項目名稱一欄,輸入一個你喜歡的名字作為測試項目的名稱,在這裡我將測試項目命名為:Tests,然後創建該項目。解決方案中多出了下圖中的文件和工程

  5.打開CustomerTest.cs文件,發現系統自動為我們添加了一個測試:
[csharp]
/// <summary> 
///Statement 的測試 
///</summary> 
[TestMethod()] 
public void StatementTest() 

    string name = string.Empty; // TODO: 初始化為適當的值 
    Customer target = new Customer(name); // TODO: 初始化為適當的值 
    string expected = string.Empty; // TODO: 初始化為適當的值 
    string actual; 
    actual = target.Statement(); 
    Assert.AreEqual(expected, actual); 
    Assert.Inconclusive("驗證此測試方法的正確性。"); 

該測試沒什麼用處,我們要做的是清空它,並使用前面產生的標准數據來編寫第一個測試,結果參照如下:
[csharp] 
[TestMethod()] 
public void StatementForCharles() 

    Movie braveHeart = new Movie("BraveHeart", Movie.NEW_RELEASE); 
    Movie godFather = new Movie("GodFather", Movie.REGULAR); 
    Movie wallE = new Movie("Wall-E", Movie.CHILDRENS); 
 
    Rental bh4DayRental = new Rental(braveHeart, 4); 
    Rental bh2DRental = new Rental(braveHeart, 2); 
    Rental we7DRental = new Rental(wallE, 7); 
    Rental gfRental = new Rental(godFather, 5); 
    Rental we3DRental = new Rental(wallE, 3); 
 
    Customer charles = new Customer("Charles"); 
    charles.Add(bh4DayRental); 
    charles.Add(gfRental); 
 
    string expected = "Rental Record for Charles\n" 
                    + "\tBraveHeart\t12\n" 
                    + "\tGodFather\t6.5\n" 
                    + "Amount owed is 18.5\n" 
                    + "You earned 3 frequent renter points"; 
 
    Assert.AreEqual(expected, charles.Statement()); 

  需要注意的是,該測試的部分代碼是直接copy自上面Main函數,雖然這(拷貝、粘貼)不是被推薦的行為,但我們最終的目的實際上是將原來Main中的人工測試搬移到自動測試中,所以是可以被接受的,畢竟搬移後沒有功能重復的代碼。

  6.構造完該測試後,從下圖所示菜單中運行單元測試

  短暫的編譯、運行後,IDE中出現了類似下面的結果窗口:

  結果列中,綠色的小勾勾說明,我們的測試通過了,如果輸入有誤,則結果畫面如下:

  7.初步了解怎樣添加自動測試後,我們接著為另外三個參考數據編寫相應的測試用例,代碼如下:
[csharp] 
[TestMethod()] 
public void StatementForJess() 

    Movie braveHeart = new Movie("BraveHeart", Movie.NEW_RELEASE); 
    Movie godFather = new Movie("GodFather", Movie.REGULAR); 
    Movie wallE = new Movie("Wall-E", Movie.CHILDRENS); 
 
    Rental bh4DayRental = new Rental(braveHeart, 4); 
    Rental bh2DRental = new Rental(braveHeart, 2); 
    Rental we7DRental = new Rental(wallE, 7); 
    Rental gfRental = new Rental(godFather, 5); 
    Rental we3DRental = new Rental(wallE, 3); 
 
    Customer jess = new Customer("Jess"); 
    jess.Add(bh4DayRental); 
    jess.Add(we7DRental); 
    jess.Add(bh2DRental); 
 
    string expected = "Rental Record for Jess\n" 
                    + "\tBraveHeart\t12\n" 
                    + "\tWall-E\t7.5\n" 
                    + "\tBraveHeart\t6\n" 
                    + "Amount owed is 25.5\n" 
                    + "You earned 5 frequent renter points"; 
 
    Assert.AreEqual(expected, jess.Statement()); 

 
[TestMethod()] 
public void StatementForRebecca() 

    Movie braveHeart = new Movie("BraveHeart", Movie.NEW_RELEASE); 
    Movie godFather = new Movie("GodFather", Movie.REGULAR); 
    Movie wallE = new Movie("Wall-E", Movie.CHILDRENS); 
 
    Rental bh4DayRental = new Rental(braveHeart, 4); 
    Rental bh2DRental = new Rental(braveHeart, 2); 
    Rental we7DRental = new Rental(wallE, 7); 
    Rental gfRental = new Rental(godFather, 5); 
    Rental we3DRental = new Rental(wallE, 3); 
 
    Customer rebecca = new Customer("Rebecca"); 
    rebecca.Add(we7DRental); 
    rebecca.Add(we3DRental); 
 
    string expected = "Rental Record for Rebecca\n" 
                    + "\tWall-E\t7.5\n" 
                    + "\tWall-E\t1.5\n" 
                    + "Amount owed is 9\n" 
                    + "You earned 2 frequent renter points"; 
 
    Assert.AreEqual(expected, rebecca.Statement()); 

 
[TestMethod()] 
public void StatementForLucas() 

    Movie braveHeart = new Movie("BraveHeart", Movie.NEW_RELEASE); 
    Movie godFather = new Movie("GodFather", Movie.REGULAR); 
    Movie wallE = new Movie("Wall-E", Movie.CHILDRENS); 
 
    Rental bh4DayRental = new Rental(braveHeart, 4); 
    Rental bh2DRental = new Rental(braveHeart, 2); 
    Rental we7DRental = new Rental(wallE, 7); 
    Rental gfRental = new Rental(godFather, 5); 
    Rental we3DRental = new Rental(wallE, 3); 
 
    Customer lucas = new Customer("Lucas"); 
    lucas.Add(bh2DRental); 
    lucas.Add(we7DRental); 
    lucas.Add(we3DRental); 
 
    string expected = "Rental Record for Lucas\n" 
                    + "\tBraveHeart\t6\n" 
                    + "\tWall-E\t7.5\n" 
                    + "\tWall-E\t1.5\n" 
                    + "Amount owed is 15\n" 
                    + "You earned 4 frequent renter points"; 
 
    Assert.AreEqual(expected, lucas.Statement()); 

  8.雖然測試都完成了,但是每一個測試中,有相當多的關於創建Movie和Rental對象的重復代碼。本著“測試代碼也要保持整潔”的原則,在這一篇的最後,我們把這些重復的可共享代碼全部抽取為測試類的字段,得到完整的測試代碼如下:
[csharp] 
using CH01_MovieRentalHouse; 
using Microsoft.VisualStudio.TestTools.UnitTesting; 
 
namespace Tests 

    [TestClass] 
    public class CustomerTest 
    { 
        private Movie braveHeart; 
        private Movie godFather; 
        private Movie wallE; 
        private Rental bh4DayRental; 
        private Rental bh2DRental; 
        private Rental we7DRental; 
        private Rental gfRental; 
        private Rental we3DRental; 
 
        public CustomerTest() 
        { 
            braveHeart = new Movie("BraveHeart", Movie.NEW_RELEASE); 
            godFather = new Movie("GodFather", Movie.REGULAR); 
            wallE = new Movie("Wall-E", Movie.CHILDRENS); 
 
            bh4DayRental = new Rental(braveHeart, 4); 
            bh2DRental = new Rental(braveHeart, 2); 
            we7DRental = new Rental(wallE, 7); 
            gfRental = new Rental(godFather, 5); 
            we3DRental = new Rental(wallE, 3); 
        } 
 
        [TestMethod] 
        public void StatementForCharles() 
        { 
            Customer charles = new Customer("Charles"); 
            charles.Add(bh4DayRental); 
            charles.Add(gfRental); 
 
            string expected = "Rental Record for Charles\n" 
                            + "\tBraveHeart\t12\n" 
                            + "\tGodFather\t6.5\n" 
                            + "Amount owed is 18.5\n" 
                            + "You earned 3 frequent renter points"; 
 
            Assert.AreEqual(expected, charles.Statement()); 
        } 
 
        [TestMethod] 
        public void StatementForJess() 
        { 
            Customer jess = new Customer("Jess"); 
            jess.Add(bh4DayRental); 
            jess.Add(we7DRental); 
            jess.Add(bh2DRental); 
 
            string expected = "Rental Record for Jess\n" 
                            + "\tBraveHeart\t12\n"  www.2cto.com
                            + "\tWall-E\t7.5\n" 
                            + "\tBraveHeart\t6\n" 
                            + "Amount owed is 25.5\n" 
                            + "You earned 5 frequent renter points"; 
 
            Assert.AreEqual(expected, jess.Statement()); 
        } 
 
        [TestMethod] 
        public void StatementForRebecca() 
        { 
            Customer rebecca = new Customer("Rebecca"); 
            rebecca.Add(we7DRental); 
            rebecca.Add(we3DRental); 
 
            string expected = "Rental Record for Rebecca\n" 
                            + "\tWall-E\t7.5\n" 
                            + "\tWall-E\t1.5\n" 
                            + "Amount owed is 9\n" 
                            + "You earned 2 frequent renter points"; 
 
            Assert.AreEqual(expected, rebecca.Statement()); 
        } 
 
        [TestMethod] 
        public void StatementForLucas() 
        { 
            Customer lucas = new Customer("Lucas"); 
            lucas.Add(bh2DRental); 
            lucas.Add(we7DRental); 
            lucas.Add(we3DRental); 
 
            string expected = "Rental Record for Lucas\n" 
                            + "\tBraveHeart\t6\n" 
                            + "\tWall-E\t7.5\n" 
                            + "\tWall-E\t1.5\n" 
                            + "Amount owed is 15\n" 
                            + "You earned 4 frequent renter points"; 
 
            Assert.AreEqual(expected, lucas.Statement()); 
        } 
    } 

  小結:這一篇並沒有涉及具體的重構內容,而是一些重構前的准備:
   1.使用原始代碼構造參考數據
   2.使用C#的單元測試框架為這些參考數據創建自動單元測試
  至於這麼做的最重要的原因是:為後面的重構提供可自動完成的、可重復執行的檢驗標准
作者:virtualxmars

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved