TDD is Awesome You Should Try It! (Tutorial With Example)

透過測試驅動 AI 開發流程

前言

近期嘗試各種 AI 開發工具(Cursor、Claude CLI、Gemini Code) 越來越篤定測試和 AI 開發流程絕對是相輔相成的存在。本文摸索怎麼樣的流程能夠最大化與 AI 配合產出。

小試身手

透過 Vite🔗 創建一個簡單的 Vue 單頁應用,並請 AI Agent 替預設範例元件 HelloWorld.vue 撰寫測試:Write test for HelloWorld using @vue/test-utils

<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>()
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import HelloWorld from './HelloWorld.vue'
describe('HelloWorld', () => {
it('renders the message prop correctly', () => {
const message = 'Hello Vue!'
const wrapper = mount(HelloWorld, {
props: { msg: message }
})
expect(wrapper.find('h1').text()).toBe(message)
})
it('displays initial count as 0', () => {
const wrapper = mount(HelloWorld, {
props: { msg: 'Test Message' }
})
expect(wrapper.find('button').text()).toBe('count is 0')
})
it('increments count when button is clicked', async () => {
const wrapper = mount(HelloWorld, {
props: { msg: 'Test Message' }
})
const button = wrapper.find('button')
// Click the button
await button.trigger('click')
// Check if count increased to 1
expect(button.text()).toBe('count is 1')
// Click again
await button.trigger('click')
// Check if count increased to 2
expect(button.text()).toBe('count is 2')
})
it('renders the edit instruction text', () => {
const wrapper = mount(HelloWorld, {
props: { msg: 'Test Message' }
})
expect(wrapper.text()).toContain('Edit')
expect(wrapper.text()).toContain('components/HelloWorld.vue')
expect(wrapper.text()).toContain('to test HMR')
})
it('renders the create-vue link with correct attributes', () => {
const wrapper = mount(HelloWorld, {
props: { msg: 'Test Message' }
})
const createVueLink = wrapper.find('a[href="https://vuejs.org/guide/quick-start.html#local"]')
expect(createVueLink.exists()).toBe(true)
expect(createVueLink.text()).toBe('create-vue')
expect(createVueLink.attributes('target')).toBe('_blank')
})
it('renders the Vue Docs link with correct attributes', () => {
const wrapper = mount(HelloWorld, {
props: { msg: 'Test Message' }
})
const vueDocsLink = wrapper.find('a[href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"]')
expect(vueDocsLink.exists()).toBe(true)
expect(vueDocsLink.text()).toBe('Vue Docs Scaling up Guide')
expect(vueDocsLink.attributes('target')).toBe('_blank')
})
it('renders the read-the-docs paragraph with correct class', () => {
const wrapper = mount(HelloWorld, {
props: { msg: 'Test Message' }
})
const readTheDocs = wrapper.find('p.read-the-docs')
expect(readTheDocs.exists()).toBe(true)
expect(readTheDocs.text()).toBe('Click on the Vite and Vue logos to learn more')
})
it('has the correct CSS class for read-the-docs', () => {
const wrapper = mount(HelloWorld, {
props: { msg: 'Test Message' }
})
const readTheDocs = wrapper.find('.read-the-docs')
expect(readTheDocs.exists()).toBe(true)
})
it('renders all expected elements', () => {
const wrapper = mount(HelloWorld, {
props: { msg: 'Test Message' }
})
// Check for main elements
expect(wrapper.find('h1').exists()).toBe(true)
expect(wrapper.find('.card').exists()).toBe(true)
expect(wrapper.find('button').exists()).toBe(true)
expect(wrapper.find('code').exists()).toBe(true)
// Check for all paragraphs
const paragraphs = wrapper.findAll('p')
expect(paragraphs).toHaveLength(4)
})
})

可以發現僅需片刻 AI 就能根據文件配置相關套件與根據現有完成所有測試案例,雖然有些許不足的地方但大致能用,AI 開發問題不在於熟悉套件的操作或是撰寫程式的速度,而是「思考周全」與「傳達清楚目的」。

經驗豐富的測試者會知道如何撰寫更穩定有價值的測試:

  1. 撰寫有意義的測試案例
  2. 不要針對程式的私有狀態或方法去測試
  3. 透過自製的屬性搭配選取器如: data-test="add-button" 應對測試的變化

很多最佳實踐都可以在官方文件中找到,但不告知 AI 它就不會知道。所以務必針對 AI 提供充分的上下文,可以是官方提倡的最佳實踐、經驗豐富開發者的建議或常犯的錯誤。

測試案例驅動 AI 開發

既然 AI 測試寫得又快又好,那透過我們自己描述測試案例作為開發文件不是很棒的做法嗎?