## 關於那個看起來很難的<font color=#ff7738>測試</font>
---
## <font color=#ff7738>前端測試</font>是什麼?
其實在前端的測試可以分成<font color=#ff7738>三</font>個種類:<!-- .element: class="fragment" data-fragment-index="1" -->
<font>E2E Testing、</font><!-- .element: class="fragment" data-fragment-index="2" --><font>Integration Testing、</font><!-- .element: class="fragment" data-fragment-index="3" --><font>Unit Testing</font><!-- .element: class="fragment" data-fragment-index="4" -->
<!-- .element: class="fragment" data-fragment-index="5" -->
<font size=4>圖片來源:[TestPyramid 測試金字塔](https://martinfowler.com/bliki/TestPyramid.html)</font><!-- .element: class="fragment" data-fragment-index="5" -->
---
#### <font color=#ff7738>避免成為冰淇淋甜筒</font>
<!-- .element: class="fragment" data-fragment-index="1" -->
<font size=4>圖片來源:[讓我們聊聊測試自動化金字塔](https://kojenchieh.pixnet.net/blog/post/466703174-%E8%AE%93%E6%88%91%E5%80%91%E8%81%8A%E8%81%8A%E6%B8%AC%E8%A9%A6%E8%87%AA%E5%8B%95%E5%8C%96%E9%87%91%E5%AD%97%E5%A1%94)</font><!-- .element: class="fragment" data-fragment-index="1" -->
---
## <font color=#ff7738>Unit</font> Testing
單元測試,擁有以下特點:
1. <font color=#ff7738>單點突破很強</font>,最小的測試單位可以是一個 function<!-- .element: class="fragment" data-fragment-index="1" -->
2. 會<!-- .element: class="fragment" data-fragment-index="2" --><font color=#ff7738>隔離真實環境</font><!-- .element: class="fragment" data-fragment-index="2" -->,對某部分的功能做測試
3. 測試的範圍較小,<font color=#ff7738>容易</font>發現錯誤<!-- .element: class="fragment" data-fragment-index="3" -->
4. 測試速度<font color=#ff7738>最快</font>,維護成本<!-- .element: class="fragment" data-fragment-index="4" --><font color=#ff7738>最低</font><!-- .element: class="fragment" data-fragment-index="4" -->
5. <font color=#ff7738>任何時候</font>都可以導入測試,甚至是在寫程式之前<!-- .element: class="fragment" data-fragment-index="5" -->
---
## 但<font color=#ff7738>為什麼</font>我們要寫測試?
- <font color=#ff7738>不曉得</font>程式在哪一次修改後會出現錯誤<!-- .element: class="fragment" data-fragment-index="1" -->
- 更容易為程式做<!-- .element: class="fragment" data-fragment-index="2" --><font color=#ff7738>重構</font><!-- .element: class="fragment" data-fragment-index="2" -->
- <font color=#ff7738>節省時間</font>,不需要手動點擊 UI<!-- .element: class="fragment" data-fragment-index="3" -->
---
### 做測試的原因可能有很多
## 但<font color=#ff7738>目的</font>都與<font color=#ff7738>讓程式變得更好</font>有關<!-- .element: class="fragment" data-fragment-index="1" -->
---
## 和<font color=#ff7738>手動測試</font>有什麼不同?
- 每一次的手動測試都是<font color=#ff7738>一次性的花費</font>,你需要一直做<font color=#ff7738>相同</font>的事情。<!-- .element: class="fragment" data-fragment-index="1" -->
- 自動化測試是一項<font color=#ff7738>投資</font>,隨著時間越久,報酬率就越高。<!-- .element: class="fragment" data-fragment-index="2" -->
---
## 接下來
## 開始<font color=#ff7738>單元測試</font>吧!
---
## 但是該<font color=#ff7738>怎麼開始</font>?
在測試之前...<!-- .element: class="fragment" data-fragment-index="1" -->
先來了解他的<font color=#ff7738>原理</font>!<!-- .element: class="fragment" data-fragment-index="1" -->
---
#### 看看下方的 function
```javascript=
const sum = (a, b) => a + b;
const value = sum(1, 3);
// 希望 value 是 4,是或不是都顯示對應的提示
```
#### 用 <font color=#ff7738>if 判斷</font>吧!<!-- .element: class="fragment" data-fragment-index="1" -->
```javascript=
if (value === 4) {
console.log('正確!');
} else {
console.log('錯誤!');
}
```
<!-- .element: class="fragment" data-fragment-index="1" -->
---
#### 那些測試框架就只是<font color=#ff7738>把判斷包裝</font>起來
```javascript=
const expect = (actual, expected) => {
if (actual === expected) {
console.log('正確!');
} else {
console.log('錯誤!');
}
}
expect(sum(1, 3), 4);
```
---
# 但是<font color=#ff7738>還不夠</font>!
### 如果要判斷 <font color=#ff7738>NaN</font> 怎麼辦?<!-- .element: class="fragment" data-fragment-index="1" -->
```javascript=
console.log(NaN === NaN);
// 比較起來會是 false
```
<!-- .element: class="fragment" data-fragment-index="2" -->
---
### 為了要因應<font color=#ff7738>各種狀況的驗證</font>
## 所以我們不能只有一種<!-- .element: class="fragment" data-fragment-index="1" --><font color=#ff7738>判斷方法</font><!-- .element: class="fragment" data-fragment-index="1" -->
---
### 將判斷相等的邏輯<font color=#ff7738>放到一個物件</font>中
```javascript=
const expect = (actual) => ({
toBe: (expected) => {
if (actual === expected) {
console.log('正確!');
} else {
console.log('錯誤!');
}
},
});
```
---
### 在該物件內<font color=#ff7738>加入判斷 NaN</font> 的方法
```javascript=
const expect = (actual) => ({
toBe: (expected) => { /* do smoething */ },
toBeNaN: () => {
if (isNaN(actual)) {
console.log('正確!');
} else {
console.log('錯誤!');
}
},
});
```
---
#### 經過修改後測試會變成...
```javascript=
const expect = (actual) => ({
toBe: (expected) => { /* do something */ },
toBeNaN: () => { /* do something */ },
});
/* expect 會回傳斷言庫,因此可以直接使用斷言庫內的 method */
expect(sum(1, 3)).toBe(4); // 正確!
expect(sum(1, 3)).toBe(5); // 錯誤!
expect(NaN).toBeNaN(); // 正確!
```
---
## 但<font color=#ff7738>還是不夠</font>啊!
### 斷言的方式還有其他...<!-- .element: class="fragment" data-fragment-index="1" -->
- 非同步執行<!-- .element: class="fragment" data-fragment-index="2" -->
- 拋出錯誤<!-- .element: class="fragment" data-fragment-index="3" -->
- 判斷 Undefined<!-- .element: class="fragment" data-fragment-index="4" -->
- 判斷 Truthy、Falsy<!-- .element: class="fragment" data-fragment-index="5" -->
- 判斷 Object、Array<!-- .element: class="fragment" data-fragment-index="6" -->
<br />
### 全都<font color=#ff7738>要自己做嗎</font>?<!-- .element: class="fragment" data-fragment-index="7" -->
---
## 不需要!
### 現在的你<font color=#ff7738>和剛才不同了</font>!<!-- .element: class="fragment" data-fragment-index="1" -->
### 因為你<font color=#ff7738>已經知道測試框架</font>背後<!-- .element: class="fragment" data-fragment-index="2" --><font color=#ff7738>做了什麼</font><!-- .element: class="fragment" data-fragment-index="2" -->
---
### 進入<font color=#ff7738>測試框架</font>
### 開始<font color=#ff7738>使用 Jest</font>!<!-- .element: class="fragment" data-fragment-index="1" -->
<!-- .element: class="fragment" data-fragment-index="1" -->
<font size=4>圖片來源:[Jest 官方網站](https://jestjs.io/)</font><!-- .element: class="fragment" data-fragment-index="1" -->
---
#### <font color=#ff7738>Jest 的測試</font>怎麼寫?
```javascript=
test('用 1 與 3 執行 sum 會回傳 4', () => {
const expected = 4;
const actual = sum(1, 3);
expect(actual).toBe(expected);
});
```
<!-- .element: class="fragment" data-fragment-index="1" -->
### 除了 test 之外<!-- .element: class="fragment" data-fragment-index="2" -->
### 是不是<font color=#ff7738>一切都變得簡單了</font>?<!-- .element: class="fragment" data-fragment-index="2" -->
---
### 一個測試案例會有哪些部分?
- <font color=#ff7738>好的命名</font>,必須能夠<font color=#ff7738>具體知道測試</font>的內容<!-- .element: class="fragment" data-fragment-index="1" -->
- 測試目標?<!-- .element: class="fragment" data-fragment-index="2" -->
- 做了什麼?<!-- .element: class="fragment" data-fragment-index="3" -->
- 期望什麼?<!-- .element: class="fragment" data-fragment-index="4" -->
<br />
- <font color=#ff7738>好的內容</font>,遵循<!-- .element: class="fragment" data-fragment-index="5" --> <font color=#ff7738>3A 原則</font><!-- .element: class="fragment" data-fragment-index="5" -->
- <font color=#ff7738>Arrange</font> 準備<!-- .element: class="fragment" data-fragment-index="6" -->
- <font color=#ff7738>Act</font> 執行<!-- .element: class="fragment" data-fragment-index="7" -->
- <font color=#ff7738>Assert</font> 驗證<!-- .element: class="fragment" data-fragment-index="8" -->
---
#### 看回剛剛的測試案例
```javascript=
// 測試了 sum 並用 1 與 3 執行,最後回傳了 4
test('用 1 與 3 執行 sum 會回傳 4', () => {
// Arrange:準備好期望的結果值
const expected = 4;
// Act:用 1 和 3 執行 sum
const actual = sum(1, 3);
// Assert:斷言階段
expect(actual).toBe(expected);
});
```
### 他現在甚至可以<font color=#ff7738>成為 sum 的操作手冊</font>!<!-- .element: class="fragment" data-fragment-index="1" -->
---
#### 再做更多吧!
### 假設現在有個<font color=#ff7738>類別</font>...<!-- .element: class="fragment" data-fragment-index="1" -->
```javascript=
class TodoList {
constructor() {
this.todoList = [];
}
getList() {
return this.todoList;
}
addTodo(todo) {
this.todoList.push(todo);
}
}
```
<!-- .element: class="fragment" data-fragment-index="1" -->
---
### 就可以這樣子做
```javascript=
test('使用 TodoList 的 addTodo 加入的事項,可以從 getList 中取出', () => {
// Arrange
const todo = new TodoList();
const expected = ['first'];
// Act
todo.addTodo('first');
// Assert
expect(todo.getList()).toEqual(expected)
})
```
---
### 加入 <font color=#ff7738>UI</font> 呢?
#### 看看這裡 👇
[Unit Test | 香草 JS 的單元測試 feat. @testing-library/dom](https://medium.com/starbugs/unit-test-%E9%A6%99%E8%8D%89-js-%E7%9A%84%E5%96%AE%E5%85%83%E6%B8%AC%E8%A9%A6-feat-testing-library-dom-f8539137bec9)
---
# 謝謝大家!
---
參考文章:
- <font size=4>[Frontend Testing Vs. Backend Testing: What's the Difference?](https://www.guru99.com/frontend-testing-vs-backend-testing.html)</font>
- <font size=4>[Introduction to Front-End unit testing](https://dev.to/christopherkade/introduction-to-front-end-unit-testing-510n)</font>
- <font size=4>[What should we test (ReactJS Components)](https://hackernoon.com/what-should-we-test-reactjs-components-647ded674928)</font>
- <font size=4>[Challenging the Testing Pyramid](https://juristr.com/blog/2019/07/testing-cypress-intro/)</font>
- <font size=4>[JavaScript 單元測試導引 — 進入 Jest 測試前,先嘗試自己 Build 一個吧](https://medium.com/@leo36094/%E5%9F%BA%E7%A4%8E-javascript-%E8%87%AA%E5%8B%95%E5%8C%96%E6%B8%AC%E8%A9%A6%E5%B0%8E%E5%BC%95-b00729418668)</font>
---