BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル テスト駆動開発:実はそれは設計技術です

テスト駆動開発:実はそれは設計技術です

原文(投稿日:2019/05/10)へのリンク

我々にはソフトウェアが要件を満たしていることを確認するためのソフトウェアのテストが必要です。それは、入力に正しく応答すること(入力検証)、許容可能な時間内に実行すること(パフォーマンステスト)、ユーザがインストールして実行できること(デプロイメントテスト)、およびそれがステークホルダの目標を達成していることです。これらの目標は、ビジネス上の結果や、セキュリティ、使いやすさ、保守性などの機能です。

テストの種類は次のとおりです。

  • スモークテストとサニティテストはソフトウェアが基本的な前提で動くかどうか確認します。
  • Mavenを実行するときのように、継続的なテストはすべての反復で実行されます。
  • 新しいプログラミングコードを追加するとき、または既存のコードを変更するときに、回帰テストを使用します。他のコードが変わらず機能することを確認したいためです。
  • パフォーマンステストは、ソフトウェアの実行にかかる時間を測定します。
  • 受入テストでは、利害関係者がソフトウェアに満足しているかどうか、および支払いをする意思があるかどうかを確認します。

単体テストは、一連のテストの最小の構成要素です。プログラミングコード内のすべてのクラスには、それに対応するユニットテストクラスがあります。メソッド呼び出しをモックにすることで、テストは他のクラスから分離されます。

統合テストは実装が簡単です。すべての依存関係を持たせた上でクラスをテストします。ここでは、ソフトウェアを通してパスが機能していることを確認できますが、失敗した場合は、どのクラスが失敗しているのかわかりません。システムテストは、ハードウェアオペレーティングシステム、Webサービスなどを含むシステム全体をチェックします。

プログラマー以外の人がテストを読んだり変更したりできるように、テストは読みやすくするべきです。アジャイルチームでは、プログラマーはテスターやアナリストと協力し、テストと仕様は共通の基盤ですため、誰でもテストを読み、必要に応じてテストを変更することさえできるべきです。

TDD:設計と生産性に関するすべて

テスト駆動開発(TDD)は、より優れたソフトウェアを持続的に早く提供するための確立された手法です。TDDは単純な考えに基づいている。製品コードを書く前に失敗するテストを書くことです。新しい行動が必要ですか?失敗するテストを書いてください。しかし、この一見単純な考えをうまく実行するには、スキルと判断が必要です。

TDDは本当に設計のためのテクニックです。TDDの基礎は、小規模なテストを使用してボトムアップを早急に設計することであり、システムへの信頼を構築しながら迅速に何らかの価値を得ることです。よりよい名前はテスト駆動設計かもしれません。

設計方法としては、集中と単純さです。目標は、開発者が価値を提供する上で不要な余分なコードを書くことを防ぐことです。問題を解決するのに必要最小限のコードを書くことです。

多くの記事がTDDを行うことのすべての利点を誇りにしています。そして多くの技術会議の講演で、私たちにテストをするように語りかけ、そうすることがどれほどクールであるかを教えてくれます。それは正しいです(クールであることについてではなく、有用であることについて)。テストは必須です。TDDについて一般的に列挙された利点は本当です。

  • あなたはより良いソフトウェアを書いています。
  • あなたはオーバーエンジニアリングを避けます。
  • 新機能を導入するときは、今ある世界を壊さないようにします。
  • あなたのソフトウェアは自己文書化されています。

私はこれらの利点に常に同意していますが、保守可能な良いソフトウェアを書くためにTDDが必要ないと思った時がありました。今は、もちろん、私が間違っていたとわかっています。しかし、なぜ私は光り輝く魔法の利益があるにもかかわらず私がこの考えを持っていたのでしょうか。それはコストです。

TDDは多額のコストがかかります。テストを行わないとさらにコストがかかると考える人は正しいですが、それらのコストは異なる時期に生じます。TDDを実行すると、即時の費用が発生します。TDDをしないと、未来にコストが発生します。

何かを成し遂げるための最も効果的な方法は、できるだけ自然にそれをすることです。人々の本質は怠け者(ソフトウェア開発者がこの点で最高のパフォーマーかもしれない)で欲張っていることです。それで我々は今コストを削減する方法を見つけなければなりません。言うのは簡単ですが、やるのは大変です。

