# TDD 入門
##### 研一 / 戴均民
##### 業二 / 王羿喬
<div style="font-size: 2rem">https://lihi1.cc/5Lajj</div>
---
# TDD?
---
## Test-Driven
## Development
---
# 測試驅動開發
---
<img src="https://i.imgur.com/Yie6F46.png" style="max-width: 100%; max-height: 100%; border: 0; background: initial;">
---
#### As 一個收銀員
#### I want 一個發票營業稅計算工具
#### so that 我不會再開錯三聯式發票
---
## 從新增或改寫測試開始
---
## 需求太抽象怎麼辦
---
## 用範例來寫需求

---
### 用未稅價計算營業稅
<style>
table.table-2 {
font-size: 2.5rem;
}
.reveal table.table-2 tbody tr th, .reveal table.table-2 tbody tr td {
border: 1px solid #ddd;
}
</style>
<table class="table-2">
<tr>
<th colspan="2">輸入</th>
<th colspan="2">輸出</th>
</tr>
<tr>
<td>稅率 5%</td>
<td>未稅價 100 元</td>
<td>營業稅 5 元</td>
<td>含稅價 105 元</td>
</tr>
<tr>
<td>稅率 5%</td>
<td>未稅價 0 元</td>
<td>營業稅 0 元</td>
<td>含稅價 0 元</td>
</tr>
<tr>
<td>稅率 5%</td>
<td>未稅價 16190 元</td>
<td>營業稅 810 元</td>
<td>含稅價 17000 元</td>
</tr>
<tr>
<td>稅率 5%</td>
<td>未稅價 105 元</td>
<td>營業稅 5 元</td>
<td>含稅價 110 元</td>
</tr>
<tr>
<td>稅率 5%</td>
<td>未稅價 10 元</td>
<td>營業稅 1 元</td>
<td>含稅價 11 元</td>
</tr>
</table>
---
# 原則
### 沒寫測試以前不能寫任何程式邏輯
---
```js=
// index.js
exports.fromExcluded = (rate, excluded) => {}
// index.test.js
const sut = require('./index')
test.each([
[0.05, 100, 5, 105]
])('未稅價計算營業稅,稅率 %d,未稅價 %i 元,營業稅 %i 元,含稅價 %i 元', async (rate, excluded, tax, included) => {
const actual = sut.fromExcluded(rate, excluded)
expect(actual).toEqual({ rate, excluded, tax, included })
})
```
---

---
## 為什麼只寫了一個測試?
---
# 原則
### 一次只寫出一個沒通過的測試
---
## 讓測試通過
```js=
// index.js
exports.fromExcluded = (rate, excluded) => {
return {
excluded: 100,
included: 105,
rate: 0.05,
tax: 5
}
}
```
---

---
## 程式寫這樣對嗎?
---
# 原則
### 只寫剛好能通過測試的程式
---
## Over Design 就是浪費
---
## 由於測試已經通過
## 所以就繼續加測試
---
```js=
// index.test.js
const sut = require('./index')
test.each([
[0.05, 100, 5, 105],
[0.05, 0, 0, 0]
])('未稅價計算營業稅,稅率 %d,未稅價 %i 元,營業稅 %i 元,含稅價 %i 元', async (rate, excluded, tax, included) => {
const actual = sut.fromExcluded(rate, excluded)
expect(actual).toEqual({ rate, excluded, tax, included })
})
```
---

---
## 讓測試通過
```js=
// index.js
exports.fromExcluded = (rate, excluded) => {
if (excluded === 0) {
return {
excluded: 0,
included: 0,
rate: 0.05,
tax: 0
}
}
return {
excluded: 100,
included: 105,
rate: 0.05,
tax: 5
}
}
```
---

---
## 程式寫這樣對嗎?
---
# 重構程式
---
# 原則
### 只有在測試通過時 才能重構程式
---
```js=
// index.js
exports.fromExcluded = (rate, excluded) => {
return {
excluded,
included: excluded * (1 + rate),
rate: 0.05,
tax: excluded * rate
}
}
```
---

---
## 再度新增一個失敗的測試
```js=
// index.test.js
const sut = require('./index')
test.each([
[0.05, 100, 5, 105],
[0.05, 0, 0, 0],
[0.05, 16190, 810, 17000]
])('未稅價計算營業稅,稅率 %d,未稅價 %i 元,營業稅 %i 元,含稅價 %i 元', async (rate, excluded, tax, included) => {
const actual = sut.fromExcluded(rate, excluded)
expect(actual).toEqual({ rate, excluded, tax, included })
})
```
---

---
## 讓測試通過
```js=
// index.js
exports.fromExcluded = (rate, excluded) => {
return {
excluded,
included: Math.ceil(excluded * (1 + rate)),
rate: 0.05,
tax: Math.ceil(excluded * rate)
}
}
```
---

