🔎

wikiで受入テスト仕様を書くFitNesse

に公開

概要

FitNesseは「wikiで仕様を書いたら、そのまま自動テストとして実行できる」受け入れテスト用フレームワークです。

wiki上のテーブルに入力値と期待値を記載するデータ駆動型のようなテストや、

手順(キーワード)をテーブルに記載して広義のキーワード駆動型のようなテストが可能です。

FitNesseは「FIT(Framework for Integrated Testing)」のHTML/Wikiフロントエンドとして始まりました。少なくとも2006年ころには継続的にリリースされているので歴史のあるツールです。

本記事ではFitNesseの簡単な使い方を紹介します。
詳しいドキュメントを読みたい場合は公式のユーザーガイドを参照してください。

なお、前述のとおり、FitNesseはメンテナンスはされているものの歴史の長いツールです。
昔と違って選択肢が多くなっているので、新規でキーワード駆動のテストを行いたい場合は、RobotFrameworkなども合わせて検討した方が望ましいです。

以下はGoogleトレンドにおけるRobotFrameworkとFitNesseの比較になります。

単純な例

dockerコンテナでfitnesseとseleniumのコンテナを起動して簡単なブラウザテストのサンプルを確認します。

  1. 以下のリポジトリの内容を取得する

https://github.com/mima3/test_fitnesse

  1. コンテナをビルド後、起動します。
docker compose build
docker compose up
  1. http://127.0.0.1:8080にアクセスする

  2. ブラウザテストサンプルを選択する

  3. Open Browser を選択する

  4. 画面上部のTestを実行する

seleniumコンテナ上のSeleniumが動作して、ブラウザの操作がおこなわれる。

  1. テストの実行結果が表示される。正常に動作した項目は緑となる


単純な例の解説

この単純な例は以下のページを参考に作成しています。
FitNesse Training: 2. How to get started with FitNesse

Seleniumをローカルでなく別コンテナのリモートのSeleniumを使用している違いがありますが、基本的には上記ページの方法で行えます。

How to get started with FitNesseを参考に動かす場合の注意

How to get started with FitNesseは素晴らしいチュートリアルですが、実際に動かす場合にはいくつか注意すべき事柄があります。

Step 6 — Starting FitNesse
トラブル時のヒントがほぼないので-vをつけておいたほうが無難です。
これで詳細なログが表示されます。

Step 11 — Specify the page field
pathにfixturesのパスを指定してますが絶対パスにしておいた方が無難です。

Step 34 — Copy this text in the page field
この例ではGoogle検索を行っていますが、現在、SeleniumでのGoogle検索は遮断される可能性があります。
のように適当なページに変更しておいた方が望ましいです。

Step 38 — Check if your first test was successful
おそらく、エラーになります。
画面上のエラーメッセージやDockerコンテナのログメッセージをみて対応しましょう。
もし、chromedriverのエラーが出る場合は、新しいchromedriverをダウンロードしてwebdriversドライバのもとと置き換えます。

macの場合、入手したchromedriverは、そのままだと実行できないので、セキュリティーとプライバシーでブロックを解除する必要があります。

簡単な例の動作の仕組み

Wikiのテーブルは命令リストに変換され、Slimサーバへ送られます。
Slimサーバはその指示に従ってBrowserTestを生成します。
BrowserTest は Selenium WebDriver のクライアントとして動作し、設定に応じて リモート または ローカルのドライバ経由でブラウザを操作・検証します

テストスイートの作成

受け入れテストを書くには、まずテストスイートを作成する必要があります。
ここで類似したテストを集約したり、テスト毎のSetUp処理、TearDown処理を記載します。

トップページなどで、Add→Suite Pageを選択します。

Page nameを入力します。ここで指定した名前でアクセスするようになります。

たとえば、「BrowserTests」と入力した場合、http://127.0.0.1:8080/FrontPage.BrowserTestsでアクセスするようになります。

テストスイートのコンテンツにはテストスイートの内容を記載したり、テストスイート共有の設定を行います。
Docker中のBrowserTestsテストスイートの内容は以下のようになっています。