TDDを取り巻く多くの理論、規模、および観点があります。しかし、私は実際にどのようにTDDを行うかを示すことを好みます。そして最後に、我々はこれから、開始から、最後に作品ができるまでの進むべきを見ていきます。それはTDDを使用することによってのみ達成されます。

私のプレゼンテーション「ベストプラクティスガイドラインを使ったユニットテストとTDDの概念」へのリンクはこちらです。次のトピックが含まれています。

  • なぜテストが必要か
  • テストの種類
  • どうやって、いつ各種のテストを使うか
  • テストレベルのカバー範囲
  • テスト戦略
  • TDDの実践

また、テスト中にすべきこととすべきでないことを導くガイドラインとベストプラクティスも含まれています。

「TDDの実践」セクションと紹介された概念は一般にどの言語にも当てはまりますが、デモにはJavaを使用します。目標は、コードを書くだけでなく、設計するときにどのように考えるべきかを示し、ワクワクするようなアートを作成することです。

問題を分析する

複雑さに関係なく問題を解決するための最初のステップは、問題を分析し、次に入力シナリオと出力がどうあるべきかを考慮して、小さな連続した完全なステップに分割することです。実装の詳細に深く入ることなく、ビジネスの観点から、元の要件との差がないことを確認するためにこれらの手順をレビューします。

これは重要なステップです。最も重要なステップの1つは、以降の実装フェーズを合理化するために、与えられた問題のすべての要件を識別できるようにすることです。これらの小さなステップを踏むことで、クリーンで実装が簡単なテスト可能なコードを実装できるようになります。

TDDは、問題のすべてのケースをカバーするまで、そのようなステップを開発および維持するためのキーです。

ローマ数字をそれと同等のアラビア数字に変換するための変換ツールライブラリの開発を依頼されたとしましょう。開発者として、私は次のことを行います。

  1. ライブラリプロジェクトを作成します。
  2. クラスを作成します。
  3. 変換メソッドを作成に取りかかります。
  4. 起こりうる問題のシナリオと何をすべきかを考えます。
  5. 退屈なテストケース(テストケースを書かないことにつながる傾向がある)を書いたことを確認するために、タスクのテストケースを書きながら、一方で、通常どおりメインメソッドでタスクをほぼテストしてしまいます。

この種の開発はひどいです。

コードの開発中にプロセスを正しく開始してTDDを実践するために、プロジェクトが成功して最終に至るまでの実践的な手順に従ってください。それには、将来の開発にかかる時間とコストを抑えるための一連のテストケースを使用します。

このコード例は、私のGitHubリポジトリからクローンすることができます。ターミナルを起動し、好きな場所を指定して、次のコマンドを実行してください。

$ git clone https://github.com/mohamed-taman/TDD.git

TTDの赤/緑/青の変更に対してコミットするようにプロジェクトを管理していますので、コミットをナビゲートするときに、最終的なプロジェクト要件に向けた変更と実施されたリファクタリングに気付くことができます。

私はMavenビルドツール、Java SE 12、およびJUnit 5を使用しています。

TDDの実践

コンバーターを開発するための最初のステップは、ローマ字のIをアラビア数字の1に変換するテストケースを用意することです。

テストケースが最初の要件を満たすように、コンバータクラスとメソッド実装を作成しましょう。

でも、待って、待って!ちょっと待って!実用的なアドバイスとして、このルールを念頭に置いて開始することをお勧めします。最初にソースコードを作成するのではなく、テストケースでクラスとメソッドを作成することから開始します。これは意図的なプログラミングと呼ばれます。つまり、新しいクラスとそれが使用される新しいメソッドに名前を付けることは、私たちが書いているコードの使い方とそれがどのように使われるかについて考えることを強います。より良く、よりクリーンなAPI設計につながります。

ステップ1

テストパッケージ、クラス、メソッド、テスト実装を作成することから始めます。

パッケージ: rs.com.tm.siriusxi.tdd.roman
クラス: RomanConverterTest
メソッド: convertI()
実装: assertEquals(1, new RomanConverter().convertRomanToArabicNumber("I"));

ステップ2

ここで失敗するテストケースはありません。コンパイルエラーです。それでは、まずIDEヒントを使用して、Javaソースフォルダにパッケージ、クラス、およびメソッドを作成しましょう。

パッケージ: rs.com.tm.siriusxi.tdd.roman
クラス: RomanConverter
メソッド: public int convertRomanToArabicNumber(String roman)

