RubyでREPLの動作をテストする方法。
難しそう?実は簡単です!

2024年12月17日掲載

governance

REPL(Read-Eval-Print Loop)とは、「入力」「評価」「出力」のループを繰り返すインタラクティブな実行環境のことです。ユーザーがキーボードから式や文を入力すると、即座に評価され、結果が画面に表示されます。このインタラクティブ性により、プログラムの動作を即時に確認できるため、デバッグや試行錯誤に非常に便利です。

しかし、この対話型の特性ゆえに、どうやってテストすればいいのか、と悩まれなことはないでしょうか。

本記事では、REPLの動作を自動でテストするためのシンプルなアプローチを紹介します。ぜひ、プログラムの品質向上にお役立てください。

目次

REPLとは?

REPLについて、もう少し説明を加えておきます。

REPLをサポートする処理系が実装されている具体的なプログラミング言語で例示します。

● Rubyのirb(interactive ruby)

$ irb
irb(main):001:0> 1
=> 1
irb(main):002:0> exit

 

プログラミング言語に限りません。DSLなコマンドでもREPLが使える処理系は多いですね。
● Redis

$ redis-cli
127.0.0.1:6379> SET foo 1
OK
127.0.0.1:6379> SET bar 2
OK
127.0.0.1:6379> GET foo
"1"
127.0.0.1:6379> GET bar
"2"
127.0.0.1:6379> exit

試験方法

試験方法について解説します。

テスティングフレームワークには、Rubyのminitestを使います。シンプルかつ拡張性もあるため、今回のようなREPLテストの自動化にも最適です。

試験対象のREPLは先の例で示したRedisとします。試験内容も例示した2つのSETとGETにします。

大きな流れは次の通りです。

  1. テストのメインコード(親プロセス)がpopenを呼び出す
  2. popenでRedis CLI(子プロセス)を起動
  3. 子プロセスがRedisサーバーと通信し、結果を親プロセスに返す

コードをいったんすべて掲載します。

require 'minitest/autorun'

describe 'Redis client' do
  def run_script(commands)
    output = []
    IO.popen("redis-cli", "r+") do |pipe|
      commands.each do |command|
        begin
          pipe.puts command
        rescue Errno::EPIPE
          puts "Failed to send command: #{command}"
          break
        end
      end

      pipe.close_write
      output = pipe.read.split("\n")
    end
    output
  end

  it 'sets values' do
    result = run_script([
      "SET foo 1",
      "SET bar 2"
    ])
    assert_equal ["OK", "OK"], result
  end

  it 'gets values' do
    result = run_script([
      "GET foo",
      "GET bar"
    ])
    assert_equal ["1", "2"], result
  end
end

 

実行します。

$ ruby redis-test.rb
-snip-
Finished in 0.014699s, 136.0670 runs/s, 136.0670 assertions/s.
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

プログラムのポイント

大事な点を少し掘り下げてみます。Redis CLIに直接コマンドを送る方法としてpopenを使用しています。

IO.popen("redis-cli", "r+") do |pipe|
-snip-
end

popenはサブプロセスを生成し、そのプロセスの標準入出力を制御できる便利なメソッドです。親プロセスとその子プロセス間に、標準入力(STDIN)と標準出力(STDOUT)のパイプラインを確立します。親プロセスから試験したいコマンドを送信し、それを子プロセスで実行します。またその結果を親プロセスへ返す通信となります。

このフロー図を見て、REPL固有の話ではなく、外部コマンドを使う場合と類似のアプローチでは、と思われたかもしれません。その通りです。状態の維持という差異はありますが、サブプロセスを起動して、popenやsystemを使い制御する点は共通です。

親子プロセス間で、入力と出力のファイルディスクリプタを変えてデータを授受するコンピュータの動作原理を思い浮かべれば、そりゃそうだ、という話です。

まとめ

REPLのインタラクティブ性から、一瞬テストをどうするべきか迷ってしまったのでまとめてみました。開発プロセスの効率向上と品質確保に少しでも貢献できれば幸いです。

おすすめの記事

条件に該当するページがございません