Upgrade to Pro — share decks privately, control downloads, hide ads and more …

テストだけで見つかる? Rubyでつくるメソッド発掘器

 テストだけで見つかる? Rubyでつくるメソッド発掘器

北陸Ruby会議02(https://regional.rubykaigi.org/hokuriku02/) で発表したスライドです。

Yukimitsu Izawa

November 14, 2026

Transcript

52 slides
  1. 自己紹介 井澤 ゆきみつ キャンプ系YouTuber My Outdoor Life (https://kanazawa.camp/) ものづくり全般が趣味(電子工作・ナイフ制作・レザークラフトなど) 博士(情報科学)

    インターネットを使った疎結合分散システムの研究をしています 株式会社 クルウィット 取締役COO 兼 北陸支社長 一般社団法人 Code for Kanazawa 理事 Kanazawa.rb ファシリテーション/ノベルティ 担当
  2. fi 準備運動 [1, 3, 2].??? == rst も min も通る。

    見分ける入力を、1つ 💭 頭の中で考える 1
  3. 準備運動 first も min も、このテストを通る [1, 3, 2].??? == 1

    見分けるには、先頭が最小でない入力を足す [3, 1, 2].first #=> 3 [3, 1, 2].min #=> 1
  4. 道具 メソッド発掘器 f = Finder.new f.add([1, 3, 2], 1) #

    入力, 期待する戻り値 f.candidates 1 Array / Enumerable の公開メソッドを、候補にする 2 引数もブロックも渡さずに呼ぶ 3 戻り値が期待と等しいものを残す
  5. 道具 3個、見つかりました 3 候補 [1, 3, 2] → 1 →

    1 → 1 →1 配列から先頭を取り除く
  6. 発掘 1 並べ替え [2, 1].??? == [1, 2] 通るメソッドは 7個。

    全部、言えますか? 💭 頭の中で、7個挙げる
  7. 発掘 1 | 並べ替え 7個目は minmax 7 [2, 1] →

    [1, 2] 候補 最小 1 と 最大 2 → [1, 2]
  8. 発掘 1 並べ替え [3, 2] → [2, 3] … [101,

    100] → [100, 101] 同じ形のテストを、100個に増やす。 7個の候補は、何個に減る? ✌ 指で数を出す
  9. 発掘 1 | 並べ替え 100個とも合格。候補は 7個のまま 7 候補 これまでのテスト [2,

    1] → [1, 2] + 同じ形の例を足して、100個 100個、すべて合格
  10. 発掘 1 並べ替え [3, 1, 2] → [1, 2, 3]

    形の違う例を、1つ足す。 残るのは sort と sort! の 2個? 💭 頭の中で予想する
  11. 発掘 1 | 並べ替え 4個。rotate が残りました 4 + [3, 1,

    2] → [1, 2, 3] → [1, 2, 3] → [2, 1, 3] 候補 → [1, 2, 3] これまでのテスト [2, 1] → [1, 2] → [1, 3]
  12. 発掘 1 | 並べ替え 1つ回すと、昇順に並ぶ [3, 1, 2].rotate #=> [1,

    2, 3] 回転でも、たまたま昇順に並ぶ入力だった
  13. 発掘 1 | 並べ替え 2個。sort と sort! 2 候補 これまでのテスト

    [2, 1] → [1, 2] [3, 1, 2] → [1, 2, 3] + [2, 3, 1] → [1, 2, 3] → [1, 2, 3] → [3, 1, 2]
  14. 発掘 1 | 並べ替え 154通り試しても、2個のまま 154 0 通りの入力を試した 件。戻り値が違った入力 長さ

    0〜5 の順列すべて sort と sort! の戻り値を == で比べた 0! + 1! + 2! + 3! + 4! + 5!
  15. 発掘 1 | 並べ替え 呼び出したあとの配列も見る 渡した入力 [2, 3, 1] 戻り値

    呼び出したあとの入力 [1, 2, 3] [2, 3, 1] そのまま [1, 2, 3] [1, 2, 3] 書き換えた 戻り値を比べるだけでは、配列の書き換えを検出できない
  16. 発掘 1 | 並べ替え sort だけが残りました 1 候補 これまでのテスト [2,

    1] → [1, 2] + 入力の内容を変えない 標本ラベル sort 発掘地 Array 決め手 入力 3つ + 呼び出したあとの配列 139 ▸ 7 ▸ 4 ▸ 2 ▸ 1 [3, 1, 2] → [1, 2, 3] [2, 3, 1] → [1, 2, 3] 🦴 発掘でも、骨と一緒に、骨が埋まっていた地層を記録する
  17. 発掘 2 | 平均 3個、見つかりました 3 候補 [1, 3] →

    2 → 2 → 2 → 2 要素が 2個なので、要素数も 2
  18. 発掘 2 | 平均 例を足すと、0個 0 候補 + [1, 2,

    3] → 2 → 3 → 3 → 3 これまでのテスト [1, 3] → 2 引数なしで平均を返すメソッドは、ない 見つからなかったことも、発掘の結果
  19. 発掘 3 | 重複を取り除く 4個、通りました 4 候補 [1, 1, 2]

    → [1, 2] 引数なしでも、配列の重複を除く
  20. 発掘 3 | 重複を取り除く minmax、2回目 4 候補 [1, 1, 2]

    → [1, 2] 引数なしでも、配列の重複を除く 最小 1 と 最大 2 → [1, 2]
  21. 発掘 3 | 重複を取り除く 値を 3種類にすると、3個 3 候補 + [1,

    1, 2, 3] → [1, 2, 3] → [1, 2, 3] → [1, 2, 3] → [1, 2, 3] これまでのテスト [1, 1, 2] → [1, 2] → [1, 3]
  22. 発掘 3 | 重複を取り除く uniq! が落ちて、2個 2 候補 これまでのテスト [1,

    1, 2] → [1, 2] [1, 1, 2, 3] → [1, 2, 3] + [1, 2] → [1, 2] → [1, 2] → nil → [1, 2]
  23. 発掘 3 | 重複を取り除く 2個で、発掘を終えます 2 + 入力の内容を変えない 標本ラベル 標本ラベル

    uniq union 発掘地 Array 発掘地 Array 候補 これまでのテスト [1, 1, 2] → [1, 2] [1, 1, 2, 3] → [1, 2, 3] [1, 2] → [1, 2] どちらも、いまの要求を満たしている 1個に絞ることは、目的ではない
  24. しくみ 発掘器の中心は、12行 METHODS = (Array.public_instance_methods(false) | Enumerable.public_instance_methods(false)).sort %i[shuffle shuffle! sample

    hash] def candidates(examples) METHODS.select do |name| examples.all? do |input, expected| input.dup.public_send(name) == expected rescue StandardError false end end end
  25. しくみ 探している範囲と、先行例 対象 Array / Enumerable の公開メソッド 139個 Ruby 3.4

    では 138個。4.0 で r nd が加わった 呼び方 引数なし・ブロックなしで、1回だけ 除外 shu e、sample など 結果がランダムに変わる。[2, 1].shu e は、2分の1の確率で合格する 先行例 Squeak Smalltalk の Method Finder ffl fi ffl Ted Kaehler 作。Ruby 版は Andrew Birkett「MethodFinder for Ruby」(2006年)
  26. 発掘 4 | 空かどうか 1 を入れたら false 2 候補 これまでのテスト

    [] → true + [1] → false → true → false → false
  27. 発掘 4 空かどうか [nil, false].??? == 「空」の意味を、まだ決めていない 要素が 1つもない →

    false 真の値が 1つもない → true ✋ 「false と書く」→「true と書く」の順に、挙手 ?
  28. 発掘 4 | 空かどうか 要求によって、期待値が変わる 要素が 1つもない → 期待値は false

    empty? 答える問い 要素が 0個か 真の値が 1つもない → 期待値は true none? 答える問い 真の値が 0個か 同じ入力 [nil, false] に、2通りの期待値を書ける
  29. 普段のテスト | 選んだ 2つの実装の比較 未設定のときだけ、既定値を補う 実装 A 実装 B def

    notify?(setting) def notify?(setting) setting || true setting.nil? ? true : setting notify?(nil) #=> true ✓ notify?(nil) テスト assert_equal true, notify?(nil) assert_equal ____, notify?(____) #=> true ✓
  30. 普段のテスト | 選んだ 2つの実装の比較 未設定のときだけ、既定値を補う 実装 A 実装 B def

    notify?(setting) def notify?(setting) setting || true setting.nil? ? true : setting notify?(nil) #=> true ✓ notify?(nil) #=> true ✓ notify?(false) #=> true ✗ notify?(false) #=> false ✓ OFF にした人にも、通知してしまう テスト assert_equal true, notify?(nil) assert_equal false, notify?(false)
  31. この博物館にちなんで イグアノドンの「角」 1834 この年に見つかった標本から、姿を復元する 円錐形の骨は、「鼻の角」として描かれた 全身の骨は、まだ揃っていなかった Gideon Mantell の復元図(1834年以降)。パブリックドメイン /

    Wikimedia Commons 1878 ベルギーの炭鉱で発掘が始まる。全身に近い骨格が 約30体 円錐形の骨は、「親指」に付いていた 🦴 40年以上、「鼻の角」という復元が通用していた
  32. お土産と宿題 お土産: 数行で書ける発掘器(shu e、sample などのランダムな候補も試す) find = ->(input, expected) {

    (Array.instance_methods(false) | Enumerable.instance_methods(false)) .select { |m| (input.dup.public_send(m) == expected) rescue false } } find.([2, 1], [1, 2]) 宿題 [3].??? == 3 通るメソッドは、何個? ffl first と min を見分ける、次の入力は? ありがとうございました 井澤 ゆきみつ / Kanazawa.rb 🦕

More Decks by Yukimitsu Izawa

Other Decks in Programming