昨天了解了主要三個測試類型:Unit Test, Integration Test, E2E Test
但是了解理論還是很模糊,所以今天我找了一個學習影片,用簡單的例子來快速實作三種測試類型。
實作學習搭配:JavaScript Testing Introduction Tutorial - Unit Tests, Integration Tests & e2e Tests
|-- dist: 存放 webpack 打包後的檔案內容。
|-- index.html: 網頁畫面。
|-- app.js: 存取 index.html 的 DOM,操作畫面邏輯。
|-- util.js: 操作邏輯會用到的 function。
|-- ...
{
"scripts": {
"test": "jest"
"auto-test": "jest --watch" // 執行一次後,後面有修改程式碼,都會自動執行測試
},
}
兩個 Input 加上一個按鈕,點擊按鈕產生 User列表。
學習畫面來源
準備好就開始吧!
generateText
function。
exports.generateText = (name, age) => {
// Returns output text
return `${name} (${age} years old)`;
};
util.test.js
// 引入要測試的資料夾位置與測試目標
const { generateText } = require("./util");
// 使用 jest 提供的測試方法:test(測試目標, 執行測試函式)
test("Expect: Output name and age string", () => {
const testOutput = generateText("Joanna", 18);
// expect(測試項目).<選擇執行方法>(預期執行結果)
expect(testOutput).toBe("Joanna (18 years old)");
});
補充:
toBe()
用途:使用Object.is()
比對基礎型別或物件型別兩者是否相等。
參考來源:https://jestjs.io/docs/expect#tobevalue
執行測試結果:執行測試指令yarn test
補充反向錯誤測試結果:如果 generateText() 應該要傳入參數,而沒傳入的情況。
test("Expect: input string w/ undefined", () => {
const testOutput = generateText();
expect(testOutput).toBe("undefined (undefined years old)");
});
我們可以針對同一個函式測試多種情況,也可以在同一支 測試函式test()
中寫入多個input與 預期expect()
。
測試方向:我想測試輸入兩個 Input,經過檢查機制後,Output 結果是否符合預期。
測試目標:AddUser
內的 validateInput()
與 generateText()
const addUser = () => {
// Fetches the user input, creates a new HTML element based on it
// and appends the element to the DOM
const newUserNameInput = document.querySelector('input#name');
const newUserAgeInput = document.querySelector('input#age');
// ------------------------ 測試項目1 ------------------------
if (
!validateInput(newUserNameInput.value, true, false) ||
!validateInput(newUserAgeInput.value, false, true)
) {
return;
}
// ------------------------ 測試項目1 ------------------------
const userList = document.querySelector('.user-list');
// ------------------------ 測試項目2 ------------------------
const outputText = generateText(
newUserNameInput.value,
newUserAgeInput.value
);
// ------------------------ 測試項目2 ------------------------
const element = createElement('li', outputText, 'user-item');
userList.appendChild(element);
};
先進行重構,把上面要進行的測試項目拆出,方便後續測試:./utils.js
: 包含兩個函式邏輯。
exports.checkAndGenerate = (name, age) => {
if (
!validateInput(name, true, false) ||
!validateInput(age, false, true)
) {
return false;
}
return generateText(name, age);
}
app.js
const addUser = () => {
// Fetches the user input, creates a new HTML element based on it
// and appends the element to the DOM
const newUserNameInput = document.querySelector('input#name');
const newUserAgeInput = document.querySelector('input#age');
// ---------------------------- 重構內容 ----------------------------
const outputText = checkAndGenerate(
newUserNameInput.value,
newUserAgeInput.value
)
if(!outputText){
return false
}
// ---------------------------- 重構內容 ----------------------------
const userList = document.querySelector('.user-list');
const element = createElement('li', outputText, 'user-item');
userList.appendChild(element);
};
撰寫測試:
const { checkAndGenerate } = require("./util");
test("Expext: checkValue & return results", () => {
const testOutput1 = checkAndGenerate("Joanna", 18);
expect(testOutput1).toBe("Joanna (18 years old)")
const testOutput2 = checkAndGenerate();
expect(testOutput2).toBe(false);
})
執行結果:
這裡的 Integration Test 目標checkAndGenerate()
可以看到,裡面包含兩個函式:validateInput()
與 generateText()
。假設我將兩個函式 都進行 Unit Test 通過,但這只能證明兩個函式I/O 都符合預期,無法保證 Integration Test 不會有問題。像是這段:
if (
!validateInput(name, true, false) ||
!validateInput(age, false, true)
) {
return false;
}
如果 if-else 邏輯寫錯,Integration Test 也不會測試成功(不符合預期)。
測試方向:我想測試在瀏覽器上輸入兩個 Input,Output 結果是否符合預期。
測試目標:使用 Poppeteer
操作 DOM,模擬使用者行為,查看輸出結果。
撰寫測試:Poppeteer
模擬操作:
puppeteer.launch()
test("Expect: user type inputs & click to get results", async () => {
const browser = await puppeteer.launch({
slowMo: 80,
headless: false, // 代表我想看到 puppeteer 開啟 瀏覽器 的 GUI
});
const page = await browser.newPage();
// 帶入 Live Demo 路徑,或 檔案路徑
await page.goto("http://127.0.0.1:5500/index.html");
await page.click("input#name");
await page.type("input#name", "Joanna");
await page.click("input#age");
await page.type("input#age", '18');
await page.click("button#btnAddUser");
const textContent = await page.$eval(".user-item", el => el.textContent);
expect(textContent).toBe("Joanna (18 years old)");
});
執行結果:
Puppeteer
執行每個步驟都會回傳 Promise,記得要加 async
await
Puppeteer
會停在一半,無法往下進行。$eval(<選取器>, 操作函式)
:擷取網頁上的一數據。今天的練習很充實,寫了 code 更有感覺,三種類型的測試都有明確的測試目標,需要看專案上實際功能,才能提高測試價值。而用「好寫測試」的方向去重構程式碼,確實比我自己埋頭思考怎麼寫出簡潔程式碼有頭緒多了!
但我在寫 test()
有一點點疑惑,需要自己假設使用者不同的輸入寫法做測試,但使用者輸入的可能性是我沒想到的情況,就很有可能測試有漏網之魚吧!
Anyway~且戰且走,邊走邊看吧~