0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

番外編: レガシーすぎるコードのユニットテストを作ろうとして挫折したあの日

0
Posted at

むかし、むかし・・・

あるところにプロジェクトがありました。
そのプロジェクトでは、ユニットテストが役に立っていませんでした。
なぜかというと、ユニットテストの2割が常に不合格になっていたからです。

ユニットテストの件数は全部で4000件ほどですので、不合格になるテストは800ほどでしょうか。
人々はみな、ユニットテストについてあきらめていました。
コード更新時には自動でユニットテストが動きますが、常に不合格が通知されますので
気にしても仕方がありません。
もちろん全部を合格にする元気はありません。
彼らは何件が不合格になるかを覚えており、
コードを修正するときは、不合格が増えないようにしつつ、
ユニットテストが不合格になっている処理に虫が入り込まないよう、
天に祈りをささげて日々を過ごしていました。

ある日、やる気に満ちた・・・

一人のベテラン開発者がそのプロジェクトに新たに参加しました。
その開発者はやる気に満ちていましたので、ユニットテストの状況を見るに見かねて
何とか立て直そうと考えました。

数か月間そのソフトウェアの機能を学び、いざ1つの不合格になっているユニットテストを作り直そうとします。

まずは準備(Arrange)

さて、ユニットテストを行うには、対象のクラスを初期化せねばなりません。
そのクラスを見るとフィールドがいっぱいあります。
1、2、3・・・ 簡単には数えられません。20ほどでしょうか。
それぞれのフィールドがクラスだったりしますので、1つのフィールドを初期化するのに10か20の値を設定する必要があります。
1つのフィールドを初期化するだけでも一苦労です。

引数も準備せねばなりません。
引数にも数えきれないフィールドがあります。

実行(Act)、そして検証(Assert)

何とか初期化し、メソッドを呼び出しました。取り合えず動いています。
では、結果を確認せねばなりません。

メソッドに対するドキュメントなんてありませんので、何をやっているかはソースを読まねばなりません。
何やらクラスのフィールドを色々更新しています。
戻り値だけでなく、フィールドの更新内容も確認しなければなりません。
20ほどあるフィールドの元の値と引数に応じて、20ほどのフィールドが正しく更新されていることを確認せねばなりません。

彼はくじけました。
無理なものは無理です。
そして彼はプロジェクトから姿を消しました。

改めて思い返してみる

どうすれば崩壊したユニットテストを立て直すことができたのでしょうか?

その頃の私は「ガシーコード改善ガイド」も「単体テスト本」も読んでいませんでした。
読み終えた今なら違う結果になったのでしょうか?
また、プロジェクトに参加してまだ半年ほどでしたので、その業務に対する知識も十分では無かったと思います。

では、今から過去に戻ってそのプロジェクトに参加し、さらに業務についてしっかりと知識を得た、と仮定したらどうでしょうか?
それでも立て直せる気がしません。

では、どうすれば良かったのでしょうか?

設計の原則に従う

このプロダクションコードはSOLID原則のような設計の原則に多くの違反をしています。

  • 膨大な量のフィールドは単一責任の原則 (SRP)への違反
    • クラスが多くの役割を持ちすぎているため、テストの準備(Arrange)であらゆる状態を再現しなければならなくなります
  • 肥大化した引数はインターフェース分離の原則への違反
    • 呼び出し側が関心のないデータまで渡す必要があるため、テストデータ作成の労力が肥大化します
  • 副作用によるフィールド更新はカプセル化の失敗
    • 戻り値ではなく「内部状態がどう変わったか」を検証(Assert)せねばならず、テストが実装の細部に依存して壊れやすくなります

リリース初期からこの状況だったのかは分かりませんが、
これらの違反が溜まり過ぎた状況では現実的な労力でリファクタリングすることもユニットテストを実装することも不可能です。
そもそも合格しているユニットテストも、どこまでケースを網羅できているか確認することも出来ません。

既に頑張れば出来るという精神論でどうにかなるレベルではなくなっています。
こうなる前にリファクタリングを行うべきでした。

ユニットテストの悲鳴を無視しない

私が参加するはるか前ですが、
ユニットテストが4000件あるということは、プロジェクト初期にはユニットテストが運用され、
メンバー全員がユニットテストの価値を認めていたはずです。

しかしある時、プロダクションコードの修整を優先したために不合格を放置し始めたはずです。
この時ユニットテストはプロダクションコードの品質低下への警告を叫んでいたはずです。
そこがターニングポイントだったはずです。

そこを大きく踏み越えてプロダクションコードの品質が下がり続けた結果
もはやどうにもならないレベルまで到達してしまっていました。

メンバーがユニットテストの価値を学びなおす

ユニットテストが崩壊していましたので、メンバーは価値を見いだせていませんでした。
それでもユニットテストを自動で動かし続けていました。
動かしたところで、毎回不合格が出ていて大した意味がないのにです。
そして新たなユニットテストを書くときには
バグを発見できるかよりも将来不合格にならないことを重視していました。

せめて不合格になるテストを全て捨てる勇気を持つべきでした。ユニットテストが全て合格する状態を当たり前のものとし、チームの文化を変えることができれば
状況が少しずつでも変わったかもしれません。
ですが、せっかく作ったテストを捨てる勇気が有りませんでした。

なぜユニットテストを作り、自動で動かすのか、
それによってどういう価値を得ようとしているのかを再確認し
メンバー全員でユニットテストをどうしていくべきかを再確認するべきでした。

そういう現場で必ず聞く不毛な発言

プロダクションコードの品質が低下してどうにもならなくなると
必ずと言っていいほど、以下のようなことを言うメンバーが現れます。
「もう一から作り直さなきゃ無理だよ」

この類の発言を聞くたびに私は衝撃を受けます。
発言者は一から作り直せば、今のようなコード品質にならないと本気で思っているのです。

今、目の前のコードのリファクタリングもできず、
それに対して何か対策を学んで試してみようという意思も持たず
今の状況を作り出した一人なのにです。
得てして、この発言をする人は大した技術力も有りませんでした。

もし作り直して改善できる開発者であれば、
その状況からでも少しでも改善するよう足掻くはずです。

  • リファクタリングやレガシーコードの改善に関する勉強をする
  • ユニットテストの不合格を潰しながら線形の改善方法を模索する
  • 新機能でユニットテストをどうするべきか考え改善する

そしてチームとして設計への規律を整え、リファクタリングを当然のものとし
ユニットテストの品質、特にリファクタリングへの耐性を高めてからでなければ、
作り直しても数年もすれば今と変わらないものが出来上がるだけです。

今できることはいくつでもあります。

訪れるか分からない作り直す機会に向けて少しでも対策を考え続けること、
それがこの状況を変えうる唯一の道と信じています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?