OOP版lsコマンドのテスト実装で大苦戦した振り返り

FBCで難関と呼ばれている?Rubyでlsコマンドを作成する課題について振り返りをまとめたいと思います。

lsコマンドの課題は

の2段階に分けて構成されていて、こちらの振り返りは オブジェクト指向 の課題に対してになります。

要件としては、

  • オプションなし
  • aオプション
  • rオプション
  • lオプション
  • オプションの組み合わせ

が必須となり、以下は歓迎要件になります。

  • 引数にファイルやディレクトリを指定可能にする
  • 半角英数字以外のファイル名(ひらがなや漢字など)を持つファイルがあっても表示が崩れない

実際の実行結果は以下になります。

私は歓迎要件までやったため、非OOP版の段階から実装が複雑になっていて、OOP版の合格をいただくまでにPRでのやりとりはコメント数150越えの超大作となりました(笑)

OOP版lsコマンドの構成

実装の内容を具体的に書くとFBC生のネタバレになってしまうため、ボカしていますが・・・私が作成したOOP版lsコマンドの構成は以下のイメージです。

実行ファイルを起点に

  1. 標準入力から受け取るオプションとファイルやディレクトリの情報を処理する
  2. 1を受け取りフォーマッターの判定と呼び出し、出力処理を実行
  3. 2で呼び出されたフォーマッターはオプションとファイルやディレクトリの情報に応じて、加工した結果を返す

という流れです。

名前重要

maimux2x.hatenablog.com

昨年、上記ブログを書いたのですが非OOP版lsコマンドで自分が使用していた変数名やメソッド名が酷すぎてw、まずは名前の見直しが第一関門でした。 加えて、OOP版ではクラス名も適切に考える必要があり、レビューをいただいた内容を参考にクラス名・メソッド名・変数名は順次見直しをしていきました。

動けばいいは通用しない

OOPの実装をベースにOOP版は実装をしていったのですが、当時の自分の至らない点をリファクタリングせずに処理が通っているから大丈夫という甘い判断をしていました・・・。

例えば

  • long_formatterString を返していた
  • short_formatterArray を返していた
  • フォーマッタークラス内で出力処理まで担ってしまっていた
  • map メソッド内で元の配列に副作用を伴う加工処理を行なってしまっていた
  • 左辺しか評価されていないのに || が残っていた

etc・・・ 挙げ出したらキリがないです(笑)

ただ、細かくレビューしていただいたおかげで、自分の癖だったり見落としがちな点を認識することができました

テストが通らず大苦戦

課題ではテストコードは必須要件ではないのですが、テストも書きました。 書いたはいいのですが、全くテストが通らず大苦戦しました。

苦戦した原因はlsコマンドは標準入力からオプションとファイルやディレクトリの情報を受け取るため、2つの引数を適切に加工しテスト結果を比較する部分をうまく書くことができませんでした。

もう少し具体的に書くとARGV を使って、標準入力から受け取った情報を配列でその後の処理に回していくのですが、その際に getopts を使っていました。 当初、テストでは Array を作成して、それに対して getopts を呼べばいいじゃん!と思っていたのですが、Arraygetopts を持っていないためどうしよう・・・となったのでした。

ここについてはメンターさんにペアプロしていただき、Test Doubleというやり方を教えていただきました。

Test Doubleについては我らが伊藤さんのブログがとても分かりやすいです。

blog.jnito.com

今回、テストを実行する上で標準入力から値を受け取るという部分をテストコード上では再現できません。

そのため、伊藤さんのブログにあるようにテスト対象が依存する外部メソッドをテストのために代用で用意したダミー処理で置き換えることで ARGV の課題部分をクリアすることができました。

以下は実装イメージです。

class FakeArgv
  def getopts(*)
    { 'a' => true, 'l' => false, 'r' => false }
  end
end

params = FakeArgv.new

p params.getopts('a', 'l', 'r')

この経験を通して、

  • 何をテストしたいのか
  • テスト対象が依存している外部メソッドまでテストの必要があるのか

を考えることの必要性を学びました。

Test Doubleについては奥が深そうです。 私はRSpecではなくminitestでテストを書いているため、RSpecでも書けるように勉強中ですが、伊藤さんの記事や書籍をもとに基本を押さえていきたいと思います。

lsコマンドの課題では伊藤さんの記事にほんと〜〜〜〜〜〜に助けられました。

lsコマンドの出力結果を評価するためにヒアドキュメント内に期待値を書いていたのですが、テストを実行すると一致しているはずなのに落ちている。

expected = <<~TEXT
      .              example.md     test.txt
      blank          memo.txt       あいう.md
      dummy.md       test.html      テスト.md
TEXT

調べた結果、私が実装したlsコマンドは実際の出力の際に末尾に空白スペースと改行が入っているためでした。

それをヒアドキュメント内で再現しようとすると見た目で分からないため、数ヶ月後の自分は絶対忘れているだろうなと思いました。

以下の伊藤さんの記事で配列と join を使って期待値を書いている箇所があり、こちらに置き換えて無事にテストが通せました(涙)

qiita.com

まとめ

OOP版lsコマンドはFBCで一番忘れられない課題です。

合格まで中々の道のりでしたが、得たものはこの先自分を助けてくれるであろう大切なポイントばかりでした。

レビューがかなり大変だったと思われ、たくさん気づきを与えてくれたメンターさんにとても感謝しています。

現在取り組んでいるチーム開発も卒業まで後少しなため、引き続き頑張りたいと思います!