FitNesseによるテストファースト開発

過去数年間、私はサーバーサイドJavaScript、Perl、PHP、Struts、Swing、およびモデル駆動型アーキテクチャーを使用して、テストプロセスのすべての役割に携わってきました。すべてのプロジェクトは異なっていましたが、すべてにいくつかの共通点がありました。締め切りが遅れ、プロジェクトは顧客が本当に必要としていることを達成するのに苦労しました。

すべてのプロジェクトにはある種の要件があり、非常に詳細なものもあれば、ほんの数ページの長さのものもありました。これらの要件は通常、次の3つのフェーズを経ました。

  • それらは(顧客または請負業者のいずれかによって)書かれ、ある種の公式の承認を受けました
  • テスターは要件を処理しようとしましたが、多かれ少なかれ不十分であることがわかりました
  • プロジェクトは受け入れテストの段階に入り、顧客はソフトウェアが追加的/異なる方法で実行する必要のあるあらゆる種類のことを突然思い出しました。

最後のフェーズは変更につながり、それが期限の遅れにつながり、開発者にストレスを与え、それがさらに多くのミスにつながりました。バグ数は急速に増加し始め、システムの全体的な品質は低下しました。おなじみですか?

上記のプロジェクトで何が悪かったのかを考えてみましょう。顧客、開発者、テスターが協力していませんでした。彼らは要件を通過しましたが、それぞれの役割には異なるニーズがありました。さらに、開発者は通常、ある種の自動テストを開発し、テスターはいくつかのテストも自動化しようとしました。通常、彼らはテストを十分に調整することができず、多くの項目が2回テストされましたが、他の項目(通常はハードパーツ)はまったくテストされませんでした。そして、顧客はテストを見ませんでした。この記事では、要件と自動テストを組み合わせて、これらの問題を解決する方法について説明します。

FitNesseを入力してください

FitNesseは、JUnitテストをトリガーするためのいくつかの追加機能を備えたwikiです。これらのテストを要件と組み合わせると、具体的な例として機能し、要件がさらに明確になります。さらに、テストデータは論理的に整理されています。ただし、FitNesseを使用する上で最も重要なことは、その背後にある考え方です。つまり、要件は(部分的に)テストとして記述され、テスト可能になり、したがって、その履行を検証できるようになります。

FitNesseを使用すると、開発プロセスは次のようになります。要件エンジニアは(Wordではなく)FitNesseで要件を記述します。彼は可能な限り顧客を巻き込むように努めていますが、それは通常、日常的に達成することはできません。テスターはドキュメントを繰り返し覗き、初日から難しい質問をします。テスターは考え方が異なるため、「ソフトウェアは何をするのか」とは考えていません。しかし、「何がうまくいかない可能性がありますか?どうすればそれを壊すことができますか?」開発者は、要件エンジニアのように考えています。彼は「ソフトウェアは何をしなければならないのか」と知りたがっています。

テスターは、要件がまだ完了していないときに、テストの作成を早期に開始します。そして、彼はそれらを要件に書き込みます。テストは、要件だけでなく、要件のレビュー/承認プロセスの一部にもなります。これには、いくつかの重要な利点があります。

  • 顧客はテストについても考えるようになります。通常、彼女はそれらの作成にも関わっています(これで彼女がどれほど楽しいことができるかに驚くかもしれません)。
  • テストは通常​​、単なるテキストよりも正確であるため、仕様ははるかに詳細で正確になります。
  • 実際のシナリオについて早い段階で考え、テストデータを提供し、例を計算すると、プロトタイプのように、より多くの機能を備えたソフトウェアのビューがはるかに明確になります。

最後に、要件は開発者に渡されます。仕様が変更される可能性が低く、含まれているすべての例があるため、彼は今では簡単な仕事をしています。このプロセスによって開発者の仕事がどのように簡単になるかを見てみましょう。

テストファーストの実装

