はじめに
AtCoderのプログラムは標準入力からデータを読み込み、結果を標準出力に出力するプログラムを作ります。
これをLivebookで実行するにはどうすればよいか。
この方法を模索していていました。
テストで利用するMockを利用する事で、実現できました。この方法を書いてみます。
やりたいこと
AtCoderの回答のプログラムでは、IOモジュールを使うと想定します。
IO.put, IO.read, IO.getn の第一引数はdeviceで:stdioが渡されます。この値を別の値に置き換えて、実行すること。
IOモジュールを置き換える方法
次の記事でモック化について、そのバックグランドや、いくつかの方法が紹介されているので、一度みてみると参考になるかと思います。
https://blog.appsignal.com/2023/04/11/an-introduction-to-mocking-tools-for-elixir.html
今回は、この記事でも最初に紹介されている、Mockを使いました。
記述方法
Mockはユニットテストで使う事が多いので、testの中での記述例が多いですが、今回は、AtCoderのプログラムを実行するという目的なので、Judgeモジュールの中に実装してみました。
with_mock関数がポイントです。
ここにMock化するモジュールと関数名、置き換える関数を与えて、その中で対象の処理(今回は、Main.main())を実行します。
judge(string_in, string_out) は入力例、出力例を文字列で渡すとMain.main()を実行して、結果の判定をする関数です。
defmodule Judge do
import Mock
def judge(string_in, string_out) do
{:ok, file} = StringIO.open(string_in)
with_mock IO, [:passthrough], [
getn: fn :stdio, a, b -> passthrough([file, a, b]) end,
getn: fn a, b -> passthrough([file, a, b]) end,
read: fn :stdio, option -> passthrough([file, option]) end,
read: fn option -> passthrough([file, option]) end,
puts: fn :stdio, item -> passthrough([file, item]) end,
puts: fn item -> passthrough([file, item]) end,
] do
Main.main()
end
{:ok, {_, result}} = StringIO.close(file)
if string_out == result do
IO.puts("OK")
true
else
IO.inspect(string_in, label: "Input")
IO.inspect(string_out, label: "Expected")
IO.inspect(result, label: "Received")
false
end
end
end
ハマりポイント
IO.read(:stdio,:line)をMockにして、その内部で、IO.read(file,:line)を呼び出す処理が必要です。
この時、IO.readはモック化されているので、そのまま記述すると、定義されていないエラーになってしまいます。
Mockにはpassthough機能があったので、解決できました。
まとめ
- Mockを使うことで、簡単な記述で、任意のモジュールを入れ替えた状態で実行する事ができる。
- passthough機能もある
- AtCoderの回答を修正することなくLivebook上で実行できた。