ステップ3(赤の状態)

指定されたクラスとメソッドが正しいことと、テストケースが実行されることを確認する必要があります。

convertRomanToArabicNumberメソッドを実装してIllegalArgumentExceptioをスローすることで、赤の状態になることを確認します。

public int convertRomanToArabicNumber(String roman) {
        throw new IllegalArgumentException();
 }

テストケースを実行します。赤いバーが見えます。

ステップ4(緑の状態)

このステップでは、テストケースをもう一度実行する必要がありますが、今度は緑色のバーが表示されます。テストケースを満足させ、緑にするために、最小限のコードでメソッドを実装します。そのため、メソッドは1を返す必要があります。

public int convertRomanToArabicNumber(String roman) {
        return 1;
 }

テストケースを実行します。緑色のバーが見えます。

ステップ5(リファクタリング状態)

今度はリファクタリングの時間です(必要あれば)。リファクタリングプロセスには、本番コードだけでなくテストコードも含まれることを強調したいと思います。

テストクラスから未使用のインポートを削除します。

テストケースをもう一度実行して青いバーを確認します。冗談です。青いバーはありません。それでもリファクタリング後にすべてが意図したとおりに機能する場合は、緑色のバーが再び表示されます。

未使用のコードを削除することは基本的で単純なリファクタリング方法の1つです。未使用のコードは、コードの読みやすさを低下させ、クラスのサイズを大きくし、したがってプロジェクトのサイズを大きくします。

ステップ6

これ以降は、赤から緑へ、そして青への一貫したプロセスをたどります。TDDの最初のポイントである赤の状態を、失敗するテストケースとして新しい要件または問題のステップを記述することによって通過します。それを機能全体が完成するまで続けます。

基本的な要件またはステップから始めて、必要な機能が終了するまでステップバイステップで進みます。これにより、最も簡単なものから複雑なものまで、明確なステップで完了することができます。

実装全体について、飛躍して一度に多くの時間を費やすのではなく、これを実行する方が良いでしょう。一度にやろうとすると、必要としていないことまで考え、必要としないケースをカバーすることに至るかもしれません。これはオーバーコーディングをもたらします。そして生産的ではありません。

次のステップは、IIを2に変換することです。

同じテストクラスで、新しいテストメソッドとその実装を次のように作成します。

メソッド: convertII()
テストケースを実行すると、convertII()メソッドが失敗したため、赤いバーになります。convertI()メソッドは緑色のままです。

ステップ7

テストケースを実行して緑色のテストバーを表示させる必要があります。両方のケースを満たす方法を実装しましょう。両方のケースを処理するために単純なif/else if/elseチェックを使用でき、elseの場合はIllegalArgumentExceptionをスローします。

public int convertRomanToArabicNumber(String roman) {
        if (roman.equals("I")) {
            return 1;
        } else if (roman.equals("II")) {
            return 2;
        }
        throw new IllegalArgumentException();
    }

これを避けるべき問題は、roman.equals("I")のようなコード行から発生するNULLポインタ例外です。それを修正するには、単に等号を"I".equals(roman)に変換します。

テストケースをもう一度実行すると、すべてのケースで緑色のバーが表示される。

ステップ8

さて、私たちはケースをリファクタリングする機会があります。ここにコードの匂いがします。リファクタリングでは、通常、コードのにおいを次の形式で探します。

  • 長いメソッド
  • 重複コード
  • たくさんのif/elseコード
  • switch-caseの悪魔
  • 論理の単純化
  • 設計の問題

ここでのコードのにおいは(あなたはそれを見つけましたか?)if/elseステートメントと多くのリターンです。

たぶん、ここでsum変数を導入し、ローマ字の文字配列をループ処理するためにforループを導入することでリファクタリングする必要があります。文字がIの場合、sumに1を加えてからsumreturnします。

しかし、私は防御的プログラミングが大好きなので、無効な文字をすべてカバーするために、throw節をif文のelse文に移動します。

public int convertRomanToArabicNumber(String roman) {
        int sum = 0;
        for (char ch : roman.toCharArray()) {
            if (ch == 'I') {
                sum += 1;
            } else {
                throw new IllegalArgumentException();
            }
        }  
        return 0;
    }

Java 10以降では、変数の定義にvarを使用でき、int sum = 0;でなく、var sum = 0;を使います。

