Ajile開発手法

イントロダクション

Ajile開発のワークショップに行ってきました。
とても、有意義かつ、勉強になったワークショップでした。
そんなわけで、学んだことをまとめます。

早い話が、こーゆーやり方すると効率的に開発が進められるというものでした。

Ajile開発とは

早い話が、動くものを作りましょう

と言うことですが、はじめからみんなそう考えてるのでは?と疑問に思うと思います。それは歴史的な経緯があります。

歴史的な経緯

昔は、パソコンがなかったので、コードを一度にコードを書ける人(コンピュータにプログラムを実装する人)が限られていました。1人一台のパソコンは無かったので大きなコンピュータしかなく、現代の様にみんなでワイワイとコードをかけませんでした。

しかし時代は変わり1人一台のパソコンを使用して巨大なシステムの実装が可能になり、それが普通になってきました。

昔のやり方

1人しかコードの実装ができないので設計レベルでしっかり作り後はコードを書くだけで良い形をとっていました。が、システムが巨大になり、昔のやり方では追いつかなくなってきました。なので下のように実装してました。

  1. どのように動くのか、仕様を決める
  2. 設計をする
  3. 実装する
  4. テストする
  5. リリース

今のやり方

  1. どのように動くのか、仕様を決める
  2. テスト仕様を作る
  3. 設計をする
  4. 実装とテストを並行して行う
  5. リリース

テストがはじめの方に来ます。

これで何が変わるのか?ズバリ実装が終わってからの仕様偏向が減ります。

仕様変更は、色んな原因で発生しますが、少なくとも設計ミスでの仕様変更はテスト中に発見できるので、作業の手間が減ります。その他色んなメリットがありますが、「やった事がない」という理由で日本には浸透していません。。。(2021年現在)

Wikiを参照すると下のようなものでした。

アジャイルソフトウェア開発手法の多くは、反復 (イテレーション) と呼ばれる短い期間単位を採用することで、リスクを最小化しようとしている。 1つの反復の期間は、プロジェクトごとに異なるが、1週間から4週間くらいであることが多い。
アジャイル開発手法においては、開発対象を多数の小さな機能に分割し、1つの反復 (イテレーション) で1つの機能を開発する(⇒反復型開発)。そして、この反復のサイクルを継続して行うことで、1つずつ機能を追加的に開発してゆくのである。また、各々の反復は、小規模なソフトウェア開発プロジェクトに似ている。なぜなら、計画、要求分析、設計、実装(コーディング)、テスト、文書化といった、ソフトウェアプロジェクトに要する全ての工程を、1つの反復内で行うからである。

そして、まとめると開発の作業範囲を限定して、細かく作業を分けて、その単位で開発を行うということです。

具体的に

例えば、コンソール出力版テキストRPGを作成するプロジェクトがあったとします。その時に作成するべき内容は以下の通りです。※自分で作成するのにこのような仕様にしました。

作成する項目(仕様)(TextRPG)

  1. 戦闘シーンを作成する(単体のモンスターと)
  2. 複数モンスターとの戦闘
  3. 仲間を加え戦闘することができるようにする
  4. プレーヤーがレベルアップできるようにする
  5. ストーリー展開のシーンを表示できるようにする
  6. タイトルを表示をできるようにする
  7. ストーリー展開と戦闘シーンの切り替えをできるようにする
  8. タイトルとストーリー展開シーンの切り替えをできるようにする
  9. タイトル~戦闘シーンまでの切り替えをできるようにする
  10. ストーリー展開シーンの中でストーリーを進行させることができるようにする
  11. 装備などでステータスを、変更できるようにする
  12. 町などで、買い物ができるようにする
  13. アイテムの使用ができるようにする(戦闘シーンも含む)
  14. エンディングを表示できるようにする

上記のように、各仕様に対してその実装を行うのですが、その仕様でちゃんとアプリケーションが動くように作成していきます。つまりは、以下の様なバージョン分けを行い「このレベルで動くアプリケーション」と言う区切りを設けて作り込んで行きます。

そして、ポイントになるのがテスト駆動開発です、それは、各仕様を実装していくのに、先に仕様を決めて、その通りに動くか確認しながら実装していくためです。そして、実装した部分を修正し続けていく必要があるため他に影響が出ないか確認するです。

ここで、テスト仕様から作成し、動きを確かめながら実装してくのもそうですが、修正するときに「テストが通ればどんな修正でもOK」という形にしておけば修正も楽だしレビューだって楽にできる状態になります。
つまり、レビューワーはテスト仕様がちゃんと組まれているか?を確認、テストの実行結果もOKということを確認するだけでよいのです。

「楽」というのは、作業時間が少なくて済むということです。空いた時間はほかの作業を行えばよいので仕事にも余裕ができます。

テスト駆動開発に関してはこちらの記事に詳細を記載しています。

Java ミニゲーム ソース付き 〜テスト駆動開発:文言出力部品を作る〜

JUnitの使い方~テストクラスの作成と実行~

スクラムフレームワーク

アジャイル開発の現場では「スクラム」というフレームワークを使用することが多いようです。リンク先はWikiです。
このフレームワークは、下のようなフローで作業を行うと作業効率がよく、作業員が成長できるというスグれものです。Springなどのフレームワークと違いプログラミング用のフレームワークではありません。

スクラムに登場する役割

  • 開発チーム: プログラマーチーム
  • プロダクトオーナー: 製品の総責任者。 顧客の意思の代表としての役割を担う。
  • スクラムマスター: チーム内外の組織間調停( ファシリテーション )と外部妨害を対処することとされる。顧客側で担当することが多い。

作業その1:バックログの作成

製品に対する「要求」を作成すること、上の仕様がこの部分に当たる。
上の仕様は、大まかなものなので、これらの仕様を実現するための仕様(タスク)を実現するために「スプリント」という期間を作り、対応する。

作業その2:スプリントの作成

スプリントは上記の「要求」を実現するための「タスク」を切り出して、特定の期間内で完了するための作業です。バックログを作成(タスク切り出し)→スプリント(実行項目切り出し)というような流れになります。

スプリントプランニング

  1. チームはスプリントで実現するバックログの項目を選択
  2. 選択したバックログ項目を実現するためのタスク化を行う
  3. チームが共同でタスク化する過程で、チーム内メンバーの認識差異がないことを最終確認する。

デイリースクラム

  1. 毎日スクラム会議を開く。
  2. 平日の決まった時間に決まった場所で行う。また、15分以内で完了させなければならない。
  3. スクラムマスターは、必ず出席、チーム全員に対して、以下の質問を行う
      1. 「前回のスクラム会議以降、何をしたか」
      1. 「問題はあるか」
      1. 「次回のスクラム会議までに何をするか」
  4. 問題があると報告された場合、スクラムマスターは、即座に意思決定する責任を負う。問題が外的要因によるものである場合、スクラムマスターが、その解決の責任を負う。

プロダクトバックログ・リファインメント
実際に行う作業の見積もりを行う、作業量がどのくらいか?期間内で完了できるか?を見積もるのにポイントを振り分ける。
初めて、行う作業に関しては、実際にポイントを振って行いその結果「XXポイント」でどのくらいの時間が必要だったか?の平均をとり、おおよその見積もりを取る。
(例)

  1. タスクA,B,Cとあったときに基準にするタスクを決めポイントを割りふる。この時は直感で判断して良い。そして。チーム内でそのポイントを割り振った理由を言う
  2. 基準にしたタスクから「これぐらい」という見積もりをチームのメンバー内で話し合いで決める。この時に時間をかけないように注意する。
  3. 実行してみて、その結果、ポイントの消化がどれくらいでできたか?の平均値を取り基準にする

スプリントレビュー
スプリントレビューでは、スプリントで開発されたソフトウェアのレビューが行われ、必要に応じて、新たなバックログ項目が追加される。つまり、参加者の中に顧客がいるのでレビュー時に追加の要望などが出てきたりする。
このレビューには、顧客、マネージャ、開発者が参加する。
なお、場合によっては、営業やマーケティング関係者も参加する場合もある。

スプリント・レトロスペクティブ(振り返り)
振り返りを行う。その振り返りのフレームワークとして以下のものがある。
各項目に対して意見をメンバーで出して、それを話し合い次のスプリントへつなげる

  • KPT: Keep, Problem, Try
  • YWT: やったこと、わかったこと、次にやること
  • FDL: Fun(面白かったこと), Done(やったこと), Learn(学んだこと)

モブプログラミング

実装者一人、他は全部レビューワー。の形で実装を進めるプログラミング方法。

実装者の技術レベルが近く、互いに切磋琢磨できる関係ができていればとても有意義なプログラミング手法。

実際にやってみて面白かった。

また、技術レベルが離れている状態で行った場合には実践的な指導ができる。

ペアプログラミング

これも、上記同様実装者一人、レビューワー一人で実装を行う。

まとめ

スクラムフレームワークを使用してチーム開発を行うときに下のようなツールを使用するとやり易い。

  1. Gitなどのバージョン管理ソフト
  2. 意見交換するためのツール、ウェブホワイトボード、スケッチブックなど
  3. 時間の管理を行うタイムキーパー
  4. チームの活動を見守る人

自分の参加したワークショップは仕事ではなく、「学習」だったのでとても楽しかったが「仕事だと楽しくないよな。。。」という意見もありました。

最も作業効率が良く、生産性の高い現場というのは、「楽しく仕事ができる」というところだと思いました。

また、テスト駆動開発がちゃんと出来ていれば、仕様変更に対してもすぐに対応可能だという事に気がつきました。

具体的には、仕様変更があったときに、どの部分を修正するか確認したら、既にあるテストクラスに修正点を確認するコードを書き、それが通れば仕様変更完了になる。という事です。

もちろん。自動テストで、周りに影響がない事を確認します。

でわでわ。。。

関連ページ一覧

EclipseセットアップWindows版

Eclipse セットアップ

  1. Java Install Eclipse〜開発ツールのインストール〜
  2. TensorFlow C++環境〜EclipseにCDTをインストール〜
  3. Setup OpenGL with Java〜JOGLを使う準備 for Eclipse〜
  4. Eclipse Meven 開発手順〜プロジェクトの作成〜
  5. Java OpenCV 環境セットアップ(on Mac)
  6. Eclipse SceneBuilderを追加する
  7. JavaFX SceneBuilder 〜EclipseとSceneBuilder連携~

Java Basic一覧

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Git関連

  1. Java Git clone in Eclipse 〜サンプルの取得〜
  2. Eclipse Gitリポジトリの取得 〜GitからソースをPullしよう〜
  3. IntelliJ IDEA Git〜Gitリポジトリからクローン〜

JavaFX関連ページ

  1. Eclipse SceneBuilderを追加する
  2. JavaFX SceneBuilder 〜EclipseとSceneBuilder連携~
  3. JavaFX SceneBuilder〜ボタンにメソッドを割り当てるワンポイント〜
  4. Java プロコンゲーム 〜見た目の作成(SceneBuilderの使用)〜

ステップアップ関連ページ一覧

  1. Java 初めてでも大丈夫〜ステップアッププログラミングのススメ〜
  2. ステップアッププログラミング〜Java FxでHelloWorld解説〜
  3. Java StepUpPrograming〜JavaFX で四則計算〜
  4. Java StepUpPrograming〜JavaFXで画面切り替えを作る1〜
  5. Java StepUpPrograming〜JavaFXで画面切り替え2ボタン作成〜
  6. Java StepUpPrograming〜JavaFXで画面切り替え3アクション〜
  7. Java StepUpPrograming〜JavaFXで画面切り替え4Pane切り替え〜
  8. Java StepUpPrograming〜JavaFXで画面切り替え5WebEngine

JavaFX + ND4Jで機械学習準備

  1. JavaFX + ND4J〜数学への挑戦1:ND4Jのインストール〜
  2. JavaFX + ND4J〜数学への挑戦2: 行列の計算〜
  3. Java + ND4J 〜数学への挑戦3: ベクトル(配列)の作成方法〜

