gpt_test_gen: GPT-3でDartのテストコードを書いてくれるツール
これはなに?
巷で噂のChatGPT(OpenAI API)を使ったテストコード生成ツールです。
ソースコードのパスを指定すると、その中身の関数などに対してテストコードを生成してくれます。
使い方
pub.devに公開してあるので、下記コマンドで自分のプロジェクトに追加します。
$ dart pub add dev:gpt_test_gen
あるいは直接dev_dependenciesに追加しても大丈夫です。
dev_dependencies:
gpt_test_gen:
追加できたら、後はコマンドを叩くだけです。
$ dart run gpt_test_gen -t <token> -i <your_source_file_path>
<token>
はOpenAIアカウントで発行できるAPIトークンです。自身のアカウントでログインして以下から発行してください。
your_source_file_path
は、テストコードを書いてほしいDartのソースファイルのパスを指定します。
例えば、lib/entities/foo_state.dart
などになります。
やっていること
やっていることは至極単純、
- 各引数で必要な情報を受け取る
- OpenAI APIにプロンプトとともにファイルの中身を展開して投げる
- 結果をテストコードファイルとして出力する
です。
情報を受け取る
DartはCLIツールを作るのに便利なpackageがいくつか用意されていて、args もその一つです。
ArgParser
というクラスが提供されるので、それを使うことで、コマンドライン引数をいい感じに解釈する事ができます。
今回は内部で GptTestGenerator
というクラスを用意しているので、引数を解釈した後はそっちに処理を全て移譲します。
const _tokenKey = 'token';
const _inputKey = 'input';
const _maxTokensKey = 'max-tokens';
void main(List<String> args) async {
// parserを用意して、argsを読み込ませて解釈する
final argParser = ArgParser()
..addOption(_tokenKey, abbr: 't')
..addOption(_inputKey, abbr: 'i')
..addOption(_maxTokensKey);
final result = argParser.parse(args);
final token = result[_tokenKey] as String?;
if (token == null) {
print('Please specify OpenAI API token like: `-t <token>`');
exit(1);
}
final inputPath = result[_inputKey] as String?;
if (inputPath == null) {
print('Please specify original source file path like: `-i lib/foo.dart`');
exit(1);
}
final generator = GptTestGenerator(token: token);
await generator.generate(
inputPath: inputPath,
maxTokens: result[_maxTokensKey] as String?,
);
}
APIに投げる
必要な情報をもらったら、GptTestGenerator
の中で処理を行っていきます。
入力ファイルの中身を読み取ってAPIコールをします。
Future<void> generate({
required String inputPath,
required String? maxTokens,
}) async {
final inputFile = File(inputPath);
final original = await inputFile.readAsString();
final result = await _callApi(original, maxTokens);
// 略
}
固定のプロンプトの中にファイルの内容を埋め込んでAPIコール、結果を抜き出してreturnします。
プロンプトは Please write a unit test of the following Dart code. No explanatory text is required as we would like to save your output as source code as is. Test cases should also include boundary conditions.
で、ほぼ参考記事のものと同じです(Swiftの部分はDartに置き換えています)。
以下のSwiftコードのユニットテスト(XCTest)を書いてください。出力したものをそのままソースコードとして保存したいので、説明文は不要です。
https://zenn.dev/zozotech/articles/4f1763c83db76e#xctestの自動生成
アレンジとして、「テストケースには境界条件を含めてください( Test cases should also include boundary conditions.
)」という一文を盛り込んでいます。
Future<String> _callApi(String original, String? maxTokens) async {
final url = Uri.parse('https://api.openai.com/v1/completions');
final prompt =
'Please write a unit test of the following Dart code. No explanatory text is required as we would like to save your output as source code as is. Test cases should also include boundary conditions.\n\n$original';
print(prompt);
final body = jsonEncode({
"model": "text-davinci-003",
"prompt": prompt,
"max_tokens": maxTokens ?? 1000,
});
final result = await http.post(
url,
headers: {
'Content-Type': ContentType.json.value,
'Authorization': 'Bearer $token',
},
body: body,
);
// エラー処理
// レスポンスから出力を取り出してreturn
}
最後に出力結果をファイルに出力します。
出力するファイルのパスは、入力として与えられたパスの lib/
の部分を test/
に置き換え、ファイル名の末尾に _test
をつけたものになっています。
final result = await _callApi(original, maxTokens);
final outputPath = inputPath
.replaceFirst(r'lib', 'test')
.replaceFirst(r'.dart', '_test.dart');
final outputFile = File(outputPath);
await outputFile.writeAsString(result);
動作イメージ
今後の展望
今後真面目にメンテナンスするかは不明ですが、するとなったら
- モデルの指定
- パス指定の柔軟性確保
-
lib/
をつけなくていいようにする
-
- (メンテとは違うけど)VS Code拡張化
あたりですかね?パッと思いつくのは…
Discussion