TDD
測試導向開發 (Test Driven Development, TDD)
2022/03/24 (新增連結)
概念
測試導向開發不只是重視測試,而且是在開發前先準備測試案例,而且以測試來主導開發。根據Version One(2018)的調查,就敏捷的測試相關實務而言,有75%採用Unit Test,35%採用TDD,32%採用Automated Acceptance Testing,17%採用BDD。
很多人會認為,開發的時間都來不及了,怎麼可能先準備好測試案例? 以TDD的概念,就是在寫需求的時候,就開始準備測試案例,也利用撰寫測試案例的過程來確認需求,這樣並不會花費更多時間,因為可以節省誤會需求的浪費,另外,也會因為測試自動化,可以減少在測試上花的時間,也一併提昇開發的品質。
TDD Is Not Just About Testing
Do You Really Need to Go Faster?
Be Conscious
2021/12/16 (新增投影片)
TDD模式
TDD循環
紅燈模式
測試模式
綠燈模式
Katalon
Katalon 最新版本 9.0.0 (2023/11/21)
Katalon Studio是免費使用
API Testing
測試api要比測試web UI稍微單純一點,所以,我們來介紹一下如何利用Katalon進行API測試。
不過,如果一開始的專案不是選"API",會看不到"Add Web Service Keywords",不過,還是可以在Add裡找到API所需要的指令
利用Katalon提供的API來測試。
先設定web request,命名為UserGet,URL:
https://sample-web-service-aut.herokuapp.com/api/users/1
應該收到的內容:
<User>
<id>1</id>
<username>John Smith</username>
<password>123</password>
<gender>MALE</gender>
<age>25</age>
<avatar/>
</User>
Test case的腳本:
import static com.kms.katalon.core.checkpoint.CheckpointFactory.findCheckpoint
import static com.kms.katalon.core.testcase.TestCaseFactory.findTestCase
import static com.kms.katalon.core.testdata.TestDataFactory.findTestData
import static com.kms.katalon.core.testobject.ObjectRepository.findTestObject
import static com.kms.katalon.core.testobject.ObjectRepository.findWindowsObject
import com.kms.katalon.core.checkpoint.Checkpoint as Checkpoint
import com.kms.katalon.core.cucumber.keyword.CucumberBuiltinKeywords as CucumberKW
import com.kms.katalon.core.mobile.keyword.MobileBuiltInKeywords as Mobile
import com.kms.katalon.core.model.FailureHandling as FailureHandling
import com.kms.katalon.core.testcase.TestCase as TestCase
import com.kms.katalon.core.testdata.TestData as TestData
import com.kms.katalon.core.testobject.TestObject as TestObject
import com.kms.katalon.core.webservice.keyword.WSBuiltInKeywords as WS
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI
import com.kms.katalon.core.windows.keyword.WindowsBuiltinKeywords as Windows
import internal.GlobalVariable as GlobalVariable
response = WS.sendRequest(findTestObject('UserGet'))
WS.verifyResponseStatusCode(response, 200)
WS.verifyElementPropertyValue(response, 'id', 1)
WS.verifyElementPropertyValue(response, 'username', 'John Smith')
WS.verifyElementPropertyValue(response, 'password', '123')
如果想要有自己的API,可以使用Airtable。Airtable是個雲端資料庫,跟一般的關聯式資料庫不同的是,airtable支援資料表間的多對多關係,使用Airtable還有一個好處是,Airtable提供RESTful web service的API。
新增web request,命名為: Airtable Table1,並設定web request的URL:
https://api.airtable.com/v0/appLNbQOB0WL1ZeDD/Table%201?maxRecords=30&view=Grid%20view
HTTP header:
Authorization Bearer KeyXXXXXXXXXX
老師寫的script
import static com.kms.katalon.core.testobject.ObjectRepository.findTestObject
import com.kms.katalon.core.webservice.keyword.WSBuiltInKeywords as WS
response = WS.sendRequest(findTestObject('Airtable Table1'))
WS.verifyResponseStatusCode(response, 200)
WS.verifyElementPropertyValue(response, 'records[0].fields.Name', 'Ben Wu')
WS.verifyElementPropertyValue(response, 'records[1].fields.Name', 'Ana')
WS.verifyElementPropertyValue(response, 'records[0].fields.City', '台北市')
WS.verifyElementPropertyValue(response, 'records[1].fields.City', '新北市')
產生一個Web Service Request
產生一個TestCase
新增Web Service Keyword: Send Request
選擇剛剛設定好的Web Service Request
設定輸出
確認內容
執行測試
Web UI Testing
Getting Started with Web Testing
Create test cases
Validate test cases
Plan test cases
Execute test suites
Integration
要先安裝Katalon Recorder,WebUI才有辦法開啟瀏覽器
產生一個新的專案 (或利用Sample Project產生一個新專案)
如果要進行Web Testing,請選擇Web
可以利用Sample Project產生一個新專案,就會有一些範例可以參考
這些範例都在github上: github.com/katalon-studio-samples
新增一個Test case
如果已經安裝了Katalon Recorder,可以直接啟動Web Recorder (Record Web Utility)
Web Recorder會給一個預設的URL ( https://katalon-demo-cura.herokuapp.com/ ),可以直接使用
建議先看一下這篇文章的步驟: Creating test cases using Record & Playback (文章內容是針對舊的版本)
不過,可以把三個部分一併完成,不需要重複三次,因為,在錄製的同時,最好先把物件的名稱就改好,另外,所有需要確認的物件,可以不用在錄製時去指定如何確認,但是,最好在錄製的時候都要點選一下,否則,這些物件沒有被記錄下來,事後要加上確認的時候,就沒有物件可以選,就必須要再去該頁錄製一次。
SELECTION METHOD的部分"Basic"似乎已經改成"Attributes",不過,這部分可以忽略
Add Validation Point的部分也改了,按Add之後,要選擇Web UI Keyword,再挑選Verify Element Exists,Object就挑選要確認的物件,並將Input的值改為30秒,也就是browser會等30秒,如果,30秒內沒有呈現挑選的物件,這個測試步驟就會失敗
** 尚未更新 ** 請pull老師的Katalon專案 https://gitlab.com/benwu/katalon.git
可參考老師錄好的script
import static com.kms.katalon.core.testobject.ObjectRepository.findTestObject
import com.kms.katalon.core.testobject.TestObject as TestObject
import com.kms.katalon.core.webui.keyword.WebUiBuiltInKeywords as WebUI
WebUI.openBrowser('https://katalon-demo-cura.herokuapp.com/')
WebUI.click(findTestObject('Object Repository/Page_CURA Healthcare Service/a_Make Appointment'))
WebUI.setText(findTestObject('Object Repository/Page_CURA Healthcare Service/input_Username_username'), 'John Doe')
WebUI.setEncryptedText(findTestObject('Object Repository/Page_CURA Healthcare Service/input_Password_password'), 'g3/DOGG74jC3Flrr3yH+3D/yKbOqqUNM')
WebUI.click(findTestObject('Object Repository/Page_CURA Healthcare Service/button_Login'))
WebUI.selectOptionByValue(findTestObject('Object Repository/Page_CURA Healthcare Service/select_Tokyo CURA Healthcare Center _5b4107'),
'Hongkong CURA Healthcare Center', true)
WebUI.click(findTestObject('Object Repository/Page_CURA Healthcare Service/input_Apply for hospital readmission_hospit_63901f'))
WebUI.click(findTestObject('Object Repository/Page_CURA Healthcare Service/input_Medicaid_programs'))
WebUI.click(findTestObject('Object Repository/Page_CURA Healthcare Service/span_Visit Date (Required)_glyphicon glyphi_cada34'))
WebUI.click(findTestObject('Object Repository/Page_CURA Healthcare Service/td_20'))
WebUI.setText(findTestObject('Object Repository/Page_CURA Healthcare Service/textarea_Comment_comment'), 'A regular health exam')
WebUI.click(findTestObject('Object Repository/Page_CURA Healthcare Service/button_Book Appointment'))
WebUI.verifyElementText(findTestObject('Object Repository/Page_CURA Healthcare Service/p_Hongkong CURA Healthcare Center'),
'Hongkong CURA Healthcare Center')
WebUI.verifyElementText(findTestObject('Object Repository/Page_CURA Healthcare Service/p_Yes'),'Yes')
WebUI.verifyElementText(findTestObject('Object Repository/Page_CURA Healthcare Service/p_Medicaid'),'Medicaid')
WebUI.verifyElementText(findTestObject('Object Repository/Page_CURA Healthcare Service/p_20052020'),'20/05/2020')
WebUI.verifyElementText(findTestObject('Object Repository/Page_CURA Healthcare Service/p_A regular health exam'), 'A regular health exam')
WebUI.click(findTestObject('Object Repository/Page_CURA Healthcare Service/a_Go to Homepage'))
WebUI.closeBrowser()
Web UI測試
利用Spy Web產生Test Object
產生Test Case
或利用Record Web產生Test Object及Test Case
執行測試
Mobile Testing
參考資料
TDD 則是「先寫測試再開發程式」。沒有程式要怎麼寫測試呢?除了有些工具可以讓你寫測試時,一邊幫你產生空的類別與方法外(如 Eclipse 撰寫 Java 時就有類似的功能),一般來說也可以直接假設你已經撰寫好了程式,先揣摩如果已經寫好了這個程式該要如何使用。
BDD 則是比起 TDD 更進一步,除了在實作前先寫測試外,而在測試前還要先寫規格,但是這份規格並不是單純的敘述軟體的功能,而是這份規格,是一份「可以執行的規格」。
誤解一:TDD是一種測試方法
TDD是一種「開發方法」,藉由先定義規格,再撰寫程式的方式來開發軟體。
誤解二:TDD很神
誤解三:TDD可治百病
TDD & DDD
My version of the FDIC Bank Data Developer Portal is a Loopback based Node.js application generated from a Swagger, or Open API Specification, file with 2 endpoints to access FDIC Banks data.
When I submit new code to Github, the code is automatically tested using Postman and Newman using a Scripted Pipeline with Groovy in Jenkins to set up the Continuous Integration (CI).
Your Test Should Verify If The Code Solves The Problem, Not If It Runs
Practical TDD (.net & XUnit)
I Tried Kent Becks’ Test && Commit || Revert. Here’s what I learnt
3 easy ways to write cleaner unit tests
One test, one function.
Good test names.
AAA: Arrange, Act, Assert
Why Test Driven Development (TDD) is the Best Way for Robust Coding.
You Fix Bad Code Without Breaking Anything
TDD Enforces Documentation.
TDD Helps in Better Design
And Lastly TDD Follows Best Coding Practices
TDD promotes good coding principles including DRY, KISS, YAGNI and SOLID.
RIP TDD — Or are we just thinking about it wrong?
Thinking in test driven development is like how a mechanic would test a car. The engineer designs it, the factory machines build it, but your local mechanic is the one that goes through a series of tests to check if the car is road worthy.
This is basically TDD — testing to see if your software produces an expected, higher level and clearly specified expected result.
Test Driven Development Is Dumb. Fight Me.
The idea behind TDD is that you’re able to write a test because you sit down and think about things first.
Javascript
Jest (詳參: A Beginner’s Guide to Jest Testing)
寫jest的測試,如:src/utils/friday.test.js
import isItFriday from './friday';
describe('Today is or is not Friday',() => {
test('Sunday is "Nope"', () => {
expect(isItFriday('Sunday')==='Nope');
});
test('Friday is "TGIF"', () => {
expect(isItFriday('Friday')==='TGIF');
});
});
也可以這樣寫:
import isItFriday from './friday';
describe.each`
day | answer
${'Friday'} | ${'TGIF'}
${'Sunday'} | ${'Nope'}
${'anything else'} | ${'Nope'}
`('$day + $answer', ({day,answer}) => {
test(`Sunday is ${answer}`, () => {
expect(isItFriday(day)===answer);
});
});
src/utils/friday.js
export default (today) => {
if (today === "Friday") {
return "TGIF";
} else {
return "Nope";
}};
看起來,測試要比被測試的程式碼還要長。當然,我們不會測試每一個簡單的功能,而是針對一些比較複雜的功能來寫測試,確保程式碼的正確性。而且,當新功能完成時,可以執行所有的測試,確保新功能沒有影響既有的所有功能。所以,當系統越長越大的時候,就可以感受到測試自動化的好處了。
Jest (詳參: A Beginner’s Guide to Jest Testing)
//Define the Calc constructor method
function Calc(firstNumber, secondNumber) {
//Define the two properties
this.firstNumber = firstNumber;
this.secondNumber = secondNumber;
//Define your add method that will return the result of adding two properties.
this.add = function() {
return this.firstNumber + this.secondNumber;
}
}
//Assign variables to an instance of Calc with 5 and 10, and also numbers switched around.
const numberToAdd = new Calc(5, 10);
const numberToAddReversed = new Calc(10, 5);
//Group your add test
describe('Check if the add method works as inteded', () => {
test('Check is 5 + 10 is falsey', () => {
expect(numberToAdd.add() === 50).toBeFalsy();
});
test('Check is 5 + 10 is truthy', () => {
expect(numberToAdd.add() === 15).toBeTruthy();
});
test('Check if numbers switched around is truthy', () => {
expect(numberToAddReversed.add()).toEqual(15);
});
})
如果要測試react,enzyme提供了一些方法來測試
先安裝相關的套件 (假設已經安裝jest)
npm install enzyme enzyme-adapter-react-16
src/setupTests.js
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
src/App.js
import React, { Component } from 'react';
//import logo from './logo.svg';
import './App.css';
import Calculator from './Calculator/Calculator';
class App extends Component {
render() {
return (
<div>
<h2>React Calculator</h2>
<Calculator />
</div>
);
}
}
export default App;
src/App.css
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 40vmin;
pointer-events: none;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
src/App.test.js 測試元件存在及測試內容,shallow是只有產生App,而mount則是產生及App所包含的元件。
import React from 'react';
import App from './App';
import {shallow, mount} from 'enzyme'
describe('Calculator component', () => {
it('renders Calculator', () => {
const component = shallow(
<App />
)
expect(component.exists('Calculator')).toBe(true)
})
})
describe('App: ', () => {
const wrapper = mount(<App/>);
it('It shows,"React Calculator"', () => {
expect(wrapper.text()==='React Calculator');
})
});
參考資料
An Overview of JavaScript Testing in 2019
Unit and Integration Tests Framework
Jest, Mocha, AVA, Tape
In short, if you want to “just get started” or are looking for a fast framework for large projects, you can’t go wrong with Jest.
Functional Testing (UI Test)Tools
TestCafe, Cypress.io, Nightwatch.js, WebdriverIO
In short, if you want to “just get started” with a simple to set-up cross-browser all-in-one tool, go with TestCafe.
Test Environment(Mocha、Jasmine、Jest、Karma)
Test structure (Mocha、Jasmine、Jest、Cucumber)
Assertion(Chai、Jasmine、Jest、Unexpected)
Creation, display, and watch test result(Mocha、Jasmine、Jest、Karma)