通常、テストファースト開発を開始する上で最も難しいのは、テストの作成にそれほど多くの時間を費やしたくないということです。それから、テストを機能させる方法を見つけます。上記のプロセスを使用して、開発者は契約の一部として機能テストを受け取ります。彼のタスクは、「私があなたの仕事を調べて変更を加えるまで、私が欲しいものを構築し、あなたは完了します」から「これらのテストを機能させ、あなたは完了します」に変わります。これで、誰もが何をすべきか、いつ作業を完了するか、そしてプロジェクトがどこにあるかについてより良い考えを持っています。

これらのテストのすべてが自動化されるわけではなく、すべてが単体テストになるわけでもありません。通常、テストは次のカテゴリに分類されます(詳細は次のとおりです)。

  • 単体テストとして実装する必要があるデータ駆動型テスト。計算は典型的な例です。
  • アプリケーションの使用を自動化するキーワード駆動型テスト。これらはシステムテストであり、アプリケーションが実行されている必要があります。ボタンがクリックされ、データが入力され、結果のページ/画面が特定の値を含むかどうかがチェックされます。テストチームは通常、これらのテストを実装しますが、一部の開発者もこれらのテストの恩恵を受けています。
  • 手動テスト。これらのテストは、自動化するにはコストがかかりすぎて、発生する可能性のあるエラーが十分に深刻でないか、非常に基本的(スタートページが表示されない)であるため、破損がすぐに発見されます。

2004年に初めてFitNesseについて読んだとき、私は笑って、それは決して機能しないと言いました。私のテストをウィキに書き込んで自動的にテストに変えるというアイデアは、ばかげているように思えました。結局、私は間違っていました。FitNesseは、見た目と同じくらいシンプルです。

この単純さは、インストールから始まります。FitNesseの完全なディストリビューションをダウンロードして解凍するだけです。以下の説明では、ディストリビューションをC:\ fitnesseに解凍したと仮定します。

C:\ fitnesseでrun.batrun.shLinuxの場合)スクリプトを実行して、FitNesseを起動します。デフォルトでは、FitNesseはポート80でWebサーバーを実行しますが-p 81、バッチファイルの最初の行に追加することで、別のポート、たとえば81を指定できます。これですべてです。// localhost:81でFitNesseにアクセスできるようになりました。

この記事では、WindowsでJavaバージョンのFitNesseを使用します。ただし、これらの例は、他のバージョン(Python、.Net)およびプラットフォームにも使用できます。

いくつかのテスト

FitNesseのオンラインドキュメントには、開始するための簡単な例(JUnitの悪名高いお金の例と比較して)がいくつか用意されています。FitNesseの使用方法を学ぶには問題ありませんが、一部の懐疑論者を説得するほど複雑ではありません。したがって、最近のプロジェクトの1つからの実際の例を使用します。私は問題を大幅に単純化しました。プロジェクトから直接取得されたのではなく、コードは説明のために作成されました。それでも、この例は、FitNesseのシンプルさの力を示すために十分に複雑である必要があります。

大規模な保険会社向けに複雑なエンタープライズJavaベースのソフトウェアを実装するプロジェクトに取り組んでいると仮定しましょう。この製品は、顧客および契約の管理と支払いを含む、会社のビジネス全体を処理します。この例では、このアプリケーションのごく一部を見ていきます。

スイスでは、親は子供1人につき1つの児童手当を受け取る権利があります。特定の状況が満たされた場合にのみこの手当を受け取り、金額は異なります。以下は、この要件の簡略版です。「従来の」要件から始めて、後でFitNesseに移動します。

児童手当にはいくつかの段階があります。申し立ては、子供が生まれた月の初日に始まり、子供が年齢の境界に達した、見習いを終えた、または死亡した月の最後の日に終了します。

