byebugのガイドをおおざっぱになぞってみる(3)

GUIDE.mdに沿って実際に動かしてみて、自分で自分に説明してみた記録。翻訳ではない。(というか英語的にどうかって言われると自信がない。)

Byebugのバージョンは9.0.6、Rubyのバージョンは2.4.0p0。

Introduction (続き)

Attaching to a running program with byebug

ここまでの例では、最初からByebugを使用してきた。しかし、コード量が多くなり、調査したい部分まで到達するのに手間がかかるようになると、別の方法でByebugを起動したくなる。

この節では、より詳しいByebugの使い方と共に、プログラムの途中にコードを挿し込む方法を説明する。

その際、ユニットテストを用いる。ユニットテストがあるとプログラムの品質を上げられる上に、デバッグの手間を大いに減らしてくれる。

ここでは、最初のサンプルコードにあったメソッドtriangleのユニットテストを作ることにする。

triangle.rbでは最後にtriangle(5)を実行している。これがある程度の動作確認になっていると言えなくもないが、自動化されたテストにはなっていない。テストコードの出力と、その出力が正しいかどうかが、誰にでも分かるように表示されなければならない。

まずはtriangle.rbrequireできるようにしたい。その前にtriangle(5)の結果の表示を行わないようにする。

ただし、メソッドの使い方の例として使えるので、triangle.rbが直接作実行されたときだけtriangle(5)の表示を行うよう変更する。

if __FILE__ == $0
  t = triangle(5)
  puts t
end

なお、byebugコマンドは、byebugコマンドを介してもこの種のコードが動作するよう$0の値を設定しなおしている。

ここでのユニットテストにはminitestフレームワークを使う。

以下のテストコードをtest_triangle.rbという名前でtriangle.rbと同じディレクトリに作る。

require 'minitest/autorun'
require_relative 'triangle.rb'

class TestTriangle < Minitest::Test
  def test_basic
    solutions = []

    0.upto(5) { |i| solutions << triangle(i) }

    assert_equal([0, 1, 3, 6, 10, 15], solutions, '最初の5つの三角数')
  end
end

実行してみよう。

$ ruby test_triangle.rb
Run options: --seed 63011

# Running:

.

Finished in 0.000915s, 1092.8962 runs/s, 1092.8962 assertions/s.

1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

コードの中でbyebugメソッドの呼び出しをすると、Byebugにより、その場所でプログラムをストップさせることができる。

  def test_basic
    byebug # このようにストップさせたいところで呼び出す
    solutions = []

byebugrequireした上でテストを実行しよう。

$ ruby -rbyebug test_triangle.rb
Run options: --seed 31679

# Running:


[2, 11] in /private/tmp/test_triangle.rb
    2: require_relative 'triangle.rb'
    3:
    4: class TestTriangle < Minitest::Test
    5:   def test_basic
    6:     byebug
=>  7:     solutions = []
    8:
    9:     0.upto(5) { |i| solutions << triangle(i) }
   10:
   11:     assert_equal([0, 1, 3, 6, 10, 15], solutions, '最初の5つの三角数')
(byebug)

byebugメソッド呼び出したところでプログラムがストップしているのが分かる。現在地を確認してみよう。

(byebug) set nofullpath
fullpath is off
(byebug) bt
--> #0  TestTriangle.test_basic at .../private/tmp/test_triangle.rb:7
    #1  block (3 levels) in Minitest::Test.block (3 levels) in run at .../lib/minitest/test.rb:105
    #2  Minitest::Test.capture_exceptions at .../lib/minitest/test.rb:202
    #3  block (2 levels) in Minitest::Test.block (2 levels) in run at .../lib/minitest/test.rb:102
    #4  Minitest::Test.time_it at .../lib/minitest/test.rb:253
    #5  block in Minitest::Test.block in run at .../lib/minitest/test.rb:101
    #6  #<Class:Minitest::Runnable>.on_signal(name#String, action#Proc) at .../minitest-5.10.1/lib/minitest.rb:349
    #7  Minitest::Test.with_info_handler(&block#Proc) at .../lib/minitest/test.rb:273
    #8  Minitest::Test.run at .../lib/minitest/test.rb:100
    #9  #<Class:Minitest>.run_one_method(klass#Class, method_name#String) at .../minitest-5.10.1/lib/minitest.rb:822
    #10 #<Class:Minitest::Runnable>.run_one_method(klass#Class, method_name#String, reporter#Minitest::CompositeReporter) at .../minitest-5.10.1/lib/minitest.rb:323
    #11 block (2 levels) in #<Class:Minitest::Runnable>.block (2 levels) in run(reporter#Minitest::CompositeReporter, options#Hash) at .../minitest-5.10.1/lib/minitest.rb:310
    ͱ-- #12 Array.each at .../minitest-5.10.1/lib/minitest.rb:309
    #13 block in #<Class:Minitest::Runnable>.block in run(reporter#Minitest::CompositeReporter, options#Hash) at .../minitest-5.10.1/lib/minitest.rb:309
    #14 #<Class:Minitest::Runnable>.on_signal(name#String, action#Proc) at .../minitest-5.10.1/lib/minitest.rb:349
    #15 #<Class:Minitest::Runnable>.with_info_handler(reporter#Minitest::CompositeReporter, &block#Proc) at .../minitest-5.10.1/lib/minitest.rb:336
    #16 #<Class:Minitest::Runnable>.run(reporter#Minitest::CompositeReporter, options#Hash) at .../minitest-5.10.1/lib/minitest.rb:308
    #17 block in #<Class:Minitest>.block in __run(reporter#Minitest::CompositeReporter, options#Hash) at .../minitest-5.10.1/lib/minitest.rb:158
    ͱ-- #18 Array.map at .../minitest-5.10.1/lib/minitest.rb:158
    #19 #<Class:Minitest>.__run(reporter#Minitest::CompositeReporter, options#Hash) at .../minitest-5.10.1/lib/minitest.rb:158
    #20 #<Class:Minitest>.run(args#Array) at .../minitest-5.10.1/lib/minitest.rb:135
    #21 block in #<Class:Minitest>.block in autorun at .../minitest-5.10.1/lib/minitest.rb:62
(byebug)

最初の節でしてきたようにbyebugコマンドでtest_triangle.rbを実行し、プロンプトに対してbreak 6、続いてcset nofullpathbtと命令を投入したのと同じ結果になる。