前にも別のモデルでやってる
https://zenn.dev/sterashima78/articles/7876bd00aa2115
https://zenn.dev/sterashima78/articles/8bca47a054fabd
ただ o1-preview は、やり取りを重ねるよりも一発で終わらせるほうがいいらしいので、最終的なテスト全体を渡すようにした。
情報の提示方法が異なると当然結果も変わるので、 gpt-4o でも同様なことを試した。
プロンプトは以下。
基本的に最初にやったときと同じ。ペアプロではないのでその部分の調整をしている
- 私がテストコードを提示するのでそのテストケースをパスする最小限の実装をしてください- Vue.js のバージョン 3 と Typescript で実装を行ってください- コードのみを示してくださいコードの解説などは必要ありません- スタイリングは必要ありません- テストケースに失敗したらその内容をチャットで送信するので最小限のコードの修正をしてください- テストのコードには vitest を利用しています jest と互換がある API なので、vitest のことを知らなければ jest を使っていると思ってください- テストの記述には testing-library/vue を利用しますimport { render, waitFor } from '@testing-library/vue';import { userEvent } from '@testing-library/user-event';import App from './App.vue';describe('Test for App', () => { let canvas: ReturnType<typeof render>; beforeEach(() => { canvas = render(App); }); test('test', () => { expect(canvas).not.toBeNull(); }); test('入力したテキストに応じてアイテムができる', async () => { const textbox = await canvas.findByRole<HTMLInputElement>('textbox', { name: 'タイトル', }); textbox.focus(); userEvent.keyboard('item1'); await waitFor(() => { expect(textbox.value).toBe('item1'); }); userEvent.keyboard('{enter}'); await waitFor(() => { expect(textbox.value).toBe(''); }); await canvas.findByRole('listitem', { name: 'item1', }); await canvas.findByText('1 item left'); }); test('アイテムを削除できる', async () => { const textbox = await canvas.findByRole<HTMLInputElement>('textbox', { name: 'タイトル', }); textbox.focus(); userEvent.keyboard('item1'); await waitFor(() => { expect(textbox.value).toBe('item1'); }); userEvent.keyboard('{enter}'); await waitFor(() => { expect(textbox.value).toBe(''); }); const item = await canvas.findByRole('listitem', { name: 'item1', }); const btn = await canvas.findByRole('button', { name: 'item1 を 削除する', }); userEvent.click(btn); await waitFor(() => { expect(canvas.queryByRole('listitem', { name: 'item1' })).toBeNull(); }); }); test('アイテムを完了できる', async () => { const textbox = await canvas.findByRole<HTMLInputElement>('textbox', { name: 'タイトル', }); textbox.focus(); userEvent.keyboard('item1'); await waitFor(() => { expect(textbox.value).toBe('item1'); }); userEvent.keyboard('{enter}'); await waitFor(() => { expect(textbox.value).toBe(''); }); await canvas.findByRole('listitem', { name: 'item1', }); await canvas.findByText('1 item left'); const checkbox = await canvas.findByRole('checkbox', { name: 'item1 を完了する', }); userEvent.click(checkbox); await canvas.findByText('0 items left'); }); test('表示項目を切り替えられる', async () => { const textbox = await canvas.findByRole<HTMLInputElement>('textbox', { name: 'タイトル', }); textbox.focus(); userEvent.keyboard('item1'); await waitFor(() => { expect(textbox.value).toBe('item1'); }); userEvent.keyboard('{enter}'); await waitFor(() => { expect(textbox.value).toBe(''); }); userEvent.keyboard('item2'); await waitFor(() => { expect(textbox.value).toBe('item2'); }); userEvent.keyboard('{enter}'); await waitFor(() => { expect(textbox.value).toBe(''); }); expect(await canvas.findAllByRole('listitem')).toHaveLength(2); await canvas.findByText('2 items left'); const checkbox = await canvas.findByRole('checkbox', { name: 'item1 を完了する', }); userEvent.click(checkbox); await canvas.findByText('1 item left'); // Active userEvent.click(await canvas.findByRole('button', { name: 'Active' })); await waitFor(async () => { expect(await canvas.findAllByRole('listitem')).toHaveLength(1); await canvas.findByRole('listitem', { name: 'item2', }); }); // All userEvent.click(await canvas.findByRole('button', { name: 'All' })); await waitFor(async () => { expect(await canvas.findAllByRole('listitem')).toHaveLength(2); }); // Completed userEvent.click(await canvas.findByRole('button', { name: 'Completed' })); await waitFor(async () => { expect(await canvas.findAllByRole('listitem')).toHaveLength(1); await canvas.findByRole('listitem', { name: 'item1', }); }); }); test('完了済み項目を削除できる', async () => { const textbox = await canvas.findByRole<HTMLInputElement>('textbox', { name: 'タイトル', }); textbox.focus(); // add item1 userEvent.keyboard('item1'); await waitFor(() => { expect(textbox.value).toBe('item1'); }); userEvent.keyboard('{enter}'); await waitFor(() => { expect(textbox.value).toBe(''); }); // add item2 userEvent.keyboard('item2'); await waitFor(() => { expect(textbox.value).toBe('item2'); }); userEvent.keyboard('{enter}'); await waitFor(() => { expect(textbox.value).toBe(''); }); // add item3 userEvent.keyboard('item3'); await waitFor(() => { expect(textbox.value).toBe('item3'); }); userEvent.keyboard('{enter}'); await waitFor(() => { expect(textbox.value).toBe(''); }); expect(await canvas.findAllByRole('listitem')).toHaveLength(3); await canvas.findByText('3 items left'); userEvent.click( await canvas.findByRole('checkbox', { name: 'item1 を完了する', }) ); userEvent.click( await canvas.findByRole('checkbox', { name: 'item3 を完了する', }) ); await canvas.findByText('1 item left'); // Completed userEvent.click(await canvas.findByRole('button', { name: 'Completed' })); await waitFor(async () => { expect(await canvas.findAllByRole('listitem')).toHaveLength(2); }); // Active userEvent.click(await canvas.findByRole('button', { name: 'Active' })); await waitFor(async () => { expect(await canvas.findAllByRole('listitem')).toHaveLength(1); }); // All userEvent.click(await canvas.findByRole('button', { name: 'All' })); await waitFor(async () => { expect(await canvas.findAllByRole('listitem')).toHaveLength(3); }); // Clear Complete userEvent.click( await canvas.findByRole('button', { name: 'Clear Completed' }) ); await waitFor(async () => { expect(await canvas.findAllByRole('listitem')).toHaveLength(1); await canvas.findByRole('listitem', { name: 'item2' }); }); }); test('項目のテキストを修正できる', async () => { const textbox = await canvas.findByRole<HTMLInputElement>('textbox', { name: 'タイトル', }); textbox.focus(); // add item1 userEvent.keyboard('item1'); await waitFor(() => { expect(textbox.value).toBe('item1'); }); userEvent.keyboard('{enter}'); await waitFor(() => { expect(textbox.value).toBe(''); }); const item = await canvas.findByText('item1'); userEvent.dblClick(item); await waitFor(async () => { expect( await canvas.findByRole('textbox', { name: 'item1 を編集', }) ).toBe(document.activeElement); }); await userEvent.keyboard( '{backspace}{backspace}{backspace}{backspace}{backspace}new item{enter}' ); await canvas.findByRole('listitem', { name: 'new item', }); });});以下のようなコードを返した。
<template><div><inputv-model="title"placeholder="タイトル"@keydown.enter="addItem"aria-label="タイトル"/><ul><liv-for="item in filteredItems":key="item.id"><inputtype="checkbox":checked="item.completed"@change="toggleComplete(item.id)":aria-label="`${item.text} を完了する`"/><spanv-if="!item.editing">{{ item.text }}</span><inputv-elsev-model="item.text"@keydown.enter="saveItem(item.id)"aria-label="item を編集"/><button@click="deleteItem(item.id)":aria-label="`${item.text} を 削除する`"> 削除</button></li></ul><div>{{ remainingItems }} item left</div><button@click="filter = 'all'">All</button><button@click="filter = 'active'">Active</button><button@click="filter = 'completed'">Completed</button><button@click="clearCompleted">Clear Completed</button></div></template><scriptlang="ts">import{ defineComponent, ref, computed}from'vue';interfaceItem{id: number;text: string;completed: boolean;editing: boolean;}exportdefaultdefineComponent({setup(){const title=ref('');const items= ref<Item[]>([]);const filter=ref('all');constaddItem=()=>{if(title.value.trim()){ items.value.push({id:Date.now(),text: title.value,completed:false,editing:false,}); title.value='';}};constdeleteItem=(id: number)=>{ items.value= items.value.filter((item)=> item.id!== id);};consttoggleComplete=(id: number)=>{const item= items.value.find((item)=> item.id=== id);if(item){ item.completed=!item.completed;}};constsaveItem=(id: number)=>{const item= items.value.find((item)=> item.id=== id);if(item){ item.editing=false;}};const filteredItems=computed(()=>{if(filter.value==='active'){return items.value.filter((item)=>!item.completed);}elseif(filter.value==='completed'){return items.value.filter((item)=> item.completed);}else{return items.value;}});const remainingItems=computed(()=>{return items.value.filter((item)=>!item.completed).length;});constclearCompleted=()=>{ items.value= items.value.filter((item)=>!item.completed);};return{ title, items, filter, addItem, deleteItem, toggleComplete, saveItem, filteredItems, remainingItems, clearCompleted,};},});</script>テスト結果は以下でパスしなかった
❯ src/index.test.ts (7) 6863ms ❯ Test for App (7) 6862ms ✓ test × 入力したテキストに応じてアイテムができる 1164ms × アイテムを削除できる 1083ms × アイテムを完了できる 1081ms × 表示項目を切り替えられる 1170ms × 完了済み項目を削除できる 1219ms × 項目のテキストを修正できる 1081ms以下のようなコードを返した
<template><inputv-model="newItem"@keyup.enter="addItem"role="textbox":aria-label="'タイトル'"/><ul><liv-for="item in filteredItems":key="item.id"role="listitem":aria-label="item.text"><inputtype="checkbox"v-model="item.completed":aria-label="`${item.text} を完了する`"/><spanv-if="editingId !== item.id"@dblclick="startEditing(item)"> {{ item.text }}</span><inputv-elsev-model="item.text"@keyup.enter="stopEditing"@blur="stopEditing"role="textbox":aria-label="`${item.text} を編集`"autofocus/><button@click="deleteItem(item)":aria-label="`${item.text} を 削除する`"> Delete</button></li></ul><div>{{ itemsLeftText }}</div><button@click="filter='all'":aria-label="'All'">All</button><button@click="filter='active'":aria-label="'Active'">Active</button><button@click="filter='completed'":aria-label="'Completed'">Completed</button><button@click="clearCompleted":aria-label="'Clear Completed'"> Clear Completed</button></template><scriptlang="ts">import{ defineComponent, reactive, computed, ref, nextTick}from'vue';exportdefaultdefineComponent({setup(){const newItem=ref('');const items= reactive<{id: number; text: string; completed: boolean}[]>([]);const filter=ref('all');const editingId= ref<number|null>(null);constaddItem=()=>{if(newItem.value.trim()){ items.push({id:Date.now(),text: newItem.value.trim(),completed:false,}); newItem.value='';}};constdeleteItem=(item: any)=>{const index= items.indexOf(item);if(index>-1){ items.splice(index,1);}};constclearCompleted=()=>{for(let i= items.length-1; i>=0; i--){if(items[i].completed){ items.splice(i,1);}}};const itemsLeft=computed(()=> items.filter((item)=>!item.completed).length);const itemsLeftText=computed(()=>{const count= itemsLeft.value;return`${count} item${count===1?'':'s'} left`;});const filteredItems=computed(()=>{if(filter.value==='active'){return items.filter((item)=>!item.completed);}elseif(filter.value==='completed'){return items.filter((item)=> item.completed);}else{return items;}});conststartEditing=(item: any)=>{ editingId.value= item.id;nextTick(()=>{const input=document.querySelector(`input[aria-label="${item.text} を編集"]`)asHTMLInputElement;if(input){ input.focus();}});};conststopEditing=()=>{ editingId.value=null;};return{ newItem, items, addItem, deleteItem, itemsLeftText, filteredItems, filter, clearCompleted, editingId, startEditing, stopEditing,};},});</script>テストはすべてパスした (!?)
✓ src/index.test.ts (7) 1245ms ✓ Test for App (7) 1244ms ✓ test ✓ 入力したテキストに応じてアイテムができる ✓ アイテムを削除できる ✓ アイテムを完了できる ✓ 表示項目を切り替えられる ✓ 完了済み項目を削除できる 308ms ✓ 項目のテキストを修正できるまた、o1-pewview は「考える」ということをするため、
思考時間と思考内容というものも表示される。
思考時間は 26秒で、思考内容は以下だった。
要求の確定ユーザーは、Vue.js 3とTypeScriptを使っているコンポーネントの実装を求めていて、指定されたテストケースを通過する最小限のコードを提供するようお願いしています。テストの理解canvasがnullでないことを確認しているど、基本的なチェックをしている。App.vueが有効なコンポーネントである限り、これが成立している。テキストの入力としてアイテムを追加し、削除するテストを進行中'タイトル'と名前が入力されたテキストボックスを見つけて、'item1'を追加し、リストアイテムに表示されて、削除ボタンをクリックしてDOMから削除されるか確認している。アイテムの取り扱いアイテムに削除ボタンがあると便利だね。「item1を削除する」という名前で。Checkboxを使って完了を示し、残りのアイテム数が更新されるんだ。アイテムの完了を示すCheckboxを使い、アイテムを完了できると「0 items left」というテキストが表示される。これまた、アイテム管理がスムーズになるね。表示項目の切り替え「item1」と「item2」を追加し、「item1」を完了にする。これにより、表示項目を切り替えることができ、効率的にアイテムを管理する方法を提案している。項目の操作ボタンを操作してビューを切り替え、未完了項目を表示し、完了済み項目を削除し、項目のテキストを編集する。To perform various actions in the todo list app, such as adding, displaying, completing, deleting, editing, filtering, and clearing items, a detailed understanding of Vue.js 3 with TypeScript is required.基本構造を定義するまずは、App.vueコンポーネントの基本的な構造を定義し始めている。次に、コンポーネントの内部に必要な要素やロジックを具体的に追加していく予定だ。ユーザビリティの向上リスト項目をダブルクリックすると、インライン編集できるようにする計画。アリアラベルを編集用に 'item1 を編集' に設定して、視覚障害者への配慮も忘れない技術的な対策をとっているところ。新しい編集者の追跡編集中の項目を追跡するために、データに editingId を追加し、テンプレートとスクリプトを更新している。項目の管理をサポートリストの項目を追加、削除、編集し、完了済みの項目を一括で削除する機能を提供している。フィルタリングやアラビラベルの設定も含まれている。賢い。
自分の要求を正確に記述することの重要性がますます高まっていることがわかる。
要求の記述方法としてテストコードはわかりやすいと言えるのでやはり有効な手段だろう。
反復的なタスクになりがちな要求やテストに関係する仕様の整理は GPT-4o 使って、それを o1-preview に突っ込んでテストコード作って、更にそれを o1-preview に突っ込んで実装を作るというのが見えてきているという感じがする。
当然それなりの規模のシステムやソフトウェアを一発で作るのは難しいだろうから、抽象的なレベルで分割してサブシステム事に実装させるなどの工夫も必要だろうが、それすら生成 AI の補助を受けられるような気がする。
ただこれは開発者が不要になるということではない。一人の開発者が担える領域と量が非常に大きくなるということだ。
バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。