用 AI 幫忙寫程式,更需要寫測試。因為你不知道接下來 AI 輔助生成的程式碼,會把舊有的程式碼改成怎樣,會不會改得面目全非?
先前的經驗是被 AI 改過好幾次,對每次的結果都已經開始半信半疑,會逐行檢查 AI 修改的結果。而且我們開發者都有可能把「程式改壞」了,更別說 AI 會不會一不小心就改壞了。
所以說,基本功能雛形完成得差不多,可以預想接下來功能與業務邏輯越來越複雜,我們需要自動化測試的幫助,來把寫好的功能「保護」起來,這樣後續的功能才能更安心地開發,放心地與 AI 協作。
因為想寫先功能,先請 AI 幫我打造雛形,做出能看得見、能摸得著的初始產品,快速試用快速迭代。等確定功能與配置都修改到滿意之後,再來加上測試,才不會說試用了覺得不太對,程式碼一大部分打掉重練,先前寫的測試都白寫了…。
*此部分當然不是說 TDD(測試驅動開發) 不需要或不好用,而是今天的案例比較像是「需求驅動開發」,先寫功能再來做測試,比較合適我自己的 Side Project 開發。
聽說 AI 很厲害可以直接生成測試項目,那應該有辦法參考檔案,直接寫出我要的測試吧?
我直接下了「@AccountingCalculatorMain.tsx
as ref, make unit test」
AI 咻咻咻的就產出了以下程式碼的測試,提供其中兩個供大家參考,體會一下。
it('renders two components with total amount as $0', () => {
render(<App />);
const totalElements = screen.getAllByText('$0');
expect(totalElements).toHaveLength(2);
});
it('allows input of numbers and updates the display', () => {
render(<App />);
fireEvent.click(screen.getByText('1'));
fireEvent.click(screen.getByText('2'));
fireEvent.click(screen.getByText('3'));
expect(screen.getByText('$123')).toBeInTheDocument();
});
//...
哇! 看起來有測到我想測試的,像是按鍵行為的測試,基本渲染的測試…等,都有測到耶!AI 自己幫我生成了這些測試項目:
等 AI 咻咻咻產生了所有測試之後,興高采烈地按下了 test
的按鈕。
幫我寫了 7 個測試,有 3 個是壞的,搞毛啊!
果然不能直接信任 AI 寫的測試,定睛一看錯誤的原因,是一些「比較低級」的錯誤,在於裏面的 screen 相關方法有點問題,以下條列幾個這次 AI 給的測試所發現的問題:
screen.getByText()
取得,這方法適用於「找到一個符合條件的組件」,但有些組件是「多個存在」的,所以就直接報錯。Unable to find an element with the text: 刪除
,是因為screen.getByText(’刪除’)
報錯,說是找不到這樣的組件。就是以上這 2 個問題而已,看起來不是什麼大問題,等等來調整一下 :)
首先是 screen.getByText()
發生的錯誤,測試原文是這樣的
test('renders the initial total amount as $0', () => {
render(<App />);
const totalElement = screen.getByText('$0');
expect(totalElement).toBeInTheDocument();
});
乍看之下沒什麼問題,但對比一下畫面以及回想一下程式邏輯,在「尚未輸入」的前提之下,應該會有「2 個」$0 才對,如以下畫面所示
所以這個測試項目,應該要改成「可以找到 2 個 $0 的組件」,一樣用指令告訴 AI 應該這樣修改:
測驗目標改為:可以找到 2 個 $0 的組件
AI 幫我改成這樣:
test('renders two components with total amount as $0', () => {
render(<App />);
const totalElements = screen.getAllByText('$0');
expect(totalElements).toHaveLength(2);
});
嗯…看起來是對了,測試也通過沒問題 👍。至於另外一個 screen.getByText()
出錯的測試,依樣畫葫蘆修改一番即可。
另一個錯誤,光看測試好像沒什麼問題,但…為何會出錯呢?
test('deletes item from history when delete button is clicked', async () => {
render(<App />);
fireEvent.click(screen.getByText('1'));
fireEvent.click(screen.getByText('0'));
fireEvent.click(screen.getByText('OK'));
fireEvent.click(screen.getByText('飲食'));
const deleteButton = screen.getByText('刪除');
fireEvent.click(deleteButton);
expect(screen.queryByText('飲食')).not.toBeInTheDocument();
expect(screen.queryByText('$10')).not.toBeInTheDocument();
});
再次操作一下實際的記帳 App 看看,發現原來是 AI 在「飲食」這裡 click 之後,並沒有再次按下「OK」,因此沒有將這筆帳目成功添加,也難怪「刪除」不會出現在畫面上。
這個就不用勞煩 AI 修改了,自個兒把上面的fireEvent.click(screen.getByText('OK'))
複製下來便行!再次跑了測試,嗯! 統統通過,可喜可賀 🥂
雖然 AI 產出的測試項目姑且算是滿意,已經涵蓋許多記帳 App 的主要功能,但這就像是老闆直接拿著需求,請工程師「通靈」寫出程式碼一樣,終究是不太可靠,且不一定測試有覆蓋到真正重要的邏輯。
接著會介紹如何精準撰寫「最需要測」的測試,而不是任憑 AI 猜測所擺佈,尤其像是比較重要的「編輯、列表數量驗證…」等,都還沒有測到,接下來會再補上指令,將測試涵蓋主要使用者故事更為完整囉!