---
## 再度新增一個失敗的測試
```js=
// index.test.js
const sut = require('./index')
test.each([
[0.05, 100, 5, 105],
[0.05, 0, 0, 0],
[0.05, 16190, 810, 17000],
[0.05, 105, 5, 110]
])('未稅價計算營業稅,稅率 %d,未稅價 %i 元,營業稅 %i 元,含稅價 %i 元', async (rate, excluded, tax, included) => {
const actual = sut.fromExcluded(rate, excluded)
expect(actual).toEqual({ rate, excluded, tax, included })
})
```
---

---
## 讓測試通過
```js=
// index.js
exports.fromExcluded = (rate, excluded) => {
return {
excluded,
included: Math.round(excluded * (1 + rate)),
rate: 0.05,
tax: Math.round(excluded * rate)
}
}
```
---

---
### 再度新增一個失敗的測試?
```js=
// index.test.js
const sut = require('./index')
test.each([
[0.05, 100, 5, 105],
[0.05, 0, 0, 0],
[0.05, 16190, 810, 17000],
[0.05, 105, 5, 110],
[0.05, 10, 1, 11]
])('未稅價計算營業稅,稅率 %d,未稅價 %i 元,營業稅 %i 元,含稅價 %i 元', async (rate, excluded, tax, included) => {
const actual = sut.fromExcluded(rate, excluded)
expect(actual).toEqual({ rate, excluded, tax, included })
})
```
---

---
### 用未稅價計算營業稅
<style>
table.table-2 {
font-size: 2.5rem;
}
.reveal table.table-2 tbody tr th, .reveal table.table-2 tbody tr td {
border: 1px solid #ddd;
}
</style>
<table class="table-2">
<tr>
<th colspan="2">輸入</th>
<th colspan="2">輸出</th>
</tr>
<tr>
<td>稅率 5%</td>
<td>未稅價 100 元</td>
<td>營業稅 5 元</td>
<td>含稅價 105 元</td>
</tr>
<tr>
<td>稅率 5%</td>
<td>未稅價 0 元</td>
<td>營業稅 0 元</td>
<td>含稅價 0 元</td>
</tr>
<tr>
<td>稅率 5%</td>
<td>未稅價 16190 元</td>
<td>營業稅 810 元</td>
<td>含稅價 17000 元</td>
</tr>
<tr>
<td>稅率 5%</td>
<td>未稅價 105 元</td>
<td>營業稅 5 元</td>
<td>含稅價 110 元</td>
</tr>
<tr>
<td>稅率 5%</td>
<td>未稅價 10 元</td>
<td>營業稅 1 元</td>
<td>含稅價 11 元</td>
</tr>
</table>
---
## 已經沒有其他測試了
---
### 就是這個程式已經完成了
---
# 程式碼
<https://github.com/taichunmin/jest-demo-20190807>
---

---
# 課後練習
---
### 用含稅價計算營業稅
<style>
table.table-1 {
font-size: 2.5rem;
}
.reveal table.table-1 tbody tr th, .reveal table.table-1 tbody tr td {
border: 1px solid #ddd;
}
</style>
<table class="table-1">
<tr>
<th colspan="2">輸入</th>
<th colspan="2">輸出</th>
</tr>
<tr>
<td>稅率 5%</td>
<td>含稅價 105 元</td>
<td>營業稅 5 元</td>
<td>未稅價 100 元</td>
</tr>
<tr>
<td>稅率 5%</td>
<td>含稅價 0 元</td>
<td>營業稅 0 元</td>
<td>未稅價 0 元</td>
</tr>
<tr>
<td>稅率 5%</td>
<td>含稅價 17000 元</td>
<td>營業稅 810 元</td>
<td>未稅價 16190 元</td>
</tr>
<tr>
<td>稅率 5%</td>
<td>含稅價 110 元</td>
<td>營業稅 5 元</td>
<td>未稅價 105 元</td>
</tr>
<tr>
<td>稅率 5%</td>
<td>含稅價 10 元</td>
<td>營業稅 0 元</td>
<td>未稅價 10 元</td>
</tr>
</table>
---
<style>
.reveal {
font-family: "微軟正黑體"
}
</style>
## Q&A
{"metaMigratedAt":"2023-06-14T23:01:24.487Z","metaMigratedFrom":"YAML","title":"測試驅動開發 TDD 入門","breaks":true,"slideOptions":"{\"transition\":\"slide\",\"theme\":\"moon\",\"allottedMinutes\":60}","contributors":"[{\"id\":\"0d9a5e06-1f92-4142-b9df-fed4c8873573\",\"add\":10809,\"del\":3701}]"}