Try   HackMD

[12/12]TDD 與重構工作坊

https://hackmd.io/@7900XTX/TDD

TDD (Test-Driven Development) 開發流程

TDD在開發過程中不斷利用單元測試 (Unit Test),確保程式的功能正確。
有別於直接開始實作功能的開發流程,TDD注重於先寫出測試,再寫出能通過測試的程式。

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

1. 寫測試

在開始實作程式邏輯之前,先寫測試。一旦最終完成的程式能夠通過測試案例,則代表程式正確,因為程式的正確性驗證已經透過測試確認完畢。

  • 列測項

    先從簡單的案例開始,接著列出具有代表性、邏輯分支的測試案例。
  • 定介面

    設計即將實作程式之使用介面、函式名稱、參數等。

在開始實作程式功能之前,先進行一次測試,確保不會通過測試。

  • 訂出介面convertgetAns,與包含1個參數的建構子FizzBizz
  • 當輸入為1時,輸出應為1。
    @Testvoid test_1() {FizzBizz sut = new FizzBizz(1); ​ sut.convert();String actual = sut.getAns();assertEquals("1",actual);}
  • 由於還沒開始寫程式功能,因此不應通過測試。
    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

2. 寫程式使通過測試

Work -> Correct -> Fast

可先直接回傳正確答案,確保程式與測試能正常運行。
成功通過測試後,用良好風格的程式實作真正的功能與邏輯。

  • 直接回傳正確答案,確保測試功能正常。

    ​​​​public String getAns() { ​​​​ return "1"; ​​​​}

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

  • 逐步增加具代表性的測項

    ​​​​class FizzBizzTest { ​​​​ @Test ​​​​ void test_1() { ​​​​ FizzBizz sut = new FizzBizz(1); ​​​​ sut.convert(); ​​​​ String actual = sut.getAns(); ​​​​ assertEquals("1",actual); ​​​​ } ​​​​ @Test ​​​​ void test_2() { ​​​​ FizzBizz sut = new FizzBizz(2); ​​​​ sut.convert(); ​​​​ String actual = sut.getAns(); ​​​​ assertEquals("1 2",actual); ​​​​ } ​​​​ @Test ​​​​ void test_3() { ​​​​ FizzBizz sut = new FizzBizz(3); ​​​​ sut.convert(); ​​​​ String actual = sut.getAns(); ​​​​ assertEquals("1 2 Fizz",actual); ​​​​ } ​​​​ @Test ​​​​ void test_15() { ​​​​ FizzBizz sut = new FizzBizz(15); ​​​​ sut.convert(); ​​​​ String actual = sut.getAns(); ​​​​ assertEquals("1 2 Fizz 4 Bizz Fizz 7 8 Fizz Bizz" + ​​​​ " 11 Fizz 13 14 FizzBizz", ​​​​ actual); ​​​​ } ​​​​}
  • 修改程式功能,使其能通過更多進階的測試案例。

    ​​​​public class FizzBizz{ ​​​​ private int number; ​​​​ private String answer = ""; ​​​​ public FizzBizz(int number) { ​​​​ this.number = number; ​​​​ } ​​​​ private void process(int i) { ​​​​ if(!answer.isEmpty()) ​​​​ answer = answer + " "; ​​​​ if(i%3==0) ​​​​ answer = answer + "Fizz"; ​​​​ if(i%5==0) ​​​​ answer = answer + "Bizz"; ​​​​ if(i%3!=0 && i%5!=0) ​​​​ answer = answer + String.valueOf(i); ​​​​ } ​​​​ public void convert() { ​​​​ for (int i = 1; i <= this.number; i++) ​​​​ process(i); ​​​​ } ​​​​ public String getAns() { ​​​​ return answer; ​​​​ } ​​​​}

3. 進行重構

在通過測試 (綠燈)後,找出可以重構的地方進行重構,測試也可能有重構的空間或必要。
重構完畢後,須確保能通過測試。
TDD在開發過程中,不斷進行測試與重構。
在專案還處於較小規模就進行重構,避免堆積成大專案時難以重構。

  • 不可同時修改程式與測試

    每次只能更改一部分程式,待通過測試綠燈後才能再改其他部分。
    為避免程式與測試之邏輯皆錯誤,但錯誤的邏輯互相抵銷後,反倒通過測試,因此不可同時修改程式與測試。
    在通過測試後若需更動程式,應確保測試不變,待新程式通過測試後,才能更動測試,反之亦然。
  • 將程式進行重構,預想未來若有其他數字的需求 (Line 21),具有較好的維護與擴充性。

    ​​​​public class FizzBizz{ ​​​​ private int number; ​​​​ private String answer = ""; ​​​​ public FizzBizz(int number) { ​​​​ this.number = number; ​​​​ } ​​​​ private String apply(int i, String currAns, int div, String word) { ​​​​ if (i % div == 0) { ​​​​ currAns += word; ​​​​ } ​​​​ return currAns; ​​​​ } ​​​​ public void convert() { ​​​​ List<String> result = new ArrayList<>(); ​​​​ for(int i=1;i<=number;i++) { ​​​​ String answer = ""; ​​​​ answer = apply(i,answer,3,"Fizz"); ​​​​ answer = apply(i,answer,5,"Bizz"); ​​​​// answer = apply(i,answer,7,"7izz"); ​​​​ if(answer.isEmpty()) { ​​​​ answer = "" + i; ​​​​ } ​​​​ result.add(answer); ​​​​ } ​​​​ this.answer = String.join(" ", result); ​​​​ } ​​​​ public String getAns() { ​​​​ return answer; ​​​​ } ​​​​}

適合TDD的場景

  • 新專案需求明確,可列出測項
  • 拼接舊專案的部分
  • 修補bug

課後心得

本次業界工作坊講題為測試驅動開發 (Test-Driven Development, TDD),並在課堂上以Fizz Buzz遊戲作為範例,學習TDD的開發流程,而我覺得在課堂上的即時練習非常有用,能在實作的過程中更加深刻地體會TDD的過程,

透過課堂上的互動,我也更深刻體驗到開放封閉原則的重要性。在軟體開發的過程中,可能會面臨到不斷有不合理的新要求提出,開發結束後仍可能會面臨到功能新增或修改,因此程式具有良好的維護與擴充性是非常重要。

在這堂課中,我學習到新的一種軟體工程專案開發的視角,在測試、主要程式與重構的不斷循環下,使專案在開發的過程,能在功能正確的前提下,不斷前進並維持良好架構。