We run the tests again to make sure that our refactoring didn’t change any code functionality.

おっと、赤いバーが見えます。ああ!すべてのテストケースで0を返しており、誤ってsumでなく0を返していました。

それを修正して、今度は美しい緑が見えます。

これは、たとえどんなに簡単な変更であっても、その後テストを実行する必要があることを示しています。リファクタリング中にバグが入り込んでしまう可能性が常にあります。そしてここで私たちのヘルパーはバグをキャッチするために実行するテストケースです。これは回帰テストの力を示しています。
もう一度コードを見ると、もう1つの匂いがす(あなたそれが見えますか?)。ここでの例外は記述されているものではないため、意味のあるエラーを提供する必要があります。

message of Illegal roman character %s, ch.

throw new IllegalArgumentException(String.format("Illegal roman character %s", ch));

テストケースをもう一度実行すると、すべてのケースで緑色のバーが表示されます。

ステップ9

別のテストケースを追加します。IIIを3に変換しましょう。

同じテストクラスで、新しいテストメソッドとその実装を作成します。

メソッド: convertIII()

テストケースをもう一度実行すると、すべてのケースで緑色のバーが表示されます。私たちの実装はこのケースをカバーします。

ステップ10

今度はVを5に変換する必要があります。

同じテストクラスで、新しいテストメソッドとその実装を作成します。

メソッド: convertV()

テストケースを実行すると、convertV()は失敗するため赤いバーが表示されますが、他は緑です。

メインのif文にelse/ifを追加してこのメソッドを実装し、char = 'v'の場合はsum+=5;とします。

for (char ch : roman.toCharArray()) {
            if (ch == 'I') {
                sum += 1;
            } else if (ch == 'V') {
                sum += 5;
            } else {
                throw new IllegalArgumentException(String.format("Illegal roman character %s", ch));
  } }

ここでリファクタリングする機会がありますが、このステップでは実施しません。実装フェーズでは、テストに合格して緑色のバーを表示することが唯一の目標です。私たちは現時点で、シンプルな設計、リファクタリング、あるいは良いコードを持っていることに注意を払っていません。コードが通過したら、戻ってリファクタリングすることができます。

リファクタリング状態では、リファクタリングのみに焦点を当てます。注意をそらして生産性が低下するのを避けるために、一度に1つのことに集中してください。

テストケースは緑色の状態にならなければなりません。

場合によっては、一連のif/else文が必要になります。最適化するには、if文のテストケースを最も訪問数の多いケースから最も訪問数の少ないケースの順に並べます。 適用可能であれば、ケースのテストを避けてケースに直接到達するようにswitch-case文に変更することをお勧めします。

ステップ11

私たちは緑の状態にあり、リファクタリングフェーズにあります。メソッドに戻ると、私が好きでないと言っているif/elseについて何かをすることができます。
おそらくこのif/elseを使う代わりに、ローマ字をキー、アラビア語の等価な値をバリューとして格納するルックアップテーブルを導入することができます。

if文を削除して、sum += symbols.get(chr);と書きましょう。電球を右クリックして、インスタンス変数を導入します。

private final Hashtable<Character, Integer> romanSymbols = new Hashtable<Character, Integer>() {
        {
            put('I', 1);
            put('V', 5);
        }
    };


前と同じように無効なシンボルをチェックする必要があるので、romanSymbolsに特定のキーが含まれているかどうかを判断するコードを作成し、含まれていない場合は例外をスローします。

    public int convertRomanToArabicNumber(String roman) {
        int sum = 0;
        for (char ch : roman.toCharArray()) {
            if (romanSymbols.containsKey(ch)) {
                sum += romanSymbols.get(ch);
            } else {
                throw new IllegalArgumentException(
                        String.format("Illegal roman character %s", ch));
            }
        }
        return sum;
    }

テストケースを実行すると、緑色の状態になります。

今度は、設計、パフォーマンス、およびクリーンなコードのための別のコードの匂いがします。HashMapの実装はHashtableと比較して非同期のため、Hashtableの代わりにHashMapを使用することをお勧めします。このようなメソッドを大量に呼び出すと、パフォーマンスが低下します。

設計のヒントは、一般的なインターフェイスを常にターゲットタイプとして使用することです。これは、より簡単に管理できるクリーンなコードだからです。コード内での使用に影響を与えずに詳細の実装を変更するのは簡単です。今回の場合は、Mapを使用します。

