AIがテストコードを元に仕様を満たすコードを生成できるか検証してみた
ふとtwitterに呟いた以下のようなツイートにそこそこLikeがついていたので、実際のところChatGPTではどのような結果になるのかを検証してみました。
テストコードを生成する
そんなわけでネタを探していたところ、
こんな記事が目にとまったので、点字メーカープログラムを作成してもらうことにします。
テストコードのプロンプトは↓のような感じ。
「優秀な」みたいにAIをヨイショすると良い結果を出してくれるようです。
# 命令書:
あなたは優秀なプロのRubyプログラマーです。
以下の仕様を元に、RSpecによるテストコードを作成してください。
# 仕様:
- 日本語のひらがなを入力として受け取り、点字をテキストで出力する
- 点字は"o"と"-"の組み合わせの6文字で表す。改行コードは含まない
- 「あ」から「を」までの点字は以下の通りである。
- あ: o-----
- い: oo----
- う: o--o--
- え: oo-o--
- お: -o-o--
- か: o----o
- き: oo---o
- く: o--o-o
- け: oo-o-o
- こ: -o-o-o
- さ: o---oo
- し: oo--oo
- す: o--ooo
- せ: oo-ooo
- そ: -o-ooo
- た: o-o-o-
- ち: ooo-o-
- つ: o-ooo-
- て: ooooo-
- と: -oooo-
- な: o-o---
- に: ooo---
- ぬ: o-oo--
- ね: oooo--
- の: -ooo--
- は: o-o--o
- ひ: ooo--o
- ふ: o-oo-o
- へ: oooo-o
- ほ: -ooo-o
- ま: o-o-oo
- み: ooo-oo
- む: o-oooo
- め: oooooo
- も: -ooooo
- や: --oo--
- ゆ: --oo-o
- よ: --ooo-
- ら: o---o-
- り: oo--o-
- る: o--oo-
- れ: oo-oo-
- ろ: -o-oo-
- わ: --o---
- ー: -o--o-
- ん: --o-oo
- っ: -o----
- を: --o-o-
- 複数の文字を受け取った場合は改行で区切って出力する。例えば「あひる」を受け取った場合の出力結果は以下の通りである。
o-----
ooo--o
o--oo-
# 最低限テストに含める要件
- 50音全ての文字の動作
- 複数文字を受け取った場合の動作
- 基本的な異常系の確認
結果はこんな感じ。それっぽいRSpecのコードになっています。
RSpec.describe "convert_to_braille" do
it "returns the correct Braille for a single Hiragana character" do
expect(convert_to_braille("あ")).to eq("o-----")
expect(convert_to_braille("い")).to eq("oo----")
expect(convert_to_braille("う")).to eq("o--o--")
expect(convert_to_braille("え")).to eq("oo-o--")
expect(convert_to_braille("お")).to eq("-o-o--")
expect(convert_to_braille("か")).to eq("o----o")
expect(convert_to_braille("き")).to eq("oo---o")
expect(convert_to_braille("く")).to eq("o--o-o")
expect(convert_to_braille("け")).to eq("oo-o-o")
expect(convert_to_braille("こ")).to eq("-o-o-o")
expect(convert_to_braille("さ")).to eq("o---oo")
expect(convert_to_braille("し")).to eq("oo--oo")
expect(convert_to_braille("す")).to eq("o--ooo")
expect(convert_to_braille("せ")).to eq("oo-ooo")
expect(convert_to_braille("そ")).to eq("-o-ooo")
expect(convert_to_braille("た")).to eq("o-o-o-")
expect(convert_to_braille("ち")).to eq("ooo-o-")
expect(convert_to_braille("つ")).to eq("o-ooo-")
# ...以下省略...
ちょっと注文をつけてみます。
テストコードについて、以下の点を修正して下さい。仕様を実装したコードの出力は不要です。
- 文字列の変換はJapaneseBrailleというクラスで実装されるようにしてください
- `JapaneseBraille.new("あ").convert`という形で変換を実行できるようにしてください
- 点字に変換するときは1文字毎に改行するため、「あいうえお」の変換結果は下記になります
o-----
oo----
o--o--
oo-o--
-o-o--
指摘内容はちゃんと反映されているようです。
require_relative './japanese_braille'
RSpec.describe JapaneseBraille do
describe "#convert" do
context "when given a valid input" do
it "returns the correct Braille for a single Hiragana character" do
expect(JapaneseBraille.new("あ").convert).to eq("o-----")
expect(JapaneseBraille.new("い").convert).to eq("oo----")
expect(JapaneseBraille.new("う").convert).to eq("o--o--")
expect(JapaneseBraille.new("え").convert).to eq("oo-o--")
expect(JapaneseBraille.new("お").convert).to eq("-o-o--")
expect(JapaneseBraille.new("か").convert).to eq("o----o")
expect(JapaneseBraille.new("き").convert).to eq("oo---o")
expect(JapaneseBraille.new("く").convert).to eq("o--o-o")
expect(JapaneseBraille.new("け").convert).to eq("oo-o-o")
expect(JapaneseBraille.new("こ").convert).to eq("-o-o-o")
# ...以下省略...
冗長なので、もう少し注文をつけてみます。
テストコードをもっと簡潔にしてください。
すると簡潔なコードにまとめてくれました。
require_relative './japanese_braille'
RSpec.describe JapaneseBraille do
describe "#convert" do
let(:valid_input) { "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわーんっを" }
context "when given a valid input" do
it "returns the correct Braille for each Hiragana character" do
expected_output = [
"o-----", "oo----", "o--o--", "oo-o--", "-o-o--",
"o----o", "oo---o", "o--o-o", "oo-o-o", "-o-o-o",
"o---oo", "oo--oo", "o--ooo", "oo-ooo", "-o-ooo",
"o-o-o-", "ooo-o-", "o-ooo-", "ooooo-", "-oooo-",
"o-o---", "ooo---", "o-oo--", "oooo--", "-ooo--",
"o-o--o", "ooo--o", "o-oo-o", "ooooo-o", "-ooo-o",
"o-o-oo", "ooo-oo", "o-oooo", "oooooo", "-ooooo",
"--oo--", "--oo-o", "--ooo-",
"o---o-", "oo--o-", "o--oo-", "oo-oo-", "-o-oo-",
"--o---", "-o--o-", "--o-oo", "-o----", "--o-o-"
]
expect(JapaneseBraille.new(valid_input).convert.split("\n")).to eq(expected_output)
end
end
context "when given an invalid input" do
it "raises an ArgumentError when given a non-Hiragana character" do
expect { JapaneseBraille.new("a").convert }.to raise_error(ArgumentError)
expect { JapaneseBraille.new("1").convert }.to raise_error(ArgumentError)
expect { JapaneseBraille.new("ア").convert }.to raise_error(ArgumentError)
end
it "raises an ArgumentError when given an empty string" do
expect { JapaneseBraille.new("").convert }.to raise_error(ArgumentError)
end
end
end
end
冗長なままの方がRSpecとしては読みやすいので、実際には冗長な方が採用される気もしますが、長くてコピペする気にならないのでnote的にはこちらのコードを採用することにします。
ちゃんと指摘事項を反映してくれるのは嬉しいですね。人間だとなかなかこうもいかない。
テストコードを満たす実装を生成する
それでは早速テストコードを満たす実装を生成してもらいましょう。
以下のプロンプトをChatGPTに与えてみます。
あなたは優秀なプロのRubyプログラマーです。
以下のテストコードの仕様を満たすRubyのコードを作成してください。
require_relative './japanese_braille'
RSpec.describe JapaneseBraille do
describe "#convert" do
let(:valid_input) { "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわーんっを" }
context "when given a valid input" do
it "returns the correct Braille for each Hiragana character" do
expected_output = [
"o-----", "oo----", "o--o--", "oo-o--", "-o-o--",
"o----o", "oo---o", "o--o-o", "oo-o-o", "-o-o-o",
"o---oo", "oo--oo", "o--ooo", "oo-ooo", "-o-ooo",
"o-o-o-", "ooo-o-", "o-ooo-", "ooooo-", "-oooo-",
"o-o---", "ooo---", "o-oo--", "oooo--", "-ooo--",
"o-o--o", "ooo--o", "o-oo-o", "ooooo-o", "-ooo-o",
"o-o-oo", "ooo-oo", "o-oooo", "oooooo", "-ooooo",
"--oo--", "--oo-o", "--ooo-",
"o---o-", "oo--o-", "o--oo-", "oo-oo-", "-o-oo-",
"--o---", "-o--o-", "--o-oo", "-o----", "--o-o-"
]
expect(JapaneseBraille.new(valid_input).convert.split("\n")).to eq(expected_output)
end
end
context "when given an invalid input" do
it "raises an ArgumentError when given a non-Hiragana character" do
expect { JapaneseBraille.new("a").convert }.to raise_error(ArgumentError)
expect { JapaneseBraille.new("1").convert }.to raise_error(ArgumentError)
expect { JapaneseBraille.new("ア").convert }.to raise_error(ArgumentError)
end
it "raises an ArgumentError when given an empty string" do
expect { JapaneseBraille.new("").convert }.to raise_error(ArgumentError)
end
end
end
end
すると、こんなコードを生成してくれました。
class JapaneseBraille
BRAILLE_MAPPING = {
"あ"=>"o-----", "い"=>"oo----", "う"=>"o--o--", "え"=>"oo-o--", "お"=>"-o-o--",
"か"=>"o----o", "き"=>"oo---o", "く"=>"o--o-o", "け"=>"oo-o-o", "こ"=>"-o-o-o",
"さ"=>"o---oo", "し"=>"oo--oo", "す"=>"o--ooo", "せ"=>"oo-ooo", "そ"=>"-o-ooo",
"た"=>"o-o-o-", "ち"=>"ooo-o-", "つ"=>"o-ooo-", "て"=>"ooooo-", "と"=>"-oooo-",
"な"=>"o-o---", "に"=>"ooo---", "ぬ"=>"o-oo--", "ね"=>"oooo--", "の"=>"-ooo--",
"は"=>"o-o--o", "ひ"=>"ooo--o", "ふ"=>"o-oo-o", "へ"=>"ooooo-o", "ほ"=>"-ooo-o",
"ま"=>"o-o-oo", "み"=>"ooo-oo", "む"=>"o-oooo", "め"=>"oooooo", "も"=>"-ooooo",
"や"=>"--oo--", "ゆ"=>"--oo-o", "よ"=>"--ooo-",
"ら"=>"o---o-", "り"=>"oo--o-", "る"=>"o--oo-", "れ"=>"oo-oo-", "ろ"=>"-o-oo-",
"わ"=>"--o---", "ー"=>"-o---o", "ん"=>"--o-oo"
}
def initialize(input)
raise ArgumentError, "Input must not be empty" if input.empty?
raise ArgumentError, "Input must only contain Hiragana characters" unless input.match?(/\A[\p{hiragana}ー]+\z/)
@input = input
end
def convert
@input.chars.map { |char| BRAILLE_MAPPING[char] }.join("\n")
end
end
以下のような注釈もつけてくれています。親切ですね。
というわけで早速RSpecに通してみると、以下のような結果に。
$ rspec japanese_braille_spec.rb
F..
Failures:
1) JapaneseBraille#convert when given a valid input returns the correct Braille for each Hiragana character
Failure/Error: expect(JapaneseBraille.new(valid_input).convert.split("\n")).to eq(expected_output)
expected: ["o-----", "oo----", "o--o--", "oo-o--", "-o-o--", "o----o", "oo---o", "o--o-o", "oo-o-o", "-o-o-o", ... "o---o-", "oo--o-", "o--oo-", "oo-oo-", "-o-oo-", "--o---", "-o--o-", "--o-oo", "-o----", "--o-o-"]
got: ["o-----", "oo----", "o--o--", "oo-o--", "-o-o--", "o----o", "oo---o", "o--o-o", "oo-o-o", "-o-o-o", ... "--oo-o", "--ooo-", "o---o-", "oo--o-", "o--oo-", "oo-oo-", "-o-oo-", "--o---", "-o---o", "--o-oo"]
(compared using ==)
# ./japanese_braille_spec.rb:22:in `block (4 levels) in <top (required)>'
Finished in 0.03096 seconds (files took 0.27083 seconds to load)
3 examples, 1 failure
Failed examples:
rspec ./japanese_braille_spec.rb:8 # JapaneseBraille#convert when given a valid input returns the correct Braille for each Hiragana character
最後の方の点字の値が食い違ってますね。
テストコードの実行結果を踏まえて自己修復していけるかどうかを検証するために、そのままのログをフィードバックとして与えてみましょう。
テストが通りませんでした。以下のエラーメッセージを参考に、コードを修正してください。
Failures:
1) JapaneseBraille#convert when given a valid input returns the correct Braille for each Hiragana character
Failure/Error: expect(JapaneseBraille.new(valid_input).convert.split("\n")).to eq(expected_output)
expected: ["o-----", "oo----", "o--o--", "oo-o--", "-o-o--", "o----o", "oo---o", "o--o-o", "oo-o-o", "-o-o-o", ... "o---o-", "oo--o-", "o--oo-", "oo-oo-", "-o-oo-", "--o---", "-o--o-", "--o-oo", "-o----", "--o-o-"]
got: ["o-----", "oo----", "o--o--", "oo-o--", "-o-o--", "o----o", "oo---o", "o--o-o", "oo-o-o", "-o-o-o", ... "--oo-o", "--ooo-", "o---o-", "oo--o-", "o--oo-", "oo-oo-", "-o-oo-", "--o---", "-o---o", "--o-oo"]
(compared using ==)
# ./japanese_braille_spec.rb:22:in `block (4 levels) in <top (required)>'
Finished in 0.0278 seconds (files took 0.27784 seconds to load)
3 examples, 1 failure
Failed examples:
rspec ./japanese_braille_spec.rb:8 # JapaneseBraille#convert when given a valid input returns the correct Braille for each Hiragana character
すると以下のようなコードを提出してきました。
・・・何かキナくさいコードになってきました。
class JapaneseBraille
BRAILLE_MAPPING = {
"あ"=>"o-----", "い"=>"oo----", "う"=>"o--o--", "え"=>"oo-o--", "お"=>"-o-o--",
"か"=>"o----o", "き"=>"oo---o", "く"=>"o--o-o", "け"=>"oo-o-o", "こ"=>"-o-o-o",
"さ"=>"o---oo", "し"=>"oo--oo", "す"=>"o--ooo", "せ"=>"oo-ooo", "そ"=>"-o-ooo",
"た"=>"o-o-o-", "ち"=>"ooo-o-", "つ"=>"o-ooo-", "て"=>"ooooo-", "と"=>"-oooo-",
"な"=>"o-o---", "に"=>"ooo---", "ぬ"=>"o-oo--", "ね"=>"oooo--", "の"=>"-ooo--",
"は"=>"o-o--o", "ひ"=>"ooo--o", "ふ"=>"o-oo-o", "へ"=>"ooooo-o", "ほ"=>"-ooo-o",
"ま"=>"o-o-oo", "み"=>"ooo-oo", "む"=>"o-oooo", "め"=>"oooooo", "も"=>"-ooooo",
"や"=>"--oo--", "ゆ"=>"--oo-o", "よ"=>"--ooo-",
"ら"=>"o---o-", "り"=>"oo--o-", "る"=>"o--oo-", "れ"=>"oo-oo-", "ろ"=>"-o-oo-",
"わ"=>"--o---", "ー"=>"-o---o", "ん"=>"--o-oo",
"っ"=>"------"
}
def initialize(input)
raise ArgumentError, "Input must not be empty" if input.empty?
raise ArgumentError, "Input must only contain Hiragana characters" unless input.match?(/\A[\p{hiragana}ー]+\z/)
@input = input
end
def convert
braille = @input.chars.map { |char| BRAILLE_MAPPING[char] }
braille[-1] = "o-----" if @input[-1] == "っ" && braille.size > 1
braille.join("\n")
end
end
ChatGPT曰く、
エラーの内容を明らかに理解できていません。
当然再度テストを実行するとコケます。
$ rspec japanese_braille_spec.rb
F..
Failures:
1) JapaneseBraille#convert when given a valid input returns the correct Braille for each Hiragana character
Failure/Error: expect(JapaneseBraille.new(valid_input).convert.split("\n")).to eq(expected_output)
expected: ["o-----", "oo----", "o--o--", "oo-o--", "-o-o--", "o----o", "oo---o", "o--o-o", "oo-o-o", "-o-o-o", ... "o---o-", "oo--o-", "o--oo-", "oo-oo-", "-o-oo-", "--o---", "-o--o-", "--o-oo", "-o----", "--o-o-"]
got: ["o-----", "oo----", "o--o--", "oo-o--", "-o-o--", "o----o", "oo---o", "o--o-o", "oo-o-o", "-o-o-o", ... "--ooo-", "o---o-", "oo--o-", "o--oo-", "oo-oo-", "-o-oo-", "--o---", "-o---o", "--o-oo", "------"]
(compared using ==)
# ./japanese_braille_spec.rb:22:in `block (4 levels) in <top (required)>'
Finished in 0.02386 seconds (files took 0.1992 seconds to load)
3 examples, 1 failure
Failed examples:
rspec ./japanese_braille_spec.rb:8 # JapaneseBraille#convert when given a valid input returns the correct Braille for each Hiragana character
もう一度機械的にフィードバックしてみます。
テストが通りませんでした。以下のエラーメッセージを参考に、コードを修正してください。
Failures:
1) JapaneseBraille#convert when given a valid input returns the correct Braille for each Hiragana character
Failure/Error: expect(JapaneseBraille.new(valid_input).convert.split("\n")).to eq(expected_output)
expected: ["o-----", "oo----", "o--o--", "oo-o--", "-o-o--", "o----o", "oo---o", "o--o-o", "oo-o-o", "-o-o-o", ... "o---o-", "oo--o-", "o--oo-", "oo-oo-", "-o-oo-", "--o---", "-o--o-", "--o-oo", "-o----", "--o-o-"]
got: ["o-----", "oo----", "o--o--", "oo-o--", "-o-o--", "o----o", "oo---o", "o--o-o", "oo-o-o", "-o-o-o", ... "--ooo-", "o---o-", "oo--o-", "o--oo-", "oo-oo-", "-o-oo-", "--o---", "-o---o", "--o-oo", "------"]
(compared using ==)
# ./japanese_braille_spec.rb:22:in `block (4 levels) in <top (required)>'
Finished in 0.02386 seconds (files took 0.1992 seconds to load)
3 examples, 1 failure
Failed examples:
rspec ./japanese_braille_spec.rb:8 # JapaneseBraille#convert when given a valid input returns the correct Braille for each Hiragana character
次はどうでしょう?
class JapaneseBraille
BRAILLE_MAPPING = {
"あ"=>"o-----", "い"=>"oo----", "う"=>"o--o--", "え"=>"oo-o--", "お"=>"-o-o--",
"か"=>"o----o", "き"=>"oo---o", "く"=>"o--o-o", "け"=>"oo-o-o", "こ"=>"-o-o-o",
"さ"=>"o---oo", "し"=>"oo--oo", "す"=>"o--ooo", "せ"=>"oo-ooo", "そ"=>"-o-ooo",
"た"=>"o-o-o-", "ち"=>"ooo-o-", "つ"=>"o-ooo-", "て"=>"ooooo-", "と"=>"-oooo-",
"な"=>"o-o---", "に"=>"ooo---", "ぬ"=>"o-oo--", "ね"=>"oooo--", "の"=>"-ooo--",
"は"=>"o-o--o", "ひ"=>"ooo--o", "ふ"=>"o-oo-o", "へ"=>"ooooo-o", "ほ"=>"-ooo-o",
"ま"=>"o-o-oo", "み"=>"ooo-oo", "む"=>"o-oooo", "め"=>"oooooo", "も"=>"-ooooo",
"や"=>"--oo--", "ゆ"=>"--oo-o", "よ"=>"--ooo-",
"ら"=>"o---o-", "り"=>"oo--o-", "る"=>"o--oo-", "れ"=>"oo-oo-", "ろ"=>"-o-oo-",
"わ"=>"--o---", "ー"=>"-o---o", "ん"=>"--o-oo",
"っ"=>"------"
}
def initialize(input)
raise ArgumentError, "Input must not be empty" if input.empty?
raise ArgumentError, "Input must only contain Hiragana characters" unless input.match?(/\A[\p{hiragana}ー]+\z/)
@input = input
end
def convert
braille = @input.chars.map { |char| BRAILLE_MAPPING[char] }
if @input[-1] == "っ" && braille.size > 1
braille[-2] += "------"
braille.pop
end
braille.join("\n")
end
end
何だか明らかにヤバイ方向へ改修が進んでいきます。
完全にナナメ上ですね・・・。
もちろんテストは以下のように落ちてしまいます。
$ rspec japanese_braille_spec.rb
F..
Failures:
1) JapaneseBraille#convert when given a valid input returns the correct Braille for each Hiragana character
Failure/Error: expect(JapaneseBraille.new(valid_input).convert.split("\n")).to eq(expected_output)
expected: ["o-----", "oo----", "o--o--", "oo-o--", "-o-o--", "o----o", "oo---o", "o--o-o", "oo-o-o", "-o-o-o", ... "o---o-", "oo--o-", "o--oo-", "oo-oo-", "-o-oo-", "--o---", "-o--o-", "--o-oo", "-o----", "--o-o-"]
got: ["o-----", "oo----", "o--o--", "oo-o--", "-o-o--", "o----o", "oo---o", "o--o-o", "oo-o-o", "-o-o-o", ... "--ooo-", "o---o-", "oo--o-", "o--oo-", "oo-oo-", "-o-oo-", "--o---", "-o---o", "--o-oo", "------"]
(compared using ==)
# ./japanese_braille_spec.rb:22:in `block (4 levels) in <top (required)>'
Finished in 0.02639 seconds (files took 0.26334 seconds to load)
3 examples, 1 failure
Failed examples:
rspec ./japanese_braille_spec.rb:8 # JapaneseBraille#convert when given a valid input returns the correct Braille for each Hiragana character
このままだとどんどんナナメ上のコードになっていきそうなので、この方針での実験は中止しようと思います。
失敗した理由を考察する
前半のテストコードでは一体何がいけなかったのでしょうか?
恐らくテストコードからひらがなと点字の対応が読み解けなかったために、テスト結果のフィードバックを与えてもどんどんナナメ上の方向に解釈が進んでしまったのだと思われます。
実際に生成されたコードを読んでみると「ら」以降の点字対応が食い違っているために誤ったコードになってしまっています。
そこで次は考察を踏まえて冗長なテストコードをインプットに与えてみることにします。
冗長なテストコードを与えて実装を生成する
以下のようにひらがなと点字の生成結果が1対1対応するテストコードを与えてみます。
あなたは優秀なプロのRubyプログラマーです。
以下のテストコードの仕様を満たすRubyのコードを作成してください。
require_relative './japanese_braille'
RSpec.describe JapaneseBraille do
describe "#convert" do
context "when given a valid input" do
it "returns the correct Braille for a single Hiragana character" do
expect(JapaneseBraille.new("あ").convert).to eq("o-----")
expect(JapaneseBraille.new("い").convert).to eq("oo----")
expect(JapaneseBraille.new("う").convert).to eq("o--o--")
expect(JapaneseBraille.new("え").convert).to eq("oo-o--")
expect(JapaneseBraille.new("お").convert).to eq("-o-o--")
expect(JapaneseBraille.new("か").convert).to eq("o----o")
expect(JapaneseBraille.new("き").convert).to eq("oo---o")
expect(JapaneseBraille.new("く").convert).to eq("o--o-o")
expect(JapaneseBraille.new("け").convert).to eq("oo-o-o")
expect(JapaneseBraille.new("こ").convert).to eq("-o-o-o")
# ...以下省略...
すると今度は素直にテストコードに従ったコードを生成してきました。
class JapaneseBraille
HIRAGANA_TO_BRAILLE = {
"あ" => "o-----",
"い" => "oo----",
"う" => "o--o--",
"え" => "oo-o--",
"お" => "-o-o--",
"か" => "o----o",
"き" => "oo---o",
"く" => "o--o-o",
"け" => "oo-o-o",
"こ" => "-o-o-o",
"さ" => "o---oo",
"し" => "oo--oo",
"す" => "o--ooo",
"せ" => "oo-ooo",
"そ" => "-o-ooo",
"た" => "o-o-o-",
"ち" => "ooo-o-",
"つ" => "o-ooo-",
"て" => "ooooo-",
"と" => "-oooo-",
"な" => "o-o---",
"に" => "ooo---",
"ぬ" => "o-oo--",
"ね" => "oooo--",
"の" => "-ooo--",
"は" => "o-o--o",
"ひ" => "ooo--o",
"ふ" => "o-oo-o",
"へ" => "oooo-o",
"ほ" => "-ooo-o",
"ま" => "o-o-oo",
"み" => "ooo-oo",
"む" => "o-oooo",
"め" => "oooooo",
"も" => "-ooooo",
"や" => "--oo--",
"ゆ" => "--oo-o",
"よ" => "--ooo-",
"ら" => "o---o-",
"り" => "oo--o-",
"る" => "o--oo-",
"れ" => "oo-oo-",
"ろ" => "-o-oo-",
"わ" => "--o---",
"ん" => "--o-oo",
"っ" => "-o----",
"を" => "--o-o-",
}
def initialize(input)
raise ArgumentError, "Input cannot be empty" if input.empty?
raise ArgumentError, "Input must contain only Hiragana characters" unless input.match?(/\A[\p{Hiragana}]+\z/)
@input = input
end
def convert
@input.chars.map { |char| HIRAGANA_TO_BRAILLE[char] }.join("\n")
end
end
これをRSpecにかけてみます。
$ rspec japanese_braille_spec.rb
FF..
Failures:
1) JapaneseBraille#convert when given a valid input returns the correct Braille for a single Hiragana character
Failure/Error: expect(JapaneseBraille.new("ー").convert).to eq("-o--o-")
ArgumentError:
Input must contain only Hiragana characters
# ./japanese_braille.rb:54:in `initialize'
# ./japanese_braille_spec.rb:51:in `new'
# ./japanese_braille_spec.rb:51:in `block (4 levels) in <top (required)>'
2) JapaneseBraille#convert when given a valid input returns the correct Braille for multiple Hiragana characters
Failure/Error: expect(JapaneseBraille.new("わーんっを").convert).to eq("--o---\n-o--o-\n--o-oo\n-o----\n--o-o-")
ArgumentError:
Input must contain only Hiragana characters
# ./japanese_braille.rb:54:in `initialize'
# ./japanese_braille_spec.rb:67:in `new'
# ./japanese_braille_spec.rb:67:in `block (4 levels) in <top (required)>'
Finished in 0.00699 seconds (files took 0.20485 seconds to load)
4 examples, 2 failures
Failed examples:
rspec ./japanese_braille_spec.rb:6 # JapaneseBraille#convert when given a valid input returns the correct Braille for a single Hiragana character
rspec ./japanese_braille_spec.rb:57 # JapaneseBraille#convert when given a valid input returns the correct Braille for multiple Hiragana characters
どうやら長音記号が「ひらがな以外受け付けない」ガードの実装に引っかかってしまっているようです。
このフィードバックを与えたときに適切にコードの修正は行われるのでしょうか?
ガードの部分が悪さをしていることは認識できているようです。が、問題認識はおかしい・・・以下のコードを提案されました。
class JapaneseBraille
HIRAGANA_TO_BRAILLE = {
# ... same as before ...
}
def initialize(input)
raise ArgumentError, "Input cannot be empty" if input.empty?
raise ArgumentError, "Input must contain only Hiragana characters" unless input.match?(/\A[\p{Hiragana}\p{Katakana}ー]+\z/)
@input = input
end
def convert
# ... same as before ...
end
end
やっぱりナナメ上だなぁ。テストも落ちてしまいます。
$ rspec japanese_braille_spec.rb
FFF.
Failures:
1) JapaneseBraille#convert when given a valid input returns the correct Braille for a single Hiragana character
Failure/Error: expect(JapaneseBraille.new("ー").convert).to eq("-o--o-")
expected: "-o--o-"
got: ""
(compared using ==)
# ./japanese_braille_spec.rb:51:in `block (4 levels) in <top (required)>'
2) JapaneseBraille#convert when given a valid input returns the correct Braille for multiple Hiragana characters
Failure/Error: expect(JapaneseBraille.new("わーんっを").convert).to eq("--o---\n-o--o-\n--o-oo\n-o----\n--o-o-")
expected: "--o---\n-o--o-\n--o-oo\n-o----\n--o-o-"
got: "--o---\n\n--o-oo\n-o----\n--o-o-"
(compared using ==)
Diff:
@@ -1,5 +1,5 @@
--o---
--o--o-
+
--o-oo
-o----
--o-o-
# ./japanese_braille_spec.rb:67:in `block (4 levels) in <top (required)>'
3) JapaneseBraille#convert when given an invalid input raises an ArgumentError when given a non-Hiragana character
Failure/Error: expect { JapaneseBraille.new("ア").convert }.to raise_error(ArgumentError)
expected ArgumentError but nothing was raised
# ./japanese_braille_spec.rb:75:in `block (4 levels) in <top (required)>'
Finished in 0.02874 seconds (files took 0.28127 seconds to load)
4 examples, 3 failures
Failed examples:
rspec ./japanese_braille_spec.rb:6 # JapaneseBraille#convert when given a valid input returns the correct Braille for a single Hiragana character
rspec ./japanese_braille_spec.rb:57 # JapaneseBraille#convert when given a valid input returns the correct Braille for multiple Hiragana characters
rspec ./japanese_braille_spec.rb:72 # JapaneseBraille#convert when given an invalid input raises an ArgumentError when given a non-Hiragana character
またこのエラーメッセージをそのままフィードバックとして与えてみましょう。どうなるか?
お、次はまともな修正になりそうな気がする!
class JapaneseBraille
HIRAGANA_TO_BRAILLE = {
"あ" => "o-----",
"い" => "oo----",
"う" => "o--o--",
"え" => "oo-o--",
"お" => "-o-o--",
"か" => "o----o",
"き" => "oo---o",
"く" => "o--o-o",
"け" => "oo-o-o",
"こ" => "-o-o-o",
"さ" => "o---oo",
"し" => "oo--oo",
"す" => "o--ooo",
"せ" => "oo-ooo",
"そ" => "-o-ooo",
"た" => "o-o-o-",
"ち" => "ooo-o-",
"つ" => "o-ooo-",
"て" => "ooooo-",
"と" => "-oooo-",
"な" => "o-o---",
"に" => "ooo---",
"ぬ" => "o-oo--",
"ね" => "oooo--",
"の" => "-ooo--",
"は" => "o-o--o",
"ひ" => "ooo--o",
"ふ" => "o-oo-o",
"へ" => "ooooo-o",
"ほ" => "-ooo-o",
"ま" => "o-o-oo",
"み" => "ooo-oo",
"む" => "o-oooo",
"め" => "oooooo",
"も" => "-ooooo",
"や" => "--oo--",
"ゆ" => "--oo-o",
"よ" => "--ooo-",
"ら" => "o---o-",
"り" => "oo--o-",
"る" => "o--oo-",
"れ" => "oo-oo-",
"ろ" => "-o-oo-",
"わ" => "--o---",
"ー" => "-o--o-",
"ん" => "--o-oo",
"っ" => "-o----",
"を" => "--o-o-",
}
def initialize(input)
raise ArgumentError, "Input cannot be empty" if input.empty?
raise ArgumentError, "Input must contain only Hiragana characters" unless input.match?(/\A[\p{Hiragana}ー]+\z/)
@input = input
end
def convert
@input.chars.map { |c| HIRAGANA_TO_BRAILLE[c] }.join("\n")
end
end
一部、ビミョーに惜しい。やっぱりテストには失敗してしまいます。
$ rspec japanese_braille_spec.rb
FF..
Failures:
1) JapaneseBraille#convert when given a valid input returns the correct Braille for a single Hiragana character
Failure/Error: expect(JapaneseBraille.new("へ").convert).to eq("oooo-o")
expected: "oooo-o"
got: "ooooo-o"
(compared using ==)
# ./japanese_braille_spec.rb:35:in `block (4 levels) in <top (required)>'
2) JapaneseBraille#convert when given a valid input returns the correct Braille for multiple Hiragana characters
Failure/Error: expect(JapaneseBraille.new("はひふへほ").convert).to eq("o-o--o\nooo--o\no-oo-o\noooo-o\n-ooo-o")
expected: "o-o--o\nooo--o\no-oo-o\noooo-o\n-ooo-o"
got: "o-o--o\nooo--o\no-oo-o\nooooo-o\n-ooo-o"
(compared using ==)
Diff:
@@ -1,6 +1,6 @@
o-o--o
ooo--o
o-oo-o
-oooo-o
+ooooo-o
-ooo-o
# ./japanese_braille_spec.rb:63:in `block (4 levels) in <top (required)>'
Finished in 0.02718 seconds (files took 0.27411 seconds to load)
4 examples, 2 failures
Failed examples:
rspec ./japanese_braille_spec.rb:6 # JapaneseBraille#convert when given a valid input returns the correct Braille for a single Hiragana character
rspec ./japanese_braille_spec.rb:57 # JapaneseBraille#convert when given a valid input returns the correct Braille for multiple Hiragana characters
しかしこれをまたフィードバックしてみたら、どうでしょう。
class JapaneseBraille
BRAILLE_TABLE = {
"あ" => "o-----",
"い" => "oo----",
"う" => "o--o--",
"え" => "oo-o--",
"お" => "-o-o--",
"か" => "o----o",
"き" => "oo---o",
"く" => "o--o-o",
"け" => "oo-o-o",
"こ" => "-o-o-o",
"さ" => "o---oo",
"し" => "oo--oo",
"す" => "o--ooo",
"せ" => "oo-ooo",
"そ" => "-o-ooo",
"た" => "o-o-o-",
"ち" => "ooo-o-",
"つ" => "o-ooo-",
"て" => "ooooo-",
"と" => "-oooo-",
"な" => "o-o---",
"に" => "ooo---",
"ぬ" => "o-oo--",
"ね" => "oooo--",
"の" => "-ooo--",
"は" => "o-o--o",
"ひ" => "ooo--o",
"ふ" => "o-oo-o",
"へ" => "oooo-o",
"ほ" => "-ooo-o",
"ま" => "o-o-oo",
"み" => "ooo-oo",
"む" => "o-oooo",
"め" => "oooooo",
"も" => "-ooooo",
"や" => "--oo--",
"ゆ" => "--oo-o",
"よ" => "--ooo-",
"ら" => "o---o-",
"り" => "oo--o-",
"る" => "o--oo-",
"れ" => "oo-oo-",
"ろ" => "-o-oo-",
"わ" => "--o---",
"ー" => "-o--o-",
"ん" => "--o-oo",
"っ" => "-o----",
"を" => "--o-o-",
}
def initialize(input)
raise ArgumentError, "Input must contain only Hiragana characters" unless input.match?(/\A[\p{hiragana}\p{space}]*\z/)
@input = input
end
def convert
@input.chars.map { |c| BRAILLE_TABLE[c] }.join("\n")
end
end
なぜガード節のコードも変更してしまうんや・・・orz
更なる修正を求めてエラーメッセージをフィードバックしたところ、以下のメッセージが返ってきて打ち止めとなりました。
まとめ
与えられた仕様からテストコードを生成することはできた
簡略化されたテストコードから仕様を満たすコードを生成することは難しかった
エラーメッセージをフィードバックとして与えると、どんどんナナメ上のコードが生成されてしまった
LLMが解釈しやすいテストコードを与えると、簡略化されたテストコードを与えたときよりも品質は向上したが、与えたテストコードを満たす実装は生成できなかった
冗長なテストコードを与えても、テストのエラーメッセージを元にコードを修正し切ることは困難だった
次世代のテスト駆動開発まではもう少しかかりそうです。