フロントエンドでAIと協働する
LLMs界隈を観測しながらフロントエンドのAI駆動開発の推移をウォッチする ---
2023/02/24
要件定義をNotionでやっていればとてもスムーズに実装コードまで辿り着けそうな予感
(ただ、裏側がGPT-3だからかChatGPTより微妙な感じもある。ここは色々実験してみてかな)
「何を確認するか」「そのためにどんなテスト設計技法を用いれば良いか」といった部分は、まだまだAIに考えさせるのが難しい分野かなと思っています。 また、デシジョンテーブルの例や、境界値分析における振る舞いの表現のように、「物事を俯瞰的に見て表現する」「ハイレベルテストケースで表現する」といった部分はまだ苦手なのかなと感じました。 koushisa.iconもきになったので試してみた
---
2023/03/13
一度に説明するのは精度を下げる原因らしい
---
プロンプト
code:md
エンタープライズ向けのタスク管理アプリケーションを開発している優秀なエンジニアのように振る舞ってください。
`markdown
## 仕様
### レイアウト
### 振る舞い
- 初期値はタスクが1件表示されている
- 押下すると、"edit"とアラートで出力される
- 押下すると、"destory"とアラートで出力される
- 初期値は''
`
出力形式は以下とします
`markdown
## 出力形式
- 概要
- テストコード
- 実装例
`
利用技術を以下に説明します。
ライブラリは最新バージョンを利用してください。
`markdown
## 利用ライブラリ
## 利用ライブラリに関する補足説明と制約
- データフェッチで利用します
- React Hooksでデータフェッチするために利用します
`
タスクの型を以下に説明します。Json Schemaで記述しますのでTypeScriptで型定義してください。
`tsx
## Json Schemaで記述したタスクの型
`
{
"$id": "task",
"title": "Task",
"type": "object",
"properties": {
"id": {
"type": "number",
"description": "The task's id."
},
"title": {
"type": "string",
"description": "The tasks's title."
}
}
}
`
## TypeScriptで記述したタスクの型
`
type Task = {
id: number
title: string
}
`
`
出力するコードの構造を以下に説明します。
ECMAScript Moduleに準拠したimport/exportで実装を分割してください。
`markdown
## ディレクトリ構造
- src
- api
- tasks.ts
- components
- Task.list.tsx
- Task.item.tsx
- Task.list.test.tsx
## ディレクトリ構造の説明
- .tsで終わるファイル名はデータフェッチのコードを記述します `
シナリオテストの一例を以下に説明します。
`markdown
## シナリオテスト
- 初期値はタスクが1件表示されている
- 初期値は''で表示されている
`
5歳のエンジニアでも分かるようにハンズオン形式で作成してください。
結果
わりと納得のいく出力結果だった koushisa.icon
詳しい感想は後述
https://scrapbox.io/files/63f859602e62d9001c004223.png
https://scrapbox.io/files/63f859698ceeaf001b5fca33.png
https://scrapbox.io/files/63f85977b31ab5001ba795e3.png
https://scrapbox.io/files/63f8598f91ae5d001ba77d2c.png
code:ts
import { rest } from 'msw'
import { setupServer } from 'msw/node'
import React from 'react'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import { QueryClient, QueryClientProvider } from 'react-query'
import TaskList from './TaskList'
const server = setupServer(
return res(
ctx.json([
{
userId: 1,
id: 1,
title: 'delectus aut autem',
completed: false,
},
])
)
})
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
describe('TaskList', () => {
it('renders task list with initial value', async () => {
render(
<QueryClientProvider client={new QueryClient()}>
<TaskList />
</QueryClientProvider>
)
await waitFor(() => screen.getByTestId('task-1'))
expect(screen.getByTestId('task-1')).toBeInTheDocument()
expect(screen.getByText('delectus aut autem')).toBeInTheDocument()
expect(screen.getByTestId('task-edit-button-1')).toBeInTheDocument()
expect(screen.getByTestId('task-delete-button-1')).toBeInTheDocument()
})
it('renders task list with added task', async () => {
render(
<QueryClientProvider client={new Query
https://scrapbox.io/files/63f859b67fe5ec001b684bc1.png
https://scrapbox.io/files/63f859bdec8532001c73857c.png
code:ts
import { useState } from 'react';
import { useQuery } from 'react-query';
import { Task } from '../types/task';
const fetchTasks = async (): Promise<Task[]> => {
const data = await res.json();
return data;
};
const TaskList = () => {
const { data: tasks } = useQuery<Task[]>('tasks', fetchTasks);
const handleTaskNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setTaskName(event.target.value);
};
const handleAddTask = () => {
const newTask: Task = { id: tasks.length + 1, title: taskName };
setTaskName('');
// TODO: add newTask to the list of tasks
};
const handleEditTask = (id: number) => {
alert('edit');
};
const handleDeleteTask = (id: number) => {
alert('destroy');
};
return (
<div>
<ul data-testid="task-list">
{tasks?.map((task) => (
<li key={task.id} data-testid="task-item">
{task.title}
<button data-testid="edit-button" onClick={() => handleEditTask(task.id)}>
Edit
</button>
<button data-testid="delete-button" onClick={() => handleDeleteTask(task.id)}>
Delete
</button>
</li>
))}
</ul>
<input data-testid="task-name-input" type="text" value={taskName} onChange={handleTaskNameChange} />
<button data-testid="add-task-button" onClick={handleAddTask}>
Add Task
</button>
</div>
);
};
export default TaskList;
感想
「おっ?」とおもったポイント
data-testidとかまでマッピングした状態でテストコード吐いてくれる
人間の手でメンテナンス可能な構造にもう1段落とし込む手間が必要
AIが出力したコードと人間が書いたコードが混ざったときのカオス具合が想像できない 用語定義とその出処を文脈から取り除く
今回の例でも、システム的に重要な単語はブラケットで注釈してみた
これ効果あるのかな?koushisa.icon
このシステムにおけるタスクとは何か、みたいな
コンパイル失敗するコードも入ってそう
動かさないとそれが正しいのかが分からないのは手間である
上記はPythonの事例
要素のクエリと実装をあまり結びつけたくない
壊れやすいテストになるし読みづらい
現段階ではa11yに準拠したroleでテストコードを書いてもらうのは難しいみたい ここは課題か
コアロジックさえ担保できればUIの実装はブラックボックスでもいいのでtestidはアリかも
時系列で変わる状態を機械的にテストするのは難しそう
XStateで実装出来れば...可能性はある...?koushisa.icon 検証する観点も分割できる
仕様ファーストで対話しつつ実装とテストを同時に作成する
人間が振る舞いを検証する
実装を完全にブラックボックスにすると運用に難があるのではという気持ちになる
エッジケースの対応コスト > 自分で書くコスト
一度やり方を覚えれば再現性は高い
出力したコードの品質や妥当性を検証するためには人間がQA知識持ってないとダメ AIに任せるフェーズに移ったとしたらQAの知識こそ重要になりそうだと思いました
いつかテストの選択も任せられるかもしれませんが、現時点で学ぶべきは設計とQA(テスト技能の正しい選択方法)だなーと…
AIへプログラム作成を依頼する時点で仕様定義が終わっていると開発と同時にテストはできる システムそのものの理解がなければできない
プロンプトからテストの手法は検討できる
開発組織として活用するときは個々の専門性や責任範囲をどうおくかにもよるか 実際は開発を並行したいというのはあるはず
プロンプト書く人とその結果を検証する人で分業するとか? ここは諦めるのが現実的か
たぶん、Figmaからも吐けるツールが今後でてくる こういうのみてると0→1フェーズでのAIとの協働は2~3年後には現実的だなと思った まとめ
QAのAIに関したmiddlewareが台頭してくる時期が来るはず 期待していること
---
2023/03/28
@shanegJP: 「ChatGPT・プラグイン」ではコードを直に走らせることもできるので動画編集はもちろん、プログラミングも全てChatGPT内で完了できます。 UXの革命が起こるでしょう。マウスはもういらん。
---
2023/05/28
---
2024/10/17