SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

翻訳書「レガシーコード改善ガイド」の注目トピック

「レガシーコード改善ガイド」のススメ
第2回:コードを理解するため、仕様化テストで文書化する

邦訳版『Working Effectively With Legacy Code』の重要トピックを紹介


  • X ポスト
  • このエントリーをはてなブックマークに追加

仕様化テストの手順

 では、宅配便の配送料金を計算するDeliveryServiceというクラスを例に取って、仕様化テストを書く手順について説明しましょう。このクラスには、発送地と目的地の郵便番号、配送指定(通常、速達など)、重量、サイズを引数として配送料金を計算するcalculateDeliveryChargesメソッドがあります。実際のコードは省略しますが、このメソッドはさまざまな条件による組み合わせを追加していった結果、巨大なメソッドとなってしまっています。このメソッドに対するテストを書いて、仕様を文書化してみましょう。

 まずは基本的なケースとして、同じ地域の目的地向けに(郵便番号が同じ)、重量が1Kgでサイズが40の荷物を通常便で送る場合のテストを書いてみましょう。結果がどうなるのかは分からないので、あり得ない値(ここでは3億円としました)を設定しておきます。

public void testCalculateDeliveryCharges() {
    DeliveryService service = new DeliveryService();
    Money charges = service.calculateDeliveryCharges(
        "100-0000", "100-0000", NORMAL, 1, 40);
    assertEquals(300000000, charges.getValue());
}

 このテストを実行すると、想定した通りテストは失敗しました。テスト結果から、このテストで指定した条件だと、calculateDeliveryChargesメソッドは配送料金として400円を返すようです。

junit.framework.AssertionFailedError: expected:<300000000> but was:<400>

 テストを成功させるには、次のようなコードを書けばよいことが分かります。

public void testCalculateDeliveryCharges() {
    DeliveryService service = new DeliveryService();
    Money charges = service.calculateDeliveryCharges(
        "100-0000", "100-0000", NORMAL, 1, 40);
    assertEquals(400, charges.getValue());
}

 このテストにより、calculateDeliveryChargesメソッドは、同じ地域内で、重量が1Kg以下で40サイズ以下の荷物を通常便で送る場合の料金は400円であるという振る舞いが確認できました。

 続けて、発送地と目的地を指定せず、通常の配送指定をし、重量とサイズが0の場合についてテストを書いてみましょう。

public void testCalculateDeliveryCharges() {
    DeliveryService service = new DeliveryService();
    Money charges = service.calculateDeliveryCharges("", "", NORMAL, 0, 0);
    assertEquals(300000000, charges.getValue());
}

 このテストを実行すると、想定通りテストは失敗しました。引数を正しく指定しなかった場合、calculateDeliveryChargesメソッドは、配送料金として-1円を返す仕様になっているようです。

junit.framework.AssertionFailedError: expected:<300000000> but was:<-1>

 この結果を受けて、テストが成功するように変更します。

public void testCalculateDeliveryCharges() {
    DeliveryService service = new DeliveryService();
    Money charges = service.calculateDeliveryCharges("", "", NORMAL, 0, 0);
    assertEquals(-1, charges.getValue());
}

仕様化テストで文書化した振る舞いを最新に保つ

 このような手順を繰り返していくと、既存のコードの振る舞いを段階的に理解しながら、同時に既存のコードの振る舞いを「文書化」できます。実際に仕様書としてまとめるわけではありませんが、仕様化テストとして整備した単体テストを見れば、そのコードの振る舞いを理解できるようになるわけです。

 もしかすると、仕様化テストを書くことで明らかになったコードの振る舞いは、ずっと昔に作られた仕様書とは異なるかもしれません。しかし、仕様書と異なっていても、その振る舞いこそが現在のコードとしては正しいことになります。加えて、仕様化テストのコードは実際に動かすことができるため、そこに書かれた振る舞いの正しさをいつでも検証できます。テストを頻繁に動かしてメンテナンスを行えば、文書化した振る舞いを最新の状態に保つことができます。

 仕様化テストを書く手順をまとめると、次のようになります。

  • ステップ1:テストコードで対象となるコードを呼び出す
  • ステップ2:失敗することが分かっている表明を書く
  • ステップ3:失敗からどんな振る舞いかを確認する
  • ステップ4:コードが実現する振る舞いをするようにテストを変更する
  • ステップ5:以上の手順を繰り返す

まずは「テストで保護する」ことに専念する

 ところで先ほどの配送料金を計算するメソッドでは、引数が不正な場合には-1円を返すことが分かりました。しかし、引数が不正な場合には、例外(java.lang.IllegalArgumentExceptionや独自の例外クラス)を発生させるべきと考えた方もおられるかもしれません。

 しかし、よく考えてみましょう。コードを「あるべき姿」に変更することはそれほど簡単ではないかもしれません。巨大化しているcalculateDeliveryChargesメソッドを修正し、さらにこのメソッドを呼び出しているすべてのコードを修正し、正しく修正できたことを確認するためのテストを行う必要があります。もしかすると、作業途中に見つけたバグの修正や別のリファクタリングをしたくなるかもしれません。こうした作業を行うのは良いことですが、テストなしに行うのには大きなリスクがあります。

 先ほど書いたテストは、システムを変更する準備作業として、コードを十分理解するために書いたものです。コードを「あるべき姿」に変更することより、まずは現状を受け入れてテストを整備することが重要です。仕様化テストでコードの振る舞いを理解し、テストを整備できれば、自信を持って安全にリファクタリングを始めることができます。

 システムに変更を加える場合、当てずっぽうで変更を加えるわけにはいきません。しかし、限られた時間では、コードを隅々まで理解することも大変です。そのような状況では、仕様化テストを整備することを試してみてください。仕様化テストが整備できれば、既存のコードの振る舞いを「保護する」ことができるようになり、安心して変更を加えることができるようになるはずです。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
翻訳書「レガシーコード改善ガイド」の注目トピック連載記事一覧

もっと読む

この記事の著者

田村 友彦(タムラ トモヒコ)

ウルシステムズ株式会社 シニアコンサルタントICカードの通信プロトコルからWebアプリケーションまで、さまざまなソフトウェア開発をさまざまな言語・環境で経験してきた。2006年より現職。現在は、技術的な支援を行う立場でシステム開発に携わっている。支援だけでなく、ときにはどっぷりとコーディングをすることもある...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/4104 2009/07/14 11:39

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング