# [12/12]TDD 與重構工作坊
#### [https://hackmd.io/@7900XTX/TDD](https://hackmd.io/@7900XTX/TDD)
## TDD (Test-Driven Development) 開發流程
TDD在開發過程中不斷利用單元測試 (Unit Test),確保程式的功能正確。
有別於直接開始實作功能的開發流程,TDD注重於先寫出測試,再寫出能通過測試的程式。

### <font color="red">1. 寫測試</font>
在開始實作程式邏輯之前,先寫測試。一旦最終完成的程式能夠通過測試案例,則代表程式正確,因為程式的正確性驗證已經透過測試確認完畢。
* #### 列測項
先從簡單的案例開始,接著列出具有代表性、邏輯分支的測試案例。
* #### 定介面
設計即將實作程式之使用介面、函式名稱、參數等。
在開始實作程式功能之前,先進行一次測試,確保<font color=#ff5e00>*不會*</font>通過測試。
:::info
* <font color=#999999>訂出介面`convert`、`getAns`,與包含1個參數的建構子`FizzBizz`。
* 當輸入為1時,輸出應為1。</font>
```java=
@Test
void test_1() {
FizzBizz sut = new FizzBizz(1);
sut.convert();
String actual = sut.getAns();
assertEquals("1",actual);
}
```
* <font color=#999999>由於還沒開始寫程式功能,因此不應通過測試。</font>

:::
### <font color=#00c22d>2. 寫程式使通過測試</font>
> Work -> Correct -> Fast
可先直接回傳正確答案,確保程式與測試能正常運行。
成功通過測試後,用良好風格的程式實作真正的功能與邏輯。
:::info
* <font color=#999999>直接回傳正確答案,確保測試功能正常。</font>
```java=
public String getAns() {
return "1";
}
```

* <font color=#999999>逐步增加具代表性的測項</font>
```java=
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);
}
}
```
* <font color=#999999>修改程式功能,使其能通過更多進階的測試案例。</font>
```java=
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;
}
}
```
:::
### <font color= #4dbeff> 3. 進行重構</font>
在通過測試 (綠燈)後,找出可以重構的地方進行重構,測試也可能有重構的空間或必要。
重構完畢後,須確保能通過測試。
TDD在開發過程中,不斷進行測試與重構。
在專案還處於較小規模就進行重構,避免堆積成大專案時難以重構。
* #### 不可同時修改程式與測試
每次只能更改一部分程式,待通過測試綠燈後才能再改其他部分。
為避免程式與測試之邏輯皆錯誤,但錯誤的邏輯互相抵銷後,反倒通過測試,因此不可同時修改程式與測試。
在通過測試後若需更動`程式`,應確保`測試`不變,待新程式通過測試後,才能更動`測試`,反之亦然。
:::info
* <font color=#999999>將程式進行重構,預想未來若有其他數字的需求 (`Line 21`),具有較好的維護與擴充性。 </font>
```java=
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的過程,
透過課堂上的互動,我也更深刻體驗到開放封閉原則的重要性。在軟體開發的過程中,可能會面臨到不斷有不合理的新要求提出,開發結束後仍可能會面臨到功能新增或修改,因此程式具有良好的維護與擴充性是非常重要。
在這堂課中,我學習到新的一種軟體工程專案開發的視角,在測試、主要程式與重構的不斷循環下,使專案在開發的過程,能在功能正確的前提下,不斷前進並維持良好架構。