オブジェクト指向関連ページ

  1. [オブジェクト指向の概念1〜OracleDocのチュートリアル1〜](https://zenryokuservice.com/wp/2019/10/301. /%e3%82%aa%e3%83%96%e3%82%b8%e3%82%a7%e3%82%af%e3%83%88%e6%8c%87%e5%90%91%e3%81%ae%e6%a6%82%e5%bf%b5-%e3%80%9coracledoc%e3%81%ae%e3%83%81%e3%83%a5%e3%83%bc%e3%83%88%e3%83%aa%e3%82%a2%e3%83%ab%ef%bc%91/)
  2. オブジェクト指向の概念2〜クラスとは〜

Java ミニゲーム ソース付き 〜テスト駆動開発:文言出力部品を作る〜

今回は、RPGゲームでよくある文字を1文字ずつ読めるスピードで出力する部品を作ります。

今まで作成したテキストRPGゲームに追加します。
現状は、こんな感じで動きます。

文字の出力は、一括で出力するのでゲームっぽくありません。どちらかというとログ出力っぽいです(笑)

文字出力部品を作る

現状は、System.out.println()を使用しています。これは引数にある文字列を標準出力に出力します。
標準出力 = System.outになります。

問題

どーやって1文字ずつ出力するか?
とりあえずは、テスト駆動開発にしますので、テストケースを作成します。

毎度おなじみJUnit

JUnitを使用してテストクラスを動かします。

作成手順

  1. Eclipseのプロジェクトに「test」フォルダを作成する
  2. テストするクラスと同じパッケージを作成する
  3. テストするクラスの後にTestをつけたクラスを作成する
  4. 〜Testクラスにテストケースを作成する
具体的に。。。

実装した時の動画を作成しました。

詳細は以下のようになります。
上の手順4以降の記載になります。

JUnitをビルドパスに追加


動画にもあるのですが、上のような画面でJUnitを追加します。細かい部分は動画にありますので参照ください。

テストケースを作る

今回のテストケースはコンソールにゆっくりと表示できれば良いので、動かすだけになります。
故に、コードは下のようになります。

public class PrintUtilsTest {
    @Test
    public void testPrint01() {
        String mes = "アイウエオカキクケコ。。。。";
        try {
            PrintUtils.print(mes);
        } catch (InterruptedException e) {
            fail(e.getMessage());
        }
    }
}

今回の作成するクラスは、PrintUtilsクラスです。テストクラスは上のコードにある通りです。

作成するクラスをガワだけ作ったら、あとは、テストケースで呼び出してやる処理を実装してやるだけです。

あとは、実装→起動→修正→確認を繰り返して終了です。

シンプルなもんです(笑)

JUnitを使用したときの再生リストへのリンクです。

JUnitで10進数を2進数に変換する処理を実装

テストファーストで、補数算出メソッドの実装

EclipseでJUnitの起動設定~テストクラスの作成1

EclipseでJUnitの起動設定~テストクラスの作成2

Java テスト駆動で引き算のメソッドを作る

関連ページ一覧

Eclipse セットアップ

  1. Java Install Eclipse〜開発ツールのインストール〜
  2. TensorFlow C++環境〜EclipseCDTをインストール〜
  3. Setup OpenGL with JavaJOGLを使う準備 for Eclipse
  4. Eclipse Meven 開発手順〜プロジェクトの作成〜
  5. Java OpenCV 環境セットアップ(on Mac)
  6. Eclipse SceneBuilderを追加する
  7. JavaFX SceneBuilder EclipseSceneBuilder連携~

Java Basic一覧

  1. Java Basic Level 1 〜Hello Java〜
  2. Java Basic Level2 〜Arithmetic Calculate〜
  3. Java Basic Level3 〜About String class〜
  4. Java Basic Level 4〜Boolean〜
  5. Java Basic Level 5〜If Statement〜
  6. Java Basic Summary from Level1 to 5
  7. Java Basic Level 6 〜Traning of If statement〜
  8. Java Basic Level8 〜How to use for statement〜
  9. Java Basic Level 8.5 〜Array〜
  10. Java Basic Level 9〜Training of for statement〜
  11. Java Basic Level 10 〜While statement 〜
  12. Java Basic Swing〜オブジェクト指向〜
  13. Java Basic Swing Level 2〜オブジェクト指向2〜
  14. サンプル実装〜コンソールゲーム〜
  15. Java Basic インターフェース・抽象クラスの作り方
  16. Java Basic クラスとは〜Step2_1〜
  17. Java Basic JUnit 〜テストスイートの作り方〜

Git関連

  1. Java Git clone in Eclipse 〜サンプルの取得〜
  2. Eclipse Gitリポジトリの取得 GitからソースをPullしよう〜
  3. IntelliJ IDEA GitGitリポジトリからクローン〜

JavaFX関連ページ

  1. Eclipse SceneBuilderを追加する
  2. JavaFX SceneBuilder 〜EclipseとSceneBuilder連携~
  3. JavaFX SceneBuilder〜ボタンにメソッドを割り当てるワンポイント〜
  4. Java プロコンゲーム 〜見た目の作成(SceneBuilderの使用)〜

ステップアップ関連ページ一覧

  1. Java 初めてでも大丈夫〜ステップアッププログラミングのススメ〜
  2. ステップアッププログラミング〜Java FxでHelloWorld解説〜
  3. Java StepUpPrograming〜JavaFX で四則計算〜
  4. Java StepUpPrograming〜JavaFXで画面切り替えを作る1〜
  5. Java StepUpPrograming〜JavaFXで画面切り替え2ボタン作成〜
  6. Java StepUpPrograming〜JavaFXで画面切り替え3アクション〜
  7. Java StepUpPrograming〜JavaFXで画面切り替え4Pane切り替え〜
  8. Java StepUpPrograming〜JavaFXで画面切り替え5WebEngine

JavaFX + ND4Jで機械学習準備

  1. JavaFX + ND4J〜数学への挑戦1:ND4Jのインストール〜
  2. JavaFX + ND4J〜数学への挑戦2: 行列の計算〜
  3. Java + ND4J 〜数学への挑戦3: ベクトル(配列)の作成方法〜

オブジェクト指向関連ページ

  1. [オブジェクト指向の概念1〜OracleDocのチュートリアル1〜](https://zenryokuservice.com/wp/2019/10/301. /%e3%82%aa%e3%83%96%e3%82%b8%e3%82%a7%e3%82%af%e3%83%88%e6%8c%87%e5%90%91%e3%81%ae%e6%a6%82%e5%bf%b5-%e3%80%9coracledoc%e3%81%ae%e3%83%81%e3%83%a5%e3%83%bc%e3%83%88%e3%83%aa%e3%82%a2%e3%83%ab%ef%bc%91/)
  2. オブジェクト指向の概念2〜クラスとは〜

Java はじめて31 〜JUnitでのテスト駆動型開発8: 処理をつなげたテスト〜

イントロダクション

今まではユニットテスト(単体テスト)をやっていましたが、複数のクラスを実行するテストケースも作成することができます。
プログラム的には、実行するプログラムを増やすだけですが。。。

イメージとしては、メインメソッドを動かすテストを実行するようなイメージです。
そうすれば、メインメソッドを動かした方が早いような気がしますが、大掛かりなアプリケーションの場合は、そうもいきません。

なので、「足し算をする処理のテスト」「ファイルを読み込むテスト」とテストを行った後に、口座に入金するテストをするというような形です。

今回のような小さなプログラムでは、あまり意味がありませんが。。。

今回は、今まで作成した1処理を「機能」として動かせることを確認するテストケースを作成します。
通常のやり方では、1.実装、2.テストと分けた場合の「2.テスト」にあたります。

機能テスト

仕様は以下の通りです。

仕様
1. ファイルを操作するためのオブジェクトを作成する
2. ファイルにデータをCSV形式で書き出す。
3. ファイルが存在するのであれば、それを読み込みデータを保持する

今までの実装、テストで行くと「3」が実装したものに相当します。なので、これをテストケースで確認する様にします。
現状では、スタブのままなので未実装になっています。
なのでテストケースを先に作成します。
ちなみに、最終的なコードはGithubにあります。

  1. KozaManager.java
  2. KozaManagerTest.java
@Test
public void testFileRead() {
    if (target.isFile() == false) {
        fail("ファイルが存在していません");
    }
    List<Data> dataList = target.readFile();
    // データは1件
    assertEquals(1, dataList.size());
    // ファイルにあるデータ
    Data data = dataList.get(0);
    assertEquals("test", data.getName());
    assertEquals("passwd", data.getPassword());
}

とりあえずで、上の様に作成しました。初めにファイルの存在チェック処理を確認して、ファイルが存在しないならテスト失敗。
次にファイルの読み込みを行い、そのデータを取得しデータが想定通りのものが入っているか確認します。

余談

テストを複数回行っていたので「koza.csv」にデータが複数作成されていました。ヘッダー付きなのでこれはダメなテストになってしまうので、ちょっと修正します。
<修正1>
「ファイルが存在するときにはファイル作成をしない」が抜けていたのでこれを追加します。

// ファイルそ新規で作成するとき
if (file.exists() == false) {
    // ヘッダー部分の出力
    build.append(this.createCSVHeader());
    // ファイル書き込み処理
    write.write(build.toString());
    write.newLine();
}

そして、修正後のメソッドは以下の様になります。

public void dataOutput(Data data) throws IOException {
    if (file.canWrite() == false) {
        throw new IOException("ファイルの書き込みができません: " + file.getAbsolutePath());
    }
    // おおよそのデータサイズを指定すると余計なメモリを使用しなくて済む
    StringBuilder build = new StringBuilder(50);
    // ファイルを新規で作成するとき
    if (file.exists() == false) {
        // ヘッダー部分の出力
        build.append(this.createCSVHeader());
        // ファイル書き込み処理
        write.write(build.toString());
        write.newLine();
    }
    // StringBuilderのクリア
    build.setLength(0);
    // データ部分の書き込み
    build.append(data.getName() + ",");
    build.append(data.getPassword());
    write.write(build.toString());
    write.newLine();
    write.close();
}

これで、ファイルが存在しているのに新たにファイルを作成することがなくなります。

しかし、テストはうまくいきませんでした。
これの原因は、テストケースにありました。

@Test
public void testFileRead() {
    if (target.isFile() == false) {
        fail("ファイルが存在していません");
    }
    List<Data> dataList = target.readFile();
    // データは1件
    assertEquals(1, dataList.size());
    // ファイルにあるデータ
    Data data = dataList.get(0);
    assertEquals("test", data.getName());
    assertEquals("passwd", data.getPassword());
}

取得したデータのサイズ(ファイルの行数)は1の想定ですが、テストを実行するたびにデータが追加されるので、テストが毎回同じ結果になりません。

これは良くない

なので、ここもちょっと修正します。

// データは0件ではない
assertNotEquals(0, dataList.size());

こんな感じで修正しました。そして、複数件あっても作成されるデータは同じなので初めの一件目が想定通りの値かどうか確認します。ここでちょっと注意「初めの1行目はヘッダー行なのでデータ部ではないですが、取得したときはリストの1番目に設定されています。なので以下の様に修正します。

public void testFileRead() {
    if (target.isFile() == false) {
        fail("ファイルが存在していません");
    }
    List<Data> dataList = target.readFile();
    // データは0件ではない
    assertNotEquals(0, dataList.size());
    // ファイルにあるデータは2行目から
    Data data = dataList.get(1);
    assertEquals("test", data.getName());
    assertEquals("passwd", data.getPassword());
}

こんな感じでファイル読み込みの一連の機能をテストしました。これと同様に、他ものもテストしました。わかりづらいですが、上のキャプチャにテストケースが、4つあります。
ファイル作成、ファイル存在チェックの処理の他は読み込みと書き込みをテストしています。

ちょっと締まりが悪いですが。今回はこれまでにします。

ここまでで31回分の学習を行いました。ここまで来たら(もしかすると途中で。。。)自分で考えて学習を進めることができるレベルまで来ているのではないでしょうか?「いや?」という声がないと良いのですが、「物事を伝えるのは至難」とも言います。また別な角度で同じことを記述したいと思います。
Jaba Basic Lv1」と称して再度記載しました。こちらも併せてご利用ください。

でわでわ。。。

<<< 前回

<Java関連の動画リスト>

<JUnit関連の動画リスト>



Java はじめて30 〜JUnitでのテスト駆動型開発7: テストクラスの実装方法〜

イントロダクション

前回までは、部分的にテストケースなどを作成してきましたが、テストケースの作成をするために必要なこと〜実装までを記載したいと思います。

今回は、まとめ的に、JUnitでの実装をやります。今までは、コードを書き終わったものを記載していましたが、今回はコードを書きながら記載していこうと思います。自分のやり方になりますが。。。

テストクラスの実装方法

仕様確認1

  1. koza.csvファイルが存在するときはファイルを読み取り、それをDataクラスに設定し、リストにして返す
  2. koza.csvがない場合は、システムエラーを出す

上の仕様を満たす様に、テストケースを考えます。シンプルなもので、単純に実装すれば良さそうです。
「〜そうです。」と記載したのは、実装などはやってみないとわからないこともあるのでこの様に記載しました。早速作ってみます。

テストケースを作る

まずは、単純にテストクラスを作成します。このクラス名は「テスト対象クラスの名前+Test」という形で作成します。
テストクラスと実装するクラスのセットが、そうするとわかりやすいからです。すでに実装していますが「初めから作る」という前提で記載します。

※ 「スタブ」というのは空実装、つまりメソッドの名前、引数、返り値を書いているけど中身のない状態です。
```java
/** スタブ実装 */
public void stubMethod() {
}
```

作成するクラス
KozaManager(実装クラス)
KozaManagerTest(テストクラス)

<実装クラス>

public class KozaManager {

    public Listl<Data> fileRead() {
        // 実装前なのでスタブにします。
    }

} 

<テストクラス>

public class KozaManagerTest {
    @Test
    public void testFileRead() {
        // これもスタブ
    }
}

現状は、上記の様な状態です。
そして、考えます。何を?。。。仕様の1番目「koza.csvファイルが存在するときはファイルを読み取り、それをDataクラスに設定し、リストにして返す」にはどの様なテストが必要か?を考えます。単純に考えるならば以下の様になります。

  1. ファイルの存在を確認する処理が想定通りに動くか?
  2. ファイルが存在するならば、それを読み込みデータリストを返却するか?

そして、もう1つの仕様「koza.csvがない場合は、システムエラーを出す」に関しては

  1. ファイルが存在しないときはシステムエラーを出し、アプリケーションを終了するか?

それぞれ、上の様なチェック(テスト)を行います。ぶっちゃけて、まずはこの単純な確認を行ってから次に「穴はないか?」を考えれば良いと思います。

なのでシンプルにその様なテストケースを作成します。
まずは、「ファイルの存在を確認する処理が想定通りに動くか?」これを確認するために実装クラスとテストクラスの両方を修正します。着手するのは1つずつなので安心ください。
ますは、テストケース(テストクラス)
<テストクラス>

public class KozaManagerTest {
    @Test
    public void testIsFile() {
        // ファイルの存在チェック処理の確認
    }
    // 今はテストしないのでコメントアウト
    // @Test
    public void testFileRead() {
        // これもスタブ
    }
}

とりあえずは、ファイルの存在チェック処理を動かすためのテストケース(メソッド)を作成しました。
ここで、足りないものがあります。

  1. テスト対象クラス(実装するクラス)
  2. ファイル存在チェック処理メソッド

通常の開発だと、これらを実装してからテストしますが。先に箱(スタブ)だけ作ってしまいます。
<実装クラス>

public class KozaManager {
    public boolean isFile() {
        // とりあえずスタブです。ファイルが存在していることを確認する
    }
    public Listl<Data> fileRead() {
        // 実装前なのでスタブにします。ファイルを読み込めることを確認する
    }

} 

そして、ちょっと考えてみると「ファイルの存在チェック処理を行うのに、ファイル名が必要だな。。。」と気がつくと思います。

ここが悩みどころ

ファイル名を(引数で)渡す処理方法と、ファイル名を固定して渡す方法のどちらもできるので「どちらの手段が汎用性、保守性共に高いだろうか?」と悩んでみます。
自分の結論は「引数でファイル名を渡す」方が良いと思ったのですが、すでに実装ているコードではコンストラクタでCSVファイルを読み込んでいるので今回は、引数なしでのメソッドにします。つまり、処理の順序的にCSVファイルを読み込んでから呼び出す処理になっているという意味です。コンストラクタの実装は下のように作っています。※設計的にビミョウな感じがします。。。
```java
public class KozaManager {
/** ファイルへの書き出しクラス */
private BufferedWriter write;
/** ファイルの読み込みクラス */
private BufferedReader read;
/** 取得(作成)するファイルクラス */
private File file;

/** コンストラクタ */
public KozaManager() {
// 操作するファイルを指定する
file = new File(FILE_PATH);
try {
write = new BufferedWriter(new FileWriter(file, true));
if (file.exists()) {
read = new BufferedReader(new FileReader(file));
}
} catch (IOException ie) {
ie.printStackTrace();
System.out.println("ファイルオープンに失敗しました。" + ie.getMessage());
System.exit(-1);
}
}
}
```

なので以下の様なコードになります。
<実装クラス>

public class KozaManager {
    /** 作成するファイルが存在するかチェック */
    public boolean isFile() {
        // fileはフィールド変数
        return file.exists();
    }
    public Listl<Data> fileRead() {
        // 実装前なのでスタブにします。
    }
} 

<テストクラス>
テストの実行前に、テスト対象クラスのインスタンスを作成(newする)し、フィールド変数にセットします。
つまり、以下の順序で処理が走ります。

1. 「\@Before」アノテーションのついているメソッド
2. 「\@Test」アノテーションのついているメソッド

テストケースの数だけ「\@Before」が動きます。

public class KozaManagerTest {
    @Before
    public void initTest() {
        target = new KozaManager();
    }

    @Test
    public void testIsFile() {
        // コンストラクタでファイルが作成されることに注意
        assertTrue(target.isFile("koa.csv"));
    }

    // 今はテストしないのでコメントアウト
    // @Test
    public void testFileRead() {
        // これもスタブ
    }
}

実行結果は下にあります。

まとめ

「テストケースを作る=実装を行う」順序

1.作成するプログラムの仕様を確認する

  1. 仕様を満たすテストケースを考える
  2. 考えたテストケースを実装する(スタブ)※テストする内容をコメントしておくと良い
  3. 実行するクラス(KozaManager)を実装する
  4. テストケースを実行(KozaManagerTest)して、処理が正しいことを確認

クラス、メソッドの依存度が高いと右を修正した後に左を修正。。。と無限ループすることがあるのでそれぞれの処理が(なるべく)独立するように作りましょう。

でわでわ。。。

<<< 前回 次回(ラスト) >>>

<Java関連の動画リスト>

<JUnit関連の動画リスト>



Java はじめて29 〜JUnitでのテスト駆動型開発6: ファイル読み込みのテスト〜

イントロダクション

前回は、ファイルの出力を実装しました。
もちろんテストケースありきの実装です(テスト駆動開発)。ここまでくると、だんだんと実装とテストと慣れてくると思います。
横っ飛びして、別なプログラムを作成し始めてみるのも1つです。

わからなくなったら、また調べれば良いのです。

今回は、今迄作成した「ファイル存在チェック」と「ファイル作成、更新」処理を繋げてテストします。

この段階で、ようやくテストらしいテストになります。ちなみにこのテストケースも実装前に作成出来ますが、小さなレベルで、テスト仕様を考える→実装→テスト実施…とやった方が早い気がします。

口座作成処理

ようやく、仕様レベルの話に戻って来ました。このテストケースはコーダー銀行に口座を開設する処理に当たりますので、実際に動かす時と同じような入力(テストなのでデータ)を渡して実行します。テストコードは下のようになります。

このテストケースが通ったらとりあえずは、製造完了です。お疲れ様でした。

ひと段落したので、一旦休むも良し、このまま突走るのも良し。

他の事に着手してみるも良し!

動く事が大事

何が言いたいかと言うと、プログラムは何かを作る、仕組みを作るなどに使用する。若しくは単純に触りたいだけ…など色々あると思いますが、ちゃんと動くモノを作った方が、実力もつくし、作ったもので遊べるし、良い事ずくめだと思うので、色々と作ってみるのが良いと思います。

今迄やって来ているのは、サンプルなので…主な目的は「Javaの学習」になっています、あまり作りたいものには届かないと思います。しかし、基礎を学べる様になっているのと、コンソールアプリなので応用はいくらでも出来ます。早い話が、コンソールアプリが作れれば、あとは環境が違うだけなので大まかな流れは理解できるずです。。。

続き

前置きは、このくらいにして、続きをやります。

早い話が、今迄作成したメソッドを続けて呼び出し、それらが想定通りに動くか?の確認を行います。

作成したメソッドは以下のものです。

そして、上のメソッドを結合してやる場合のテストケースから考えます。

データの読み込み

仕様

  • ファイル存在チェックを行いファイルが存在すれば読み込む
  • 存在しなければファイル作成を行う

仕様に関してはこの部分のみをみていましたが、ファイルの読み込みをした後に、データが入っているはずなので、これを取得する必要があります。

この部分は実装していないので、実装が必要です。とりあえずは、目の前のテストを片付けてしまいます。テストコードは下の様になりました。テストを実施しながら実装するので手直しをするからこうなりました。と言う表現を使いました。

テストが通ったので、次の事を考えます。つまり、ここまでは想定通りに行ったという事です。

データの取得

次は、読み込んだファイルのデータをクラスに設定してやる事を考えます。

ファイルに保存してあるデータを読み込んで、プログラムで使いやすい様にデータクラスを使ってデータを保持します。「保持」と言うのは、プログラムが止まると消えてしまう情報なので「保持」と言う言葉を使いました。

ちなみに、ファイルに保存すれば、プログラムが止まっても情報は残しておけます。

現状では、Dataクラスを使用してファイルにデータを書き込んでいます。
ちなみにデータクラスは下の様なコードになります。

<補足>
データクラスとは:クラスにフィールド変数、ほかはGetter,Setterが定義してあるだけのクラスのことです。
わりかし身近なものとしては、ログインするときのユーザー情報クラスがこれに当たります。設計思想によりユーザー情報クラスに何かしらの処理を持たせることがあるかもしれませんが、筆者が作るのであれば、ユーザークラスの親(スーパー)クラスに持たせます。

はっきり言って、設計思想のレベルなのでどちらが良いとかっていう話ではありません。。。

public class Data {
    /** ユーザー名 */
    private String name;
    /** パスワード */
    private String password;

    /** 
     * コンストラクタ。口座情報を保持するクラスのインスタンスを生成。
     * 
     * @param name 口座のユーザー名
     * @param password 口座のパスワード
     */
    public Data(String name, String password) {
        this.name = name;
        this.password = password;
    }
    /**
     * @return the name
     */
    public String getName() {
        return name;
    }
    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }
    /**
     * @return the password
     */
    public String getPassword() {
        return password;
    }
    /**
     * @param password the password to set
     */
    public void setPassword(String password) {
        this.password = password;
    }
}

読み込み時にもこのクラスが使えます。
まとめると以下の様な手順でファイルを読み込みます。

  1. ファイルを開く(コンストラクタで実装ぞ済み)
  2. ファイルを1行ずつ読み込みDataクラスへ設定
  3. データクラスをリストにして返却する

以上の様な手順でファイルからデータを取得します。
そして、これをテストケースにします。

テスト仕様

上の手順そのままですがこれを確認するためのテストケースを作成します。そしてこれもとりあえずは「スタブメソッド」を作成します。

/**
 * ファイルを読み込みデータをリストにして返却する
 * @return List<Data> CSVファイルのデータリスト
 */
public List<Data> readFile() {
    return null;
}

このメソッドはJavaDocコメントにある様に、リストにしてデータを返しますので、java.util.Listインターフェースを使用してデータを返却します。
Listインターフェースの配列型リスト=ArrayListクラスを生成してデータをセットする様に実装します。コードは以下の様になります。

public void testFileRead() {
    List<Data> dataList = target.readFile();
    // データは1件
    assertEquals(1, dataList.size());
    // ファイルにあるデータ
    Data data = dataList.get(0);
    assertEquals("test", data.getName());
    assertEquals("passwd", data.getPassword());
}

このテストケースを実行しエラーが出ない様なコードを作成します。一応、CSVファイルはファイル作成時に出力したデータが設定されていることを確認しています。

まとめ

テストケースを作成して実行。。。これに関してどんなことをやったのか?に関して記載しました。
「データクラス」というのが新しい単語として出てきましたが、上記の通り「フィールド変数、Getter,Setterのみのクラス」というだけです。

でわでわ。。。

<<< 前回 次回 >>>

<Java関連の動画リスト>

<JUnit関連の動画リスト>

Java はじめて28 〜JUnitでのテスト駆動型開発5: ファイル出力の実装〜

イントロダクション
前回は実装するべきクラスの実装を行いました。しかし、ファイル入出力の部分は手付かずだったので、その部分を実装しようと思います。

<JUNITの使い方>


今回は、ファイル出力処理を実装します。つまりテストケースの作成と実行を行います。記事の下に動画があるので、参考にどうぞ、

ファイル出力の実装

ファイル出力と言っても、ファイルの作成自体はすでに実装済みなのでデータをファイルに書き込む処理の実装ということになります。
<前回までにできていること>

  1. ファイル作成(koza.csv)
  2. ファイルの存在チェック(KozaManager#isFile)

補足として、ファイルの作成 => new File()ファイルへの書き込み => Writer.write()

ここまでが実装したものになります。ちなみにテストコードは下の様になります。

public class KozaManagerTest {
    /** テスト対象クラス */
    private KozaManager target;
    /**
    *  テストの初期化
    *  各テスト実行前に起動する
    */
    @Before
    public void initClass() {
        target = new KozaManager();
    }

    /**
     * テスト対象クラスのメモリ開放
     */
    @After
    public void terminate() {
        target.finalize();
    }
    /**
    * コンストラクタが起動したかどうかを確認する
    * テストケース
    */
    @Test
    public void testIsInstance() {
        assertNotNull(target);
    }
    /**
    * ファイルの存在チェック処理の確認
    */
    @Test
    public void testIsFile() {
        assertTrue(target.isFile());
    }
    /**
    * ファイルにデータを出力し保存するテストケースです。
    */
    public void testFileCeate() {
        Data data = new Data("名前", "パスワード");
        try {
            target.dataOutput(data);
        } catch(IOException ie) {
            ie.printStackTrace();
            fail();
        }
    }
}

ここでの注意点は、「@Test」のついていないテストケース(メソッド)がファイル出力のテストケースになります。
なので、今回はこのコードを実行しようと思います。
早速「@Test」を上のテストケースにつけます。

/**
* ファイルにデータを出力し保存するテストケースです。
*/
@Test
public void testFileCeate() {
    Data data = new Data("名前", "パスワード");
    try {
        target.dataOutput(data);
    } catch(IOException ie) {
        ie.printStackTrace();
        fail();
    }
}

想定通りにいかない原因

製造中につきものの「動かない。。。」にあたります。
本当であれば、テストコードの中でファイルの中身を確認するのですが、ファイル出力後のテストケースも考えていないので、とりあえず。。。と言ったところです。
さて、想定通りにいかない原因を考えます。というか探します。本体のコードを見直してみると。。。

public void dataOutput(Data data) throws IOException {
    // おおよそのデータサイズを指定すると余計なメモリを使用しなくて済む
    StringBuilder build = new StringBuilder(50);
    // ヘッダー部分の出力
    build.append(this.createCSVHeader());
    // ファイル出力処理
    write.append(build.toString());
    // StringBuilderのクリア
    build.setLength(0);
    // データ部分の出力
    build.append(data.getName() + ",");
    build.append(data.getPassword());
}

「あー、これはファイルの中身がなくて当然だなぁ」と気がつきます(笑)
上のコードでは、出力用文字列作成用のオブエジェクト(StringBuilder)を作成しているだけです。下のようにStringBuilderに文字列を追加しないと出力するべきデータがない状態になります。

build.append(data.getName() + ",");
build.append(data.getPassword());

なので修正します。

public void dataOutput(Data data) throws IOException {
    // おおよそのデータサイズを指定すると余計なメモリを使用しなくて済む
    StringBuilder build = new StringBuilder(50);
    // ヘッダー部分の出力
    build.append(this.createCSVHeader());
    // ファイル書き込み処理
    write.write(build.toString());
    // StringBuilderのクリア
    build.setLength(0);
    // データ部分の書き込み
    build.append(data.getName() + ",");
    build.append(data.getPassword());
    write.write(build.toString());
}

そして、ファイルに出力したらファイルの入出力で使用しているReaderとWriter、そのほかのフィールド変数の後始末をする必要があります。でないとメモリ上にインスタンスが残ってしまいます。Javaの場合はガベージコレクションで最終的にメモリは解放されますが、ここでは明示的に「KozaManger」クラスのインスタンスが解放されるときに各フィールドの解放を行います。(デストラクタに実装)※finalize()を呼び出しましょう。

/** デストラクタ */
@Override
protected void finalize() throws Throwable {
    // フィールド変数の後始末
    file = null;
    read.close();
    write.close();
    write = null;
    read = null;
}

それでは、改めてテストを実行してみます。しかし想定通りに行きませんでした。。。
ファイルは出力しているのですが、肝心の中身が出力されていない状態でした。
原因としては「ファイルを閉じていない」と「ファイルを開くときに追記モードで開いていない」ことでした。
そして、最終的には以下の様なコードになりました。

public void dataOutput(Data data) throws IOException {
    if (file.canWrite() == false) {
        throw new IOException("ファイルの書き込みができません: " + file.getAbsolutePath());
    }
    // おおよそのデータサイズを指定すると余計なメモリを使用しなくて済む
    StringBuilder build = new StringBuilder(50);
    // ヘッダー部分の出力
    build.append(this.createCSVHeader());
    // ファイル書き込み処理
    write.write(build.toString());
    write.newLine();
    // StringBuilderのクリア
    build.setLength(0);
    // データ部分の書き込み
    build.append(data.getName() + ",");
    build.append(data.getPassword());
    write.write(build.toString());
    write.newLine();
    write.close();
}

というわけで、テストをしながら実装するのでやりやすい(色々と試しやすい)実装方法だと思います。
ちなみにテストケースは、色々と試したので少々変わっています。

@Test
public void testFileCeate() {
    Data data = new Data("test", "passwd");
    try {
        target.dataOutput(data);
    } catch(IOException ie) {
        ie.printStackTrace();
        fail("ファイル入出力に問題があります。");
    } catch(Exception e) {
        e.printStackTrace();
        fail("想定外のエラーが起きました。");
    }
}

そして、本体のクラス(メソッド)も同様に修正が入っております。最終的に以下の様な実装になりました。

public void dataOutput(Data data) throws IOException {
    if (file.canWrite() == false) {
        throw new IOException("ファイルの書き込みができません: " + file.getAbsolutePath());
    }
    // おおよそのデータサイズを指定すると余計なメモリを使用しなくて済む
    StringBuilder build = new StringBuilder(50);
    // ヘッダー部分の出力
    build.append(this.createCSVHeader());
    // ファイル書き込み処理
    write.write(build.toString());
    write.newLine();
    // StringBuilderのクリア
    build.setLength(0);
    // データ部分の書き込み
    build.append(data.getName() + ",");
    build.append(data.getPassword());
    write.write(build.toString());
    write.newLine();
    write.close();
}

後、ファイルを開くときに追加書き込みモードで開く必要があるので。。。(今回の実装ではデータの保存を行うのでフィールドで保持する必要があるため)コンストラクタの修正も行いました。

// 修正前のコード
write = new BufferedWriter(new FileWriter(file));
// 修正後のコード
write = new BufferedWriter(new FileWriter(file, true));

実行結果は以下の通りです。

次回は、各処理を(ファイル存在チェック〜ファイル出力)テストします。最終的な確認のコードです。

でわでわ。。。

<<< 前回 次回 >>>

<Java関連の動画リスト>

<JUnit関連の動画リスト>



Java はじめて27 〜JUnitでのテスト駆動型開発4: 追加修正と実装〜

イントロダクション

前回までは、テストケースの作成に注力していましたが。今回は、プログラムを動かすためのクラス、実装の部分に入ろうと思います。
ここでは、イメージ的に、テストと実装を同時にやるようなイメージです。

実装

今回は、前回同様に「JUnitでのテストケースから、テストをクリアできる様に実態クラスを作成します。」が最終的にファイルを出力する前に、出力したファイルの存在チェック処理を作る必要があるので、そちらの処理(テストケース)を先に作成します。

追加修正

前回までに作成したものをまとめると以下の様になります。
<口座管理処理の要件>

  1. ファイルを操作するためのオブジェクトを作成する
  2. ファイルにデータをCSV形式で書き出す。
  3. ファイルが存在するのであれば、それを読み込みデータを保持する

<実装済み>
1のオブジェクト作成のみ

<テストケース>
ファイル出力のテストケースとKozaManagerクラス(本体)の実装時に、以下の不足する実装(処理)があることに気がつく

  1. 出力するCSVのヘッダー部分の作成処理
  2. デストラクタ
  3. 作成したファイルの存在確認
  4. ファイルの内容確認

上記の様なことが抜けていたので、作成するテストケースも作成順序が変わってきてしまいました。元の設計に抜けがあった様です。つまり、以下の様な順序で作成することになります。

  1. ファイルの存在チェック処理
  2. ファイルにCSVデータを出力する処理
  3. ファイルの内容確認処理
  4. ファイル出力の処理(上の1-3を全て通して行う確認)

余談ですが、テストケースを作成することで「不足ケース」を見つけることができます。当然不足があれば、テストは想定通りには動きません、プログラムですから「気を利かして。。。」なんてことはやってくれません(笑)

ファイル存在チェック

はっきり言って1行で終わります。
コンストラクタで参照(作成)するファイルオブジェクトは作成しているのでそれを使用します。
なので、もともと下の様なコンストラクタを使っていましたが、修正します。
<元のコード>

public class KozaManager {
    /** ファイルへの書き出しクラス */
    private BufferedWriter write;
    /** ファイルの読み込みクラス */
    private BufferedReader read;

    /** コンストラクタ */
    public KozaManager() {
        // 操作するファイルを指定する
        File file = new File("resources/koza.csv");
        try {
            write = new BufferedWriter(new FileWriter(file));
            if (file.exists()) {
                read = new BufferedReader(new FileReader(file));
            }
        } catch (IOException ie) {
            ie.printStackTrace();
            System.out.println("ファイルオープンに失敗しました。" + ie.getMessage());
            System.exit(-1);
        }
    }

    /** デストラクタ */
    @Override
    protected void finalize() throws Throwable {
        write = null;
        read = null;
    }

    /**
     * データクラスを受け取り、CSVファイルを出力する(書き出しを行う)
     * @param data コーダー銀行のユーザー情報
     */
    public void dataOutput(Data data) {
    }
}

<修正後のコード>
・フィールドを追加します。
・コンストラクタの「file」をフィールド変数のFIleオブジェクトに変更します。

public KozaManager() {
    // 操作するファイルを指定する
    file = new File(FILE_PATH);
    try {
        write = new BufferedWriter(new FileWriter(file));
        if (file.exists()) {
            read = new BufferedReader(new FileReader(file));
        }
    } catch (IOException ie) {
        ie.printStackTrace();
        System.out.println("ファイルオープンに失敗しました。" + ie.getMessage());
        System.exit(-1);
    }
}

本来であれば、ファイルの作成はプログラムからやるのですが、順番的にファイルの存在チェックを先にやるので手で作成します。
以下の様なファイルです。

名前, パスワード
テスト太郎, 1234
テスト花子, 2345
テストたくのじ, 3456

これを「koza.csv」と名前をつけてEclipseのresourcesフォルダの直下に配置(作成)します。。。
が、以前起動したテスト(コンストラクタのテスト)の実行時にファイルが作成されていたので(ファイルの中身は空)、テストを起動した時点でファイルが存在することになります。
なので、テストケースとしては以下の様なものになります。

public void testIsFile() {
    assertTrue(target.isFile());
}

これも1行で終わりました(笑)

しかし、これでテストを実行しても上のコードはテストとして実行されません、なぜなら「@Test」がついていないからです。
なので以下の様にテストクラスを修正します。

public class KozaManagerTest {
    /** テスト対象クラス */
    private KozaManager target;

    /**
     *  テストの初期化 
     *  各テスト実行前に起動する
     */
    @Before
    public void initClass() {
        target = new KozaManager();
    }

    /**
     *  テストの後始末 
     *  各テスト実行前に起動する
     */
    @After
    public void terminate() {
        target.finalize();
    }

    /**
     * コンストラクタが起動したかどうかを確認する
     * テストケース
     */
    @Test
    public void testIsInstance() {
        assertNotNull(target);
    }

    /**
     * ファイルの存在チェック処理の確認
     */
    @Test
    public void testIsFile() {
        assertTrue(target.isFile());
    }

    /**
     * ファイルにデータを出力し保存するテストケースです。
     */
    public void testFileCeate() {
        Data data = new Data("名前", "パスワード");
        try {
            target.dataOutput(data);
        } catch(IOException ie) {
            ie.printStackTrace();
            fail();
        }
    }
}

ここでのポイントはファイル出力のテストケースに「@Test」がついていないところです。つまりこのテストケースは実行されません。では動かしてみます。

とりあえずはテスト成功の様です。ここでファイルの存在チェックができたので、次はファイル出力のテスト実装に入ろうと思います、

でわでわ。。。

<<< 前回 次回 >>>

<Java関連の動画リスト>

<JUnit関連の動画リスト>



Java はじめて26 〜JUnitでのテスト駆動型開発3: クラスの実装〜

イントロダクション

JUnitを使用したテスト駆動開発について記載しています。
前回は、どのようにテストケースを実装したら良いか?とか実装する手法について記載しました。

<JUnitの起動設定1>

<同様に2>

Junitのサンプルコード

JUnitでのテストを行うために必要なことは

  1. junit.jarを設定
  2. テストメソッドに「\@Test」をつける

より簡単にテストを行いたければ、「/@Before」などのアノテーションを使う。

public class TestSample {
    /** テスト対象クラス */
    private Sample target;
    @Before
    public void initTest() {
        // テストの実行準備の処理
        target = new Sample();
    }
    @Test
    public void test001() {
        assertEquals("a", "a"); // 二つの引数が等しいことを確認できる。
    }
}

クラスの実装

今回は、JUnitでのテストケースから、テストをオールグリーンに出来る様に実体クラスを作成します。
つまり、JUnitでテストケースを要件を満たす様に作成し、テスト実行後に緑色の表示がされる様にKozaManagerのメソッドを作成します。これは、銀行のATMを簡易的にしたコンソールアプリを作成するための実装でもあります。
ちなみに、今回の実装する時の要件は以下の様になっています。

  1. ファイルを操作するためのオブジェクトを作成する
  2. ファイルにデータをCSV形式で書き出す。
  3. ファイルが存在するのであれば、それを読み込みデータを保持する

クラスを仕分けしてからの実装を行うので、どの様に考えれば良いか?の指針になれば良いと思います。

<クラスの仕分けの例>

  • File操作担当のクラスを作成する(仮にFileUtilクラスとする)
  • KozaManagerからFile操作をするときはこのクラスを呼ぶようにする

<KozaMangerでの実装例>

public class KozaManger {
    ...
    public boolean isFile() {
        return FileUtil.isFile("reources/koza.csv");
    }
    ...
}

<テストケース(メソッド)の例>

@Test
public void testIsFile() {
    // 存在しないファイルを指定したときはFALSEを返す
    boolean isFalse = FileUtil.isFile("resources/koza.xyz");
    assertFalse(isFalse);
    // 存在するファイルを指定したときはTRUEを返す
    boolean isTrue = FileUtil.isFile("resources/koza.csv");
    assertTrue(isTrue);
}

<FileUtilでの実装例>※テストしていません。。。
※JavaDocを参照:File#isFile()

public static boolean isFile(String path) {
    File file = new File(path);
    return file.isFile(path);
}

<ファイル入出力のテスト>

今までの概要

前回作成したのは、上の要件のうちの1(コンストラクタで実装)だけです、これはテストを実行する準備段階でもあるので、テストケースとしては「インスタンスがtargetに設定されていること」を確認するテストケースを作成しました。

public class KozaManager {
    /** ファイルへの書き出しクラス */
    private BufferedWriter write;
    /** ファイルの読み込みクラス */
    private BufferedReader read;
    /** コンストラクタ */
    public KozaManager() {
        // 操作するファイルを指定する
        File file = new File("resources/koza.csv");
        try {
            write = new BufferedWriter(new FileWriter(file));
            if (file.exists()) {
                read = new BufferedReader(new FileReader(file));
            }
        } catch (IOException ie) {
            ie.printStackTrace();
            System.out.println("ファイルオープンに失敗しました。" + ie.getMessage());
            System.exit(-1);
        }
    }
    /** デストラクタ */
    @Override
    protected void finalize() throws Throwable {
        write = null;
        read = null;
        }
    /**
    * データクラスを受け取り、CSVファイルを出力する(書き出しを行う)
    * @param data コーダー銀行のユーザー情報
    */
    public void dataOutput(Data data) {
    // スタブの状態(空実装)
    }
}

コンストラクタと、デストラクタを実装しています。
コンストラクタは、ファイル操作に必要なクラスのインスタンスを作成します。
デストラクタは作成したインスタンスを解放(Nullをセット)します。※メモリの開放をすることでリソースの節約になります。

作成したデストラクタは明示的に呼び出しましょうつまり使い終わったら下のように「finalize()」を呼び出しましょう。
ガベージコレクションに任せると、いつ呼び出されるかわかりません。。。

KozaManager kozaManager = new KozaManager();
// 何かしらの処理 ...

// メモリの開放 finalize()を明示的に呼び出す
kozaManager.finalize();

現状のテストコード

package jp.zenryoku.apps.atm.manage;
import static org.junit.Assert.assertNotNull;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import jp.zenryoku.apps.atm.data.Data;
/**
* @author takunoji
*
* 2019/09/25
*/
public class KozaManagerTest {
    /** テスト対象クラス */
    private KozaManager target;
    /**
    *  テストの初期化
    *  各テスト実行前に起動する
    */
    @Before
    public void init() {
        target = new KozaManager();
    }

    /**
     * テスト終了時にメモリの開放処理を行う
     */
     @After
     public void finish() {
        // デストラクタを明示的に呼び出す。
        target.finalize();
     }
    /**
    * コンストラクタが起動したかどうかを確認する
    * テストケース
    */
    @Test
    public void testIsFile() {
        assertNotNull(target);
    }
    /**
    * ファイルにデータを出力し保存するテストケースです。
    */
    public void testFileCeate() {
    }
}

テストケース作成

今回の目玉になります、現状はから実装になっている「testFileCreate()」の実装を行いますが、これはテスト対象クラス(本来作成するクラス)がどう動けば良いか?を考えて作成する必要があります。

どう動けば良いか?

口座情報管理(KozaManager)クラスで入力されたデータをファイルに出力する必要があるので単純に「データを受けて、それをCSVに変換してファイル出力する」処理を行うのがこのメソッドの処理になります。
まとめると以下の様になります。

  1. 入力としてDataクラス(口座情報クラス)を受ける
  2. 出力として、CSVファイルを出力する

ここで、KozaManagerクラスの他に「Dataクラス」が必要であることがわかります。これは前回作成しております
そして、CSVファイルを出力するのに、ヘッダー部分のテンプレート(CSVファイルのヘッダは固定値です)を出力する必要がありますので、これをKozaManagerに追加します。
現状では、以下の様なコードになります。

/**
* 出力するCSVのヘッダー部分を作成する。
* @return CSVヘッダーの文字列(カンマ区切り)
*/
private String createCSVHeader() {
    return "名前, パスワード";
}
/**
* データクラスを受け取り、CSVファイルを出力する(書き出しを行う)
* @param data コーダー銀行のユーザー情報
*/
public void dataOutput(Data data) throws IOException {
    StringBuilder build = new StringBuilder();
    build.append(this.createCSVHeader());
    build.append(data.getName() + ",");
    build.append(data.getPassword());
}

しかし、ここで注意したいのがCSVデータは大雑把に以下の様な形式になります。そして、サンプルデータを記載しておきます。
<形式>
1行目: ヘッダー
2行目以降: データ

名前, パスワード
テスト太郎, 1234
テスト花子, 2345
テストたくのじ, 3456

頭をひねる

ここで、元のクラスに色々と修正を加える必要が出てきました。追加する修正は以下のものです。

  1. ファイル出力する時のパス指定をハードコーディング(File file = new File("resources/koza.csv");)しているのを定数としてクラスに保持したい
  2. ファイルが出力できたか?のファイル存在チェックメソッドが欲しい

これらの修正を加えるのに、テストケースで「ファイルにデータをCSV形式で書き出す。」という処理は最後になります。
上の処理を確認するのにファイルの存在チェックなどが必要なので先にそちらの処理を作成する必要があるからです。

先にファイル存在チェック処理を作る

これをやる必要が出てきました。
これは次回やろうと思います。

Java はじめて27 〜JUnitでのテスト駆動型開発4: 追加修正と実装〜

でわでわ。。。

<<< 前回 次回 >>>

<Java関連の動画リスト>

<JUnit関連の動画リスト>



Java はじめて25 〜JUnitでのテスト駆動型開発2: テストケース作成〜

イントロダクション

前回のまとめ

JUnitがなんなのか?に関して記載しました。
そして、このフレームワークはテスト(作成したコード)を実行してその動きが想定通りに動いたかどうか?を「assert」を使用して確認できるものです。

動かすためのポイント
junit.jarがビルドパスに設定されている

テストケース作成

今回は、コンストラクタがエラーもなく正常に動いたかどうかの確認を行うところから始めます。

大まかに以下の様な形で実装します。

public class KozaManagerTest {
    /** テスト対象クラス */
    private KozaManager target;
    /**
    *  テストの初期化
    *  各テスト実行前に起動する
    */
    @Before
    public void initClass() {
        target = new KozaManager();
    }
    /**
    * コンストラクタが起動したかどうかを確認する
    * テストケース
    */
    @Test
    public void testIsFile() {
        assertNotNull(target);
    }
}

今回のテストケースを実装していますが、ポイントとしては、以下の点が挙げられます。

  1. @Beforeは各テストケース(メソッド)が起動する前に動く
  2. @Testはテストケース(メソッド)に付ける

上のコードでは、コンストラクタが起動してフィールド変数「target」にインスタンスが設定されているかどうか?(正確には、Nullではない事)を確認します。targetにインスタンスが設定されていない=コンストラクタが起動している最中に例外(Exception)が発生して@Befireのついているメソッドの処理target = new KozaManager()の処理が終わらないうちに(targetに値が設定されないうちに)例外を受け取る処理が動くため、値が設定されない=Nullになるわけです。

<余談>
\@Beforeアノテーションはテストケースが開始される前(Before)に実行覚ます。もし、インスタンスの生成は一回だけでよいのであれば「\@BefreCLass」を使いましょう。JUnit5では\@Before -> \@BeforeEach \@BeforeClass -> \@BeforeAllに代わっているようです。

具体的に

下の動画を見てもらうとわかると思います。

この処理は、コンストラクタで例外を発生させています。

public KozaManager() {
    // 操作するファイルを指定する
    File file = new File("resources/koza.csv");
    try {
        write = new BufferedWriter(new FileWriter(file));
        if (file.exists()) {
            read = new BufferedReader(new FileReader(file));
            throw new IOException("テスト例外");
        }
    } catch (IOException ie) {
        ie.printStackTrace();
        System.out.println("ファイルオープンに失敗しました。" + ie.getMessage());
        System.exit(-1);
    }
}

ここでのポイントは例外が発生した後にアプリ(メインメソッドの処理)を強制終了しているのでJUnitの緑色とか茶色(エラー時)が表示されません。この場合はテストも結果が出ません。

<補足>
ちなみに、コードを実行した後にガベージコレクションでフィールド変数などが解放されるので実装しなくても大丈夫なのですが、テストケースでなんどもKozaMangerクラスのインスタンスを生成するのでC言語的にいうところの「デストラクタ」(コンストラクタの逆=インスタンスを削除する時)が動く時にフィールド変数を解放します。この処理を追加します。ちなみに、「finalize()」はObjectクラスに定義されているメソッドです。そのため「オーバーライド」しています。

「finalize()は書いてはいけない!」みたいな記事がありましたが、「ガベージコレクションに任せるな!」という意味です。
メモリの開放などは、finalize()を明示的に呼び出しましょう

もしくは、インスタンスの開放を明示的にやりましょう

/** デストラクタ */
@Override
protected void finalize() throws Throwable {
    write = null;
    read = null;
}
public static void main(String[] args) throws Throwable {

    try {
       Sample.execute();
    } catch (Exception e) {
       e.printStackTrace();
    } finally {
      // 明示的に呼び出す。
       Sample.finalize();
    }
}

こうすることで、メモリに余計なもの(オブジェクトやインスタンス)を保存しないので、処理が快適に動きます。
「ガベージコレクションがあるからいいや」と思っていると、いつメモリの開放をするかわからないので明示的にメモリの開放処理をしましょう。
ということです。

テストケースの実装

ここからが本当のテスト駆動型開発です、現状はコンストラクタを作成していますが、説明のため。。。言い訳くさい。。。

とりあえず、以下の様に考えます。

「このクラスで要件を満たすために何をしたら良い?」

自分の答えは以下の通りです。

  1. ファイルを操作するためのインスタンスを作成する
  2. ファイルにデータをCSV形式で書き出す。
  3. ファイルが存在するのであれば、それを読み込みデータを保持する

項目1はコンストラクタで実現しているので。今回は2のファイルにCSVデータを書き出す。を実装しようと思います。
設計としては以下の様にします。
<設計>

  1. ファイルの書き出しを行うメソッドはdataOutputという名前にする
  2. 書き出しを行うときはCSV形式で出力する
  3. データを受け取るときは「Dataクラス」で受け取る

この設計から行くと作成するメソッドは「dataOutput」で引数は「Dataクラス」になります。
そして、返却値は「なし=void」です。

なので、テストケースとしては以下の様なコードになります。

/**
  * ファイルにデータを出力し保存するテストケースです。
 */
  public void testFileCeate() {
    Data data = new Data("名前", "パスワード");
    target.dataOutput(data);
}

この様に実装しましたが、「Data」クラスも「dataOutput」メソッドも存在しないのでエラーになります。
なのでこれを「スタブ=空実装(名前だけ、形だけ)」で作成します。そして、作成するDataクラスは以下の様な形で実装します。
これはデータクラスで、内容はフィールドと「Getter(ゲッター)」と「Setter(セッター)」のみになります。

public class Data {
    /** ユーザー名 */
    private String name;
    /** パスワード */
    private String password;
    /**
      * @return the name
      */
      public String getName() {
        return name;
        }
    /**
      * @param name the name to set
      */
      public void setName(String name) {
        this.name = name;
        }
        /**
          * @return the password
          */
        public String getPassword() {
            return password;
        }
        /**
          * @param password the password to set
          */
        public void setPassword(String password) {
            this.password = password;
        }
}

そして、今回作成するメソッドをスタブで作成します。スタブで作成すれば、処理の中身がないけれど、呼び出し元ではエラーなく呼ぶことができます。なのでIN(引数)とOUT(返り値)だけ定義してあれば良いのです。
さらに、データクラスをインポートします。
import jp.zenryoku.apps.atm.data.Data;

<テストケースの実装と作成するクラス>

<スタブメソッド>※返り値なしの場合です。

/**
* データクラスを受け取り、CSVファイルを出力する(書き出しを行う)
* @param data コーダー銀行のユーザー情報
*/
public void dataOutput(Data data) {
}

次回は、このスタブで要件を満たすことができるかどうか考え、必要な実装を行いたいと思います。

Java はじめて26 〜JUnitでのテスト駆動型開発3: クラスの実装〜

ここの部分が実装になります。

実装したらテストして、修正…を繰り返す形で実装します。テスト駆動だと作った処理が想定通りか確認する事とテストが同じなので一手間省けます。

でわでわ。。。

<<< 前回 次回 >>>

<Java関連の動画リスト>

<JUnit関連の動画リスト>



Java はじめて24 〜JUnitでのテスト駆動型開発1〜

イントロダクション

JUnitを使用して開発を行います。と言っても今まで作成していたクラスの追加実装を行うというだけです。ただし、作成する実装が予定通り、仕様通りに動くことを確認しながら作成します。

つまりは、テスト仕様を先に作ってからやります。

まずは、テスト仕様を作成するのですが、これは前回作成したので割愛します。

JUnitを使う

前回、JUnitが動くことを確認するプログラムを作成しました。
なので今回は、設計部分から。。。つまり「どー動けば良いか?」を考えることから実装を始めたいと思います。

参照先一覧

<JUnitでテストケースを作る>

実装内容の確認

ますはソースコードがどのように処理を行っているのか?を確認します。ソースはGithubにアップしてあるのでこれをチェックアウトしていじってみるのもよいです。
ちなみに、余計なプログラムも入っていて、「fx」「opencv」パッケージ以下のファイルは削除しても大丈夫です。

プログラムを追いかける

まずは、どこからプログラムを追いかけたらよいか?これはメインメソッドから追いかけます。
理由としては、以下の通りです。

  • Javaはまずメインメソッドが動く。
  • メインメソッドが複数あっても結局、起動できるのは1つだけ。
  • とりあえずメインメソッドが動く。

そんなわけで、今回参照しているプログラムのある場所を確認します。
パッケージの名前がjp.zenryoku.apps.atmになりますので、ここを開きます。

次に、メインメソッドのある「MainBank」クラスを見ます。

    public static void main(String[] args) {
        MainBank main = new MainBank();
        main.atm();
    }

メインメソッドは、これだけです。このクラス(MainBank)のatm()メソッドを実行しているだけですね。
ちょっと脱線しますが、「MainBankクラスのatm()メソッド」を示すのに次のような書き方をします。「MainBank#atm()

ここから処理を追いかけるのですが、大まかに次のような処理を行っています。

  1. MainBank#atm()を起動する。
  2. コーダー銀行受付文言を表示
  3. 入金するか、引き出しをするか選択してそれぞれの処理を行う。
  4. "bye"と入力するとプログラムが終了する

テスト駆動開発

テストケース(どう動くか?)を作成することから初めていきます。そのためには、まず「何がどう動くのか?」を決めておく必要があります。
その「何がどう動くのか?」を決めているのが「仕様書」になります。

仕様

  • コーダー銀行受付文言を表示を行う。表示内容は「コーダー銀行へようこそ、入金しますか?引き出しますか?」とする。
  • アプリケーションの実行時には、預金額が1000円預金されている。
  • 入金と引き出しができる。
  • "bye"と入力するとアプリケーションを終了する

ファイル操作をする

ファイルを使用してユーザー名とパスワードを管理するようにします。
プログラム的には、指定のファイルが存在すれば、ファイルを参照することができ、ファイルが存在しなけでば、ファイルを作成する。
今回はこの部分のみを実装します。初めにテストクラスの構成ですが、書くテストケースを実行する前にテスト対象クラスのインスタンスを新規で作成します。@Beforeアノテーションをつけてやるとそのメソッドはテストケースの実行前に起動します。

具体的に、テストフォルダ内に同じパッケージを作成して、「テストするクラス名 + Test」という名前のクラスを作成します。
具体的に作成した結果が以下のような形になります。テスト対象クラス「KozaManager」とします。
<PracticeJava1プロジェクト>

PracticeJava1\test\jp\zenryoku\apps\atm\manage\KozaManagerTest.java

テストクラスの作成

ようやく、前置きが終わりました。結論的には、KozaManagerクラスのテストクラスを作成しようということです。
テスト対象のクラスとテストクラスの話をしますので、一応確認します。
※ ここで使用するフレームワークはJUnitです。これを使用して実装するのはとても簡単です。

<補足>
先に、テスト対象クラスのコードを記載していますが、テストコード→テスト対象クラスのコードという順番で作成します

テストコードで以下のように作成します。

  1. テストクラスの作成
  2. テストクラスの初期化「\@Befre」
  3. テストの実行「\@Test」

上記の「\@」アノテーションのついているメソッドがそれぞれに意味を持ちます。

public class KozaManagerTest {
    /** テスト対象クラス */
    private KozaManager target;

    @Before
    public void initClass() {
        target = new KozaManager();
    }
    @Test
    public void testXXX() {
        // 「isFile()」をテストする処理
        assertEquals(true, target.isFile());
    }
}

「isFile()」メソッドは空で作成します。

public boolean isFile() {
    return ????:
}

この時に、「isFile()」の返却値はbooleanでいいのか?値はどのように返すか?など具体的な処理を考えます。

テストケースの次に実装クラス

下記のような順序で処理を実装します。

  1. テストケースを考える。
  2. テスト対象クラスのコードを実装。
  • テスト対象クラス:KozaManager
  • テストクラス:KozaManagerTest

<テスト対象クラスのコード>

/**
 * 口座の管理(登録、更新、削除)を行うクラス
 * @author takunoji
 *
 * 2019/09/21
 */
public class KozaManager {
    /** ファイルへの書き出しクラス */
    private BufferedWriter write;
    /** ファイルの読み込みクラス */
    private BufferedReader read;
    /** 取得(作成)するファイルクラス */
    private File file;
    /** ファイルのパス */
    private static final String FILE_PATH = "resources/koza.csv";

    /** コンストラクタ */
    public KozaManager() {
        // 操作するファイルを指定する
        file = new File(FILE_PATH);
        try {
            write = new BufferedWriter(new FileWriter(file, true));
            if (file.exists()) {
                read = new BufferedReader(new FileReader(file));
            }
        } catch (IOException ie) {
            ie.printStackTrace();
            System.out.println("ファイルオープンに失敗しました。" + ie.getMessage());
            System.exit(-1);
        }
    }

    /** デストラクタ */
    @Override
    protected void finalize() throws Throwable {
        // フィールド変数の後始末
        file = null;
        read.close();
        write = null;
        read = null;
    }

    /** 作成するファイルが存在するかチェック */
    public boolean isFile() {
        return file.exists();
    }

    /**
     * 出力するCSVのヘッダー部分を作成する。
     * @return CSVヘッダーの文字列(カンマ区切り)
     */
    private String createCSVHeader() {
        return "名前, パスワード";
    }

    /**
     * データクラスを受け取り、CSVファイルを出力する(書き出しを行う)
     * @param data コーダー銀行のユーザー情報
     */
    public void dataOutput(Data data) throws IOException {
        if (file.canWrite() == false) {
            throw new IOException("ファイルの書き込みができません: " + file.getAbsolutePath());
        }
        // おおよそのデータサイズを指定すると余計なメモリを使用しなくて済む
        StringBuilder build = new StringBuilder(50);
        // ファイルそ新規で作成するとき
        if (file.exists() == false) {
            // ヘッダー部分の出力
            build.append(this.createCSVHeader());
            // ファイル書き込み処理
            write.write(build.toString());
            write.newLine();
        }
        // StringBuilderのクリア
        build.setLength(0);
        // データ部分の書き込み
        build.append(data.getName() + ",");
        build.append(data.getPassword());
        write.write(build.toString());
        write.newLine();
        write.close();
    }

    /**
     * ファイルを読み込みデータをリストにして返却する
     * @return List<Data> CSVファイルのデータリスト
     */
    public List<Data> readFile() {
        List<Data> list = new ArrayList<>();
        String line = null;
        try {
            while((line = read.readLine()) != null) {
                String[] lineData = line.split(",");
                // lineData[0]: 名前, lineData[1]: パスワード
                list.add(new Data(lineData[0], lineData[1]));
            }
        } catch(IOException ie) {
            ie.printStackTrace();
            System.exit(-1);
        }
        return list;
    }

    /**
     * フィールドのfileを返却する。
     * @return file
     */
    public File getFile() {
        return this.file;
    }
}

大まかな作り方

はじめに、テストクラスの作成をします。するとある程度コードが生成されるので追加でコードを書きます。
まずは、テスト対象クラスをフィールド変数にセットで切るようにします。

public class KozaManagerTest {
    /** テスト対象クラス */
    private KozaManager target;

}

このフィールド変数というのは、クラスの中であればどこらでも参照できるものです。
そして、アクセス修飾子が「private」なので、クラスの中でのみ参照できる変数になります。
クラスの外からフィールド変数にアクセスできないように「private」にするのはセキュリティの基本になります。

そして、テストを実行する時に、テスト対象クラスのインスタンスを作成して各テストケースでテスト対象クラスのインスタンスを参照できるように
下のようなコードを書きます。

@Before
public void initClass() {
    target = new KozaManager();
}
public class KozaManagerTest {
    /** テスト対象クラス */
    private KozaManager target;

    @Before
    public void initClass() {
        target = new KozaManager();
    }
}

今後作成するテストケース=テストを実行するメソッドは次のように書きます。

@Test
public void testXXX() {
    // テストする処理
}

テストケースは、好きなだけ追加できますので、ガンガン追加して大丈夫です。※当然モノによっては重くなるので注意です。

public class KozaManagerTest {
    /** テスト対象クラス */
    private KozaManager target;

    @Before
    public void initClass() {
        target = new KozaManager();
    }
    @Test
    public void testXXX() {
        // テストする処理
    }
}

そして、問題のテストケースですが、プログラムレベルでまずは、ファイルの読み書き用クラスのインスタンスが取得できないと話にならないのでその起動確認を行います。assertを使用できることがJUnitの魅力でもあります。

Assertとは

JUnitのフレームワークで使用できる想定通りに動いたか確認するためのメソッドです。
使い方は以下の通りです。

/**
 * コンストラクタが起動したかどうかを確認する
 * テストケース
 */
@Test
public void testIsFile() {
    assertNotNull(target);
}

上のコードでは、Assertクラスを「static import」して使用しています。のでメソッドを直接呼ぶことができます。JUnitでは、assertNotNullなどの確認用メソッドが用意されています、上の場合だとtargetがNullの時にエラーを吐きます。他にもassertEqualsがありこれは

assertEquals(value, "aaa");

の様に使用し値が等しくない時にエラーを吐きます。

メソッドの中で完結しないのですが、strong>\@Beforeでコンストラクタを起動しているので、テストケースとしては実行済みになりますので、インスタンスがtargetの中に入っているかどうかの確認を行います。

そして、問題の「ファイルを読み込むためのクラス」は以下の様に処理を行っています。下のクラスはテスト対象クラスです。

/** コンストラクタ */
public KozaManager() {
    // 操作するファイルを指定する
    File file = new File("resources/koza.csv");
    try {
        write = new BufferedWriter(new FileWriter(file));
        if (file.exists()) {
            read = new BufferedReader(new FileReader(file));
        }
    } catch (IOException ie) {
        ie.printStackTrace();
        System.out.println("ファイルオープンに失敗しました。" + ie.getMessage());
        System.exit(-1);
    }
}

とりあえずは、このテストがうまくいく様に作成します。
しかし、今回のはコンストラクタを起動するだけ、しかもテストケースと呼べる様なものではないのでよろしくはないのですが、コンストラクタで必要なインスタンスなどを作成しないことには何も始まらないのでこの様になりました。

ポイント

  1. テストケースを作成する前の準備
  2. コンストラクタでテストケースに関連する部分を含むがテストを行ったことにはしない

でわでわ。。。