On reaching the age of 12, the claim is raised to 190 CHF (Switzerland's official currency symbol) starting on the first day of the month of birth.

Full-time and part-time employment of parents lead to different claims, as detailed in Figure 1.

The current rate of employment is calculated on the active work contracts. The contract needs to be valid, and if an end date is set, it needs to be situated in the "activate period." Figure 2 shows how much money a parent is entitled to, depending on the age of the child.

The regulations governing these payments are adapted every two years.

On first reading, the specification might sound exact, and a developer should be able to easily implement it. But are we really sure about the boundary conditions? How would we test these requirements?

Boundary conditions
Boundary conditions are the situations directly on, above, and beneath the edges of input and output equivalence classes. Experiences show that test cases exploring boundary conditions have a higher payoff than test cases that do not. A typical example is the infamous "one-off" on loops and arrays.

Scenarios can be a great help in finding exceptions and boundary conditions, as they provide a good way to get domain experts to talk about business.

Scenarios

For most projects, the requirements engineer hands the specification to the developer, who studies the requirements, asks some questions, and starts to design/code/test. Afterwards, the developer hands the software to the test team and, after some rework and fixes, passes it on to the customer (who will likely think of some exceptions requiring changes). Moving the text to FitNesse won't change this process; however, adding examples, scenarios, and tests will.

Scenarios are especially helpful for getting the ball rolling during testing. Some examples follow. Answering the question of how much child allowance is to be paid to each will clarify a lot:

  • Maria is a single parent. She has two sons (Bob, 2, and Peter, 15) and works part-time (20 hours per week) as a secretary.
  • Maria loses her job. Later, she works 10 hours per week as a shop assistant and another 5 hours as a babysitter.
  • Paul and Lara have a daughter (Lisa, 17) who is physically challenged and a son (Frank, 18) who is still in university.

Just talking through these scenarios should help the testing process. Executing them manually on the software will almost certainly find some loose ends. Think we can't do that, since we don't have a prototype yet? Why not?

Keyword-driven testing

Keyword-driven testing can be used to simulate a prototype. FitNesse allows us to define keyword-driven tests (see "Totally Data-Driven Automated Testing" for details). Even with no software to (automatically) execute the tests against, keyword-driven tests will help a lot.

Figure 3 shows what a keyword-driven test might look like. The first column represents keywords from FitNesse. The second column represents methods in a Java class (we write those, and they need to follow the restrictions put on method names in Java). The third column represents data entered into the method from the second column. The last row demonstrates what a failed test might look like (passed tests are green). As you can see, it is quite easy to find out what went wrong.

Such tests are easy and even fun to create. Testers without programming skills can create them, and the customer can read them (after a short introduction).

Defining tests this way, right next to the requirement, has some important advantages over the traditional definition of test cases, even without automation:

  • The context is at hand. The test case itself can be written with the least possible amount of work and is still precise.
  • If the requirement changes, there is a strong chance that the test will change as well (not very likely when several tools are used).
  • The test can be executed at once to show what needs to be fixed to make this new/changed requirement work.

To automate the test, a thin layer of software is created, which is delegated to the real test code. These tests are especially useful for automating manual GUI tests. I developed a test framework based on HTTPUnit for automating the testing of Webpages.

Here is the code automatically executed by FitNesse:

package stephanwiesner.javaworld;

import fit.ColumnFixture;

public class ChildAllowanceFixture extends ColumnFixture { public void personButton() { System.out.println("pressing person button"); } public void securityNumber(int number) { System.out.println("entering securityNumber " + number); } public int childAllowance() { System.out.println("calculating child allowance"); return 190; } [...] }

The output of the tests can be examined in FitNesse as well (see Figure 4), which greatly helps with debugging. In contrast to JUnit, where one is discouraged from writing debug messages, I find them absolutely necessary when working with automated Web tests.

When testing a Web-based application, error pages are included in the FitNesse page and displayed, making debugging much easier than working with log files.

Data-driven testing

GUIの自動化にはキーワード駆動型テストで問題ありませんが、あらゆる種類の計算を行うコードをテストするには、データ駆動型テストが最初の選択肢です。以前に単体テストを作成したことがある場合、それらのテストで最も厄介なことは何ですか?たぶん、あなたはデータについて考えます。テストはデータでいっぱいになりますが、データは頻繁に変更されるため、メンテナンスが悪夢になります。さまざまな組み合わせをテストするには、さまざまなデータが必要であり、おそらくテストが複雑で醜い獣になります。