This is my first test suite.
!contents -R2 -g -p -f -h
!*> Basic setup for tests with Slim
Workaround for https://github.com/unclebob/fitnesse/issues/989
!define slim.port {0}
!define TEST_SYSTEM {slim}
!define ALL_UNCLE_SUITE_SETUPS {true}
The classpath location:
!path /opt/fitnesse/fixtures
!path /opt/fitnesse/fixtures/*.jar
*!

Basic setup for tests with Slimの項目に記載されているのはテストを動かすための設定項目です。

  • slim.port: slimが動作するポートを指定する
  • TEST_SYSTEM {slim}: テストの実行系をSlimに切り替えます
  • ALL_UNCLE_SUITE_SETUPS: 後述のSuiteSetUp および SuiteTearDownを記載するため
  • path: テストに使用するクラスパスを追加

テストスイートにSuiteSetUpを追加

テスト実行のたびに動作させるために共通のセットアップ処理を実行させる必要があるケースがあります。
この場合、テストスイートからSuiteSetUpを追加します。

まず、テストスイートのページに遷移します。
(今回の例ではhttp://127.0.0.1:8080/FrontPage.BrowserTests

トップメニューのAdd→Static Pageを選択します。

入力画面が表示されます。

Page name: SuiteSetUp
コンテンツ:

This set up page is executed once, before any tests in this suite.
In it we configure which Java packages contain the logic for our fixtures, which browser we want to use for Selenium tests and we start this browser.
This means we do not have to do this in every test, which means easier maintenance and better performance.
Stopping the browser is done in SuiteTearDown, which is done after all tests in this suite are completed.
| Import                            |
| nl.hsac.fitnesse.fixture.slim.web |


|script|Selenium driver setup|
|connect to driver for|chrome|at|http://selenium:4444/wd/hub|
| show             | driver description    |

| Import                            |
| nl.hsac.fitnesse.fixture.slim.web |

これはページ中で使用するライブラリをインポートするのに使用します。
今回はnl.hsac.fitnesse.fixture.slim.webをインポートしてます。

|script|Selenium driver setup|

ScriptTableを使用して操作を実施します。
まず、1行目のSelenium driver setupSeleniumDriverSetupクラスをインスタンス化します。

|connect to driver for|chrome|at|http://selenium:4444/wd/hub|

この意味はconnectToDriverForAtchromehttp://selenium:4444/wd/hubを引数に呼び出します

obj.connectToDriverForAt("chrome", "http://selenium:4444/wd/hub");

Graceful Namingのルールによると表の奇数列の文字を結合してキャメルケースで表したメソッドに対して、偶数列をパラメータとして渡す挙動になります。

| show             | driver description    |

driverDescriptionメソッドの結果を表示する命令になります。

テストを実行した場合、以下のようにドライバの詳細情報が表示されるようになります。

テストスイートにSuiteTearDownを追加

テスト実行のたびに動作させるために共通の終了処理を実行させる必要があるケースがあります。
この場合、テストスイートからSuiteTearDownを追加します。

まず、テストスイートのページに遷移します。
(今回の例ではhttp://127.0.0.1:8080/FrontPage.BrowserTests

トップメニューのAdd→Static Pageを選択します。

入力画面が表示されます。

Page name: SuiteTearDown
コンテンツ:

This set up page is executed once, after all tests in this suite.
| script| selenium driver setup |
| show  | run summary           |
| stop driver                   |

これは、SeleniumDriverSetupクラスをインスタンス化して、runSummarystopDriverを実行しています。

テストスイートにテストケースを追加

実際にテストを実行するためのページを追加します。
まず、テストスイートのページに遷移します。
(今回の例ではhttp://127.0.0.1:8080/FrontPage.BrowserTests

トップメニューのAdd→Test Pageを選択します。

Page name: OpenBrowser
コンテンツ:

|script|browser test|
|open|https://the-internet.herokuapp.com|
|wait for visible|link=Add/Remove Elements|
|show|take screenshot|home-page|
|show|save page source        |
|click|link=Add/Remove Elements|
|wait for tag|h3|with text|Add/Remove Elements|
|show|take screenshot|home-page|
|show|save page source        |

このページのテストが実行される前にSuiteSetUpページのスクリプトが動作します。これによりSeleniumの設定が共通的に実施されます。

このページで行なっているテストの内容は以下の通りです。

  • BrowserTestクラスをインスタンス化します
  • openを使用してhttps://the-internet.herokuapp.comを開きます。
  • waitForVisiblelink=Add/Remove Elementsを引数として渡します。これは「Add/Remove Elements」という文字が表示されるまで待機します。
  • |show|take screenshot|home-page| スクリーンショットを撮ってテスト結果に表示します。
  • |show|save page source | ページのソースコードを保存してテスト結果に表示します。
  • clicklink=Add/Remove Elementsの要素をクリックします。
  • waitForTagWithTextを使用してh3タグにAdd/Remove Elementsが出るまで待機します。
  • スクリーンショットとページのソースコードの保存を行います。

このページのテストが終了された後にSuiteTearDownページのスクリプトを実行してSeleniumの終了処理を行います。

別プログラミング言語でテストを書く

FitNesse は Wiki のテーブルを Slim 経由で実行します。Slim サーバが用意されている言語であれば、その言語で実装した Fixture を使ってテストできます。

FitNesseとSlimサーバの連携はソケット通信または標準入出力でおこなわれます。
このプロトコルの詳細はThe Slim Protocolを参照してください。

Slimサーバーの対応言語は公式のSLiM servers for different languages/environmentsを参照してください(古い実装もあるため要確認)
には、様々なプログラミング言語をサポートするSlimサーバーが紹介されています。

SlimJsを使用したNode.jsのテスト

SlimJSはnode.js用のSlimサーバーです。
最終更新日が6年前のため、Slimの通信プロトコルはv0.4で止まっておりソケット通信のみの対応となっています。
また、Fixtureの実装も古い形式のJavaScriptで記載する必要があります。

サンプル実行

dockerコンテナを動かしている場合, http://127.0.0.1:8080/FrontPage.SlimJsからSlimJSの動作確認を行えます。
トップメニューからTest を押下することでテストを実行できます。


Fixtureの例

/opt/fitnesse/slimjs_fixtures/にFixture用のファイルを配置します。
このファイルでテスト用のクラスを実装しますが、この書き方はES5以前の書き方をする必要があります。

/opt/fitnesse/slimjs_fixtures/hello.js

function Hi(){
    this.setEcho = function(str){
        this.echo = str;
    }

    this.sayHi = function(){
        return "Hi! " + this.echo;
    }
}

module.exports.Hi = Hi;

/opt/fitnesse/slimjs_fixtures/calc.js

function Calc(){
    this.setA = function(str){
        this.a = Number(str);
    }
    this.setB = function(str){
        this.b = Number(str);
    }

    this.add = function(){
        return this.a + this.b;
    }
}

module.exports.Calc = Calc;

テストページ

!define TEST_SYSTEM {slim}
!define SLIM_PORT {9086} 
!define COMMAND_PATTERN {slimjs %p}
!path /opt/fitnesse/slimjs_fixtures

|import      |
|hello|

SlimJSを使用したテストの実験
https://github.com/noamtcohen/SlimJS/tree/master

|Hi            |
|echo |sayHi?  |
|Bob  |Hi! Bob |

|script  |Hi|
|setEcho  |Joe|
|show|sayHi?|
|check|sayHi?|Hi! Joe|



|import      |
|calc|


|Calc        |
|a |b |add?  |
|1 |2 |3     |
|-1|2 |1     |
|0 |0 |0     |

テストの設定項目

  • SLIM_PORT: Slimサーバーが動作するポートを指定する必要があります。ここで指定したポートでSlimサーバーはFitNesseからの指示を待ちます。1024より大きい値を指定しておいた方が無難です。
  • COMMAND_PATTERN: slimjsを実行するコマンドです。SlimJSをグローバルにインストールしているため、slimjsだけで実行できます。
  • path: fixturesを配置するパスを指定します。slimjsに渡した%pには、ここで指定したパスが入り、このpath中のフォルダを探索してテストに使用します。
import calc

/opt/fitnesse/slimjs_fixtures/calc.jsを読み込みます。

|Calc            |

scriptなどを特別に指示しない場合は、データ駆動のように動作します。
まず、Calcクラスをインスタンス化します。

|a |b |add?  |
|1 |2 |3     |
|-1|2 |1     |
|0 |0 |0     |

これは以下の処理を行の数だけ繰り返します。

  • setA(列aの値)
  • setB(列bの値)
  • add()の結果と列cの値

SlimJsの修正

SlimJsを改造して以下の機能を追加します

  • 標準入出力で通信する
  • classでFixtureを書けるようにする。

この実装については以下を参照してください。
https://github.com/mima3/test_fitnesse/tree/main/fitnesse/slimjs_std

Fixtureの例

/opt/fitnesse/slimjs_std/fixtures/にFixture用のファイルを配置します。
もとのSlimJsと違い,class構文を使用してclassを作成できます。

/opt/fitnesse/slimjs_std/fixtures/hello.js

export class Hi {
    constructor(echo) {
        this.echo = echo;
    }
    setEcho(str){
        this.echo = str;
    }

    sayHi(){
        return "Hi! " + this.echo;
    }
}

/opt/fitnesse/slimjs_std/fixtures/calc.js

export class Calc {
    constructor() {
        this._a = 0
        this._b = 0
    }
    setA(value) {
        this._a = value;
    }
    setB(value) {
        this._b = value;
    }

    add(){
        return this._a + this._b;
    }
}


テストページ

!define TEST_SYSTEM {slim}
!define COMMAND_PATTERN {/opt/fitnesse/slimjs_std/slim_std.js %p}
!path /opt/fitnesse/slimjs_std/fixtures

|import      |
|hello|

SlimJSを使用したテストの実験
https://github.com/noamtcohen/SlimJS/tree/master

|Hi            |
|echo |sayHi?  |
|Bob  |Hi! Bob |

|script  |Hi|
|setEcho  |Joe|
|show|sayHi?|
|check|sayHi?|Hi! Joe|




|import      |
|calc|


|Calc        |
|a |b |add?  |
|1 |2 |3     |
|-1|2 |1     |
|0 |0 |0     |

基本的な書き方はSlimJsの時とかわりません。
違いは以下の通りです。

  • SLIM_PORTが不要になった
  • COMMAND_PATTERN : パスを変更
  • path: パスを変更

まとめ

今回はFitNesseの使い方についてまとめました。
既存のSlimサーバーの使い方だけではなく、独自のSlimサーバーの作り方についても紹介しました。
これを活用することで、現在でも十分に使用することが可能です。
ただし、サードパーティーのSlimサーバーの更新状況はかんばしくないため、今から採用する場合は別の選択肢を十分に考慮してから採用をしたほうが望ましいと思います。

さらに詳しい情報が必要な場合は以下のページが参照してください。

Discussion