private static Map<Character, Integer> romanSymbols = new HashMap<Character, Integer>() {
        private static final long serialVersionUID = 1L;
        {
            put('I', 1);
            put('V', 5);
        }
    };

Java 9以降を使用している場合は、ダイヤモンド演算子がJava 9以降の無名内部クラスで機能するため、new HashMap<Character, Integer>()をnew HashMap<>()に置き換えることができます。

あるいは、もっと単純なMap.of()を使うこともできます。

Map<Character, Integer> romanSymbols = Map.of('I', 1, 'V', 5,'X', 10, 'L', 50,'C', 100, 'D', 500,'M', 1000);

java.util.Vectorjava.util.Hashtableは廃止予定となっています。まだサポートされていますが、これらのクラスはJDK 1.2コレクションクラスで廃止予定となりました。新しい開発では使用しないでください。

このリファクタリングの後、私たちはすべてが問題ないこと、そして何も壊さなかったことを確認する必要があります。ヤッホー、コードは緑です!

ステップ12

変換するいくつかの興味深い値を追加しましょう。テストクラスに戻り、ローマ字のVIを6に変換します。

メソッド: convertVI()

テストケースを実行して、それが成功するのを見ます。私たちが入れたロジックでこのケースを自動的にカバーされているようです。実装なしで、我々は再びこれを追加作業なしで手に入れました。

ステップ13

今、私たちはIVを4に変換する必要があります。それはおそらくVIを6に変換するのと同じようにスムーズにというわけにはいきません。

メソッド: convertIV()

テストケースを実行したところ、予想どおり結果が赤になりました。

テストケースに合格する必要があります。ローマ字表記では、小さい値の文字(Iのような)が大きい値の文字(Vのような)の前に追加されると、合計数値が減少することがわかります。IVは4に等しく、VIは6に等しくなります。

常に値を合計するようにコードを作成しましたが、テストに合格するには、減算を作成する必要があります。前の文字の値が現在の文字の値以上であれば、それは加算であり、そうでなければ減算です。

変数の宣言を気にせずに、頭に浮かぶように問題を解決するロジックを書くのは生産的です。ロジックを完成させてから、実装を満たす変数を作成します。私が以前に説明したように、これは意図的なプログラミングです。このようにして、私たちはすべてを前もって考えることをせずに、より速い方法で最小のコードを常に導入します。これがMVPの概念です。

現在の実装は以下のとおりです。

public int convertRomanToArabicNumber (String roman) {
        roman = roman.toUpperCase();
        int sum = 0;
        for (char chr : roman.toCharArray()) {
            if (romanSymbols.containsKey(chr))
                sum += romanSymbols.get(chr);
            else
                 throw new IllegalArgumentException(
                        String.format("Invalid roman character %s ", chr));
        }
        return sum;
    }

