陳Ken
一位熱愛爬山的前端工程師
最流行的是
「他人遺留下的原始碼」
與
「舊版本的軟體中遺留下來的代碼」
Code without Test
規格書
Readme 文件
具可讀性、良好的程式碼
有意義的註解
改不動?怕改壞?
已加入測試的程式碼
知識的獲取、創造與保留
知識的分類
Problem vs Solution
知識的獲取
Problem vs Solution
商業領域的理解 與 軟體開發與商業領域間的探索
知識的保存
Problem vs Solution
可被執行的 Spec 與 貼近商業需求且乾淨的程式碼
知識獲取了,但沒有良好保存下來
知識的獲取與保留
對用戶的需求滿足了,知識獲取了,但沒有很好保存
越後期改動程式碼的時間成本越高
軟體架構層面
軟體測試層面
組織架構層面
高內聚、低耦合
分開放,要剪紙時超難找啊...
做什麼事都要用到它...
頂多零件被兒子的車車用完
兒子跑去拆你的車叫做 side effect
有一個服務是,當今天是情人節時...
祝福使用者情人節快樂
實例化的需求 - 減少溝通成本
測試讀起來可以貼近商業需求 - 減少溝通成本、保存商業知識
(消滅遺留代碼)
撰寫測試 (描述需求)- 助於需求討論、將需求釐清明確
開發
重構開發 (解除依賴)
重構測試 - 更妥善保存知識,隱藏無關的資訊、凸顯意圖
(增進可讀性)
流程
增添新功能時,不確定有沒有 bug?
可透過添加測試來驗證、確保
2/14 情人節快樂?
2/12 情人節快樂?
3/14 情人節快樂?
4/14 情人節快樂?
仍須透過 Code Review 把關
1. 避免低級錯誤
2. 驗證想的跟寫的是否一樣
(大文豪 Ken 胡適:我手寫我口)
但想的 & 寫的一樣...也不一定代表正確
讓測試更好讀,更自然的英文語意
givenToday('2021-02-14');
shouldBeValentinesDay(holiday.checkValentinesDay());
凸顯意圖、隱藏不必要細節
測試是在描述需求的情境
討論好團隊的命名規範
辨識依賴
將其抽取成為方法
在測試中覆蓋掉
依賴會影響可測程度,但又不是受測重點
設計測試案例
一組 if else (分支)
就有正面、反面的表述情境
2/14 情人節快樂?
2/12 情人節快樂?
3/14 情人節快樂?
4/14 情人節快樂?
const Card = ({ name, url, content, status }) => {
const { derivedStatus } = useCardStatus(status);
return (
<div>
<h1>{name}</h1>
{derivedStatus === "online" ? <SvgOnline /> : <SvgOffline />}
<img src={url} />
<p>{content}</p>
<button onClick={() => openModal(name)}>See more</button>
</div>
);
};
const Card = ({ name, url, content, status }) => {
const { derivedStatus } = useCardStatus(status);
return (
<div>
<h1>{name}</h1>
{derivedStatus === "online" ? <SvgOnline /> : <SvgOffline />}
<img src={url} />
<p>{content}</p>
<button onClick={() => openModal(name)}>See more</button>
</div>
);
};
這個 hook 比較偏向為了Card 封裝隱藏了的實作細節
有可能淪為『過度指定』,測了使用者不需知道的細節
風險:可能重構功能後正確,但測試卻壞了
重構 hook 時,測試必須修改,比如 derivedStatus: "online" | "offline" 改名為 isOnline: boolean
import { render } from "@testing-library/react";
const givenStatus = (status) => ({
name: "Ken",
content: "Not important",
url: "",
status,
});
describe("Card can show different between online and offline", () => {
it("should render online correctly", () => {
const { asFragment } = render(<Card {...givenStatus("online")} />);
expect(asFragment()).toMatchSnapshot();
});
it("should renders offline correctly", () => {
const { asFragment } = render(<Card {...givenStatus("offline")} />);
expect(asFragment()).toMatchSnapshot();
});
});
測試沒叫,代表在 Card 內的重構是正確的
重構 hook 時,測試不用修改,比如 derivedStatus: "online" | "offline" 改名為 isOnline: boolean
Test Failed 才去檢查重構哪裡有問題
const useGetCardData = (group) => {
const [data, setData] = useState([]);
useEffect(() => {
fetch(`https://some-fake-url/${group}`, { method: "GET" }).then((res) => {
setData(res.json());
});
}, [group]);
return data;
};
const CardList = (group) => {
const data = useGetCardData(group);
return (
<ul>
<GoogleMapView />
{data.map((person) => (
<Card key={person.id} {...person} />
))}
</ul>
);
};
這個 hook 比較偏向單純要資料
我只要確保資料會長得像 data 這樣,就 Mock 它
專注測試元件行為
測 hook 等於在測 call api, 驗資料
辨識哪些是依賴,Mock 起來
某些ChildComponent -> <GoogleMap /> third party
fetch
some hooks
redux / context
設定一個目標:測試覆蓋率 70%
設定一個目標:測試覆蓋率 70%
今天時間非常緊迫、很短
mainBusiness 非常難測
你會怎麼達成這個目標?
Line 1-70 非常好測,寫完覆蓋率就 70 % 了
Line 71-100 非常難寫,寫完覆蓋率只有 30 % 了
如果覆蓋率被作為一種目標
可能導致我們先追求滿足覆蓋率
阿不然我再多寫幾行防呆好了
增加覆蓋率
這段 code 最重要、最有價值的地方是?
哪裡最不允許錯誤
這 30 行,代表的是整個系統的 80 % 重要性
不怕未來成為 legacy code 不敢重構,或是有這段測試綠燈能讓你更安心
風險、$、商譽
寫測試就像買保險
軟體的開發 -> 知識的獲取、保存
軟體的品質 -> 1. 軟體架構 2. 測試 3. 組織層面(TBD ?)
如何寫單元測試 ->
1. 辨識受測對象 or 依賴 2. 抽取方法 3. 複寫此方法
如何寫好單元測試 -> 凸顯意圖,隱藏非必要細節
讓測試讀起來像使用者的使用情境
覆蓋率 -> 每行 code 不等價、哪些最有價值?
By 陳Ken