ローマ字の妥当性のチェックでは、私達は新しいロジックを始めます。ロジックを普通に書いてから、IDEのヒントを使ってローカル変数を作成します。さらに、どのような種類の変数であるべきなのかということを感じています。それらは、メソッドまたはインスタンス/クラス変数に対してローカルです。

        int sum = 0, current = 0, previous = 0;
        for (char chr : roman.toCharArray()) {
            if (romanSymbols.containsKey(chr)) {
                if (previous >= current) {
                    sum += current;
                } else {
                    sum -= previous;
                    sum += (current - previous);
                }
            } else {
                throw new IllegalArgumentException(
                        String.format("Invalid roman character %s ", chr));
            }

現在の変数と以前の変数にアクセスできるように、forループをインデックスベースに変更する必要があります。したがって、正常にコンパイルするために、新しい変更を満たすように実装を変更します。

for (int index = 0; index < roman.length(); index++) {
            if (romanSymbols.containsKey(roman.charAt(index))) {
                  current = romanSymbols.get(roman.charAt(index));
                  previous = index == 0 ? 0 : romanSymbols.get(roman.charAt(index-1));
                if (previous >= current) {
                    sum += current;
                } else {
                    sum -= previous;
                    sum += (current - previous);
                }} else {
                throw new IllegalArgumentException(
                        String.format("Invalid roman character %s ", roman.charAt(index)));
            }

今、私たちはこの新しい機能を追加した後にテストケースを実行すると、グリーンになります。完璧です。

ステップ14

緑の状態では、リファクタリングの準備ができています。もっと面白いリファクタリングを試みるつもりです。

私たちのリファクタリング戦略は、常にコードを単純化しようとすることです。ここで、romanSymbols.get(roman.charAt(index))の行が2回現れることがわかります。

ここで使用するメソッドまたはクラスに重複したコードを抽出して、今後の変更が1か所になるようにしましょう。

コードを強調表示して右クリックし、NetBeansリファクタリングツール > 導入 > メソッドを選択します。それにgetSymbolValueという名前を付けてプライベートメソッドとして、次にOKを押します。

この時点で、テストケースを実行して、小さなリファクタリングでバグが発生することはなく、コードが破損することもないことを確認する必要があります。まだ緑色の状態です。

ステップ15

私たちはもっとリファクタリングをするつもりです。ステートメントの条件romanSymbols.containsKey(roman.charAt(index))は読みにくく、if文をパスするために何をテストするべきかを判断するのは容易ではありません。読みやすくするためにコードを単純化しましょう。

このコード行が何をしているのか今は理解していますが、6ヵ月後には何をしようとしているのかを理解するのが困難になることを保証します。
アジャイルでは頻繁かつ迅速にコードを変更するため、読みやすさはTDDで向上させなければならない中心的なコード品質の1つです。また、迅速な変更を行うには、コードを読みやすくなければならない。そしてどんな変更もテスト可能であるべきです。

このコード行を抽出し、それが何をするかを記述しているdoesSymbolsContainsRomanCharacterという意味のある名前のメソッドにしましょう。そして、これまでと同様にNetBeansのリファクタリングツールを使用してそれを行います。

こうすると、コードが読みやすくなります。条件は次のとおりです。シンボルがローマ字を含む場合はロジックを実行し、それ以外の場合は無効文字を表すillegal-argument例外をスローします。

すべてのテストを再度実行しても、新しいバグは見つかりません。

テストを頻繁に実行していることに注目してください。小さなリファクタリングの変更を加えたらすぐにです。私がやろうとしているすべてのリファクタリングが終わるまで待ちません。終わったら、すべてのテストケースをもう一度実行します。これはTDDに不可欠です。私たちは素早くフィードバックが欲しいですし、テストケースを実行することが私たちのフィードバックループです。それによって、できる限り早く、そして、多くのステップの後でなく小さいステップで、どんなエラーも検出することができるようになります。

リファクタリングの各ステップで新しいバグが発生する可能性があるため、コード変更とコードテストの間隔が短いほど、コードをすばやく分析して新しいバグを修正できます。

100行のコードをリファクタリングしてからテストケースを失敗させた場合、問題がどこにあるのかを正確に検出するためにデバッグに時間がかかります。5行または10行のコード変更を見るよりも、100行を精査したときの方が、エラーを発生させているコードの行を見つけるのが難しくなります。

ステップ16

紹介した2つのプライベートメソッドと例外メッセージの中に重複したコードがあります。この重複行はroman.charAt(index)であるため、NetBeansを使用してgetCharValue(String roman, int index)という名前で新しいメソッドにリファクタリングします。

すべてのテストを再実行すると、すべてが緑のままです。

ステップ17

それでは、コードを最適化してパフォーマンスを向上させるためのリファクタリングを行いましょう。変換メソッドの計算ロジックを単純化することができます。現在は次のとおりです。

int convertRomanToArabicNumber(String roman) {
        roman = roman.toUpperCase();
        int sum = 0, current = 0, previous = 0;
        for (int index = 0; index < roman.length(); index++) {
            if (doesSymbolsContainsRomanCharacter(roman, index)) {
                current = getSymboleValue(roman, index);
                previous = index == 0 ? 0 : getSymboleValue(roman, index - 1);
                if (previous >= current) {
                    sum += current;
                } else {
                    sum -= previous;
                    sum += (current - previous);
                }
            } else {
                throw new IllegalArgumentException(
                        String.format("Invalid roman character %s ",
                                getCharValue(roman, index)));
            }
        }
        return sum;
    }

このラインを改善することで、無駄なCPUサイクルを2、3節約することができます。

 previous = index == 0 ? 0 : getSymboleValue(roman, index - 1);

前の文字は単にforループの最後にある現在の文字なので、前の文字を取得する必要はありません。この行を削除して、previous = current;に置き換えることができます。else条件の終わりの計算の後の位置です。

テストケースを実行すると、緑になります。

それでは、計算を単純化して、無駄な計算サイクルをもう少し捨てましょう。if文のテストケースの計算を元に戻し、forループを逆にします。最終的なコードは次のようになります。

for (int index = roman.length() - 1; index >= 0; index--) {
            if (doesSymbolsContainsRomanCharacter(roman, index)) {
                current = getSymboleValue(roman, index);
                 if (current < previous) {
                    sum -= current;
                } else {
                    sum += current;
                }
                previous = current;
            } else {
                throw new IllegalArgumentException(
                        String.format("Invalid roman character %s ",
                                getCharValue(roman, index)));
            }

テストケースを実行します。これも緑になっているはずです。

このメソッドはオブジェクトの状態を変更しないので、静的メソッドにすることができます。また、このクラスはユーティリティクラスなので、継承のためにクローズする必要があります。すべてのメソッドは静的ですが、クラスのインスタンス化を許可しないでください。これを確定するには、プライベートデフォルトコンストラクタを追加し、クラスをfinalとしてマークします。

テストクラスでコンパイルエラーが発生します。それを修正したら、すべてのテストケースを実行すると、再び緑になります。

ステップ18

最後のステップは、テストケースを追加して、コードがすべての要件を満たしていることを確認することです。

  1. convertX()テストケースを追加します。これはX = 10として10を返すはずです。テストを実行すると、シンボルマップにX = 10を追加するまでIllegalArgumentExceptionで失敗します。もう一度テストを実行すると、緑になります。ここにリファクタリングはありません。
  2. convertIX()テストケースを追加すると、IX = 9として9が返されるはずです。テストは成功します。
  3. シンボルマップに次の値を追加するします。L = 50, C = 100, D = 500, M = 1000。
  4. convertXXXVI()テストケースを追加すると、XXXVI = 36として36を返すはずです。テストを実行すると合格します。ここではリファクタリングはありません。
  5. convertMMXII()テストケースを追加すると、2012を返すはずです。テストを実行すると合格します。ここではリファクタリングはありません。
  6. convertMCMXCVI()テストケースを追加すると、1996が返されるはずです。テストを実行すると合格します。ここではリファクタリングはありません。
  7. convertInvalidRomanValue()テストケースを追加すると、IllegalArgumentExceptionがスローされます。テストを実行すると合格します。ここではリファクタリングはありません。
  8. convertVII()テストケースを追加すると、VII = 7として7を返すはずです。しかし、小文字のviiで試してみると、大文字のみを処理するため、テストはIllegalArgumentExceptionで失敗します。これを修正するために、roman = roman.toUpperCase();という行をメソッドの始めに追加します。テストケースをもう一度実行すると、緑になります。ここではリファクタリングはありません。

この時点で、私たちはアート(実装)を完成させました。TDDを念頭に置いて、すべてのテストケースに合格し、すべての要件をリファクタリングで満たすために最小限のコード変更で、優れたコード品質(パフォーマンス、読みやすさ、および設計)を確かなものにできました。

私がそうだったように、あなたがこれを楽しんでくれたことを願っています。そして、これによって、あなたが次のプロジェクトあるいは進行中のタスクでTDDを使い始めることを願います。記事を共有し、好きになり、GitHubのコードにスターを追加することで、言葉を広めるのを手伝ってください。

著者について

Mohamed Taman氏は、Comtradeデジタルサービスのシニアエンタープライズアーキテクトであり、Javaチャンピオン、オラクルの革新的大使、Java SE.next()とJakartaEE.next()のAdopt、JCPメンバー、Was JCP Executive Committeeのメンバー、JSR 354、363 & 373エキスパートグループのメンバー、EGJUGリーダー、Oracle Egypt Architects Clubのボードメンバーです。Javaを書き、モバイル、ビッグデータ、クラウド、ブロックチェーン、DevOpsを愛しています。国際的な講演者であり、「JavaFXの要点」、「クリーンコード、Java SE 9入門」、「JShellを使用したハンズオンJava 10プログラミング」の書籍およびビデオの作者です。そして新しい本「Javaチャンピオンになる秘訣」はWon Dukeチョイス2015、2014を受賞し、そして、JCP outstanding adopt-a-jar participant 2013を受賞しています。

 

この記事に星をつける

おすすめ度
スタイル

BT