[前回] Web3.0検証(20)-MeteorでTODO管理アプリの開発(publish/subscribeによるデータ制御)
はじめに
今回は、アプリのリグレッションテスト機能です。
リグレッションテストの機能追加
- テスト機能を追加し、リグレッション発生せず、期待どおり機能することを確認
- リグレッションテスト(Regression Test)とは
- プログラムの変更に伴い、システムに予想外の影響が現れていないかを確認するテスト
- リグレッションは、
回帰テスト
、退行テスト
とも呼ばれる
- メソッドの1つを実行し、正しく機能するか確認するテストを作成
アプリをテストモードで実行
-
依存関係をインストール
-
Mocha
JavaScriptテストフレームワークのテストドライバーを追加
$ meteor add meteortesting:mocha
- テストアサーションライブラリをインストール
$ meteor npm install --save-dev chai
-
-
アプリを
テストモード
で実行- テストドライバーパッケージを指定し、
meteor test
を実行 - ポート番号が衝突しないように
- 通常アプリの実行を停止するか
-
–port XYZ
で代替ポートを指定
- テストドライバーパッケージを指定し、
$ cd simple-todos-react
$ TEST_WATCH=1 meteor test --driver-package meteortesting:mocha
[[[[[ Tests ]]]]]
=> Started proxy.
=> Started HMR server.
=> Started MongoDB.
I20220603-21:31:00.827(9)?
I20220603-21:31:00.840(9)? --------------------------------
I20220603-21:31:00.841(9)? ----- RUNNING SERVER TESTS -----
I20220603-21:31:00.841(9)? --------------------------------
I20220603-21:31:00.841(9)?
I20220603-21:31:00.842(9)?
I20220603-21:31:00.842(9)?
I20220603-21:31:00.842(9)? simple-todos-react
=> Started your app.
=> App running at: http://localhost:3000/
I20220603-21:31:00.845(9)? ✓ package.json has correct name
I20220603-21:31:00.845(9)? ✓ server is not client
I20220603-21:31:00.846(9)?
I20220603-21:31:00.846(9)?
I20220603-21:31:00.846(9)? 2 passing (8ms)
I20220603-21:31:00.847(9)?
I20220603-21:31:00.847(9)? Load the app in a browser to run client tests, or set the TEST_BROWSER_DRIVER environment variable. See https://github.com/meteortesting/meteor-mocha/blob/master/README.md#run-app-tests
サーバーテストは成功しました。
- ブラウザでクライアントテスト
http://localhost:3000/
にアクセス。
クライアントテストも成功しました。
- 上述テストが可能な理由
- Meteorアプリケーションには、サンプルテストを含む
tests/main.js
モジュールが含まれている -
Mocha
などのテストフレームワークでサポートするdescribe
、it
、assert
スタイルを使用できる
- Meteorアプリケーションには、サンプルテストを含む
スキャフォールド(Scaffold)テスト
- テストを複数モジュールに分割可能
- 以下の新しいテストモジュールを追加
imports/api/tasksMethods.tests.js
import { Meteor } from 'meteor/meteor';
if (Meteor.isServer) {
describe('Tasks', () => {
describe('methods', () => {
it('can delete owned task', () => {});
});
});
}
-
tests/main.js
にインポート - これ以外のテストは不要で残りコードをすべて削除
tests/main.js
import '/imports/api/tasksMethods.tests.js';
データベースをテスト
- データベースが期待どおりの状態にあることを確認
-
Mocha
のbeforeEach
コンストラクトを使用
-
- テスト実行ごとに異なるランダムな
userId
に関連付けられた、単一タスクを作成
imports/api/tasksMethods.tests.js
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { TasksCollection } from '/imports/db/TasksCollection';
if (Meteor.isServer) {
describe('Tasks', () => {
describe('methods', () => {
const userId = Random.id();
let taskId;
beforeEach(() => {
TasksCollection.remove({});
taskId = TasksCollection.insert({
text: 'Test Task',
createdAt: new Date(),
userId,
});
});
});
});
}
タスク削除をテスト
- 認証されたユーザーで
tasks.remove
メソッドを呼び出すテストを記述- タスクが削除されたことを確認
- メソッドを簡単に呼び出すため、ユーティリティパッケージをインストール
$ meteor add quave:testing
- アサーションツール
chai
から、assert
をインポート
imports/api/tasks.tests.js
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { mockMethodCall } from 'meteor/quave:testing';
import { assert } from 'chai';
import { TasksCollection } from '/imports/db/TasksCollection';
import '/imports/api/tasksMethods';
if (Meteor.isServer) {
describe('Tasks', () => {
describe('methods', () => {
const userId = Random.id();
let taskId;
beforeEach(() => {
TasksCollection.remove({});
taskId = TasksCollection.insert({
text: 'Test Task',
createdAt: new Date(),
userId,
});
});
it('can delete owned task', () => {
mockMethodCall('tasks.remove', taskId, { context: { userId } });
assert.equal(TasksCollection.find().count(), 0);
});
});
});
}
その他テスト
- 必要なテストを追加
imports/api/tasks.tests.js
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { mockMethodCall } from 'meteor/quave:testing';
import { assert } from 'chai';
import { TasksCollection } from '/imports/db/TasksCollection';
import '/imports/api/tasksMethods';
if (Meteor.isServer) {
describe('Tasks', () => {
describe('methods', () => {
const userId = Random.id();
let taskId;
beforeEach(() => {
TasksCollection.remove({});
taskId = TasksCollection.insert({
text: 'Test Task',
createdAt: new Date(),
userId,
});
});
it('can delete owned task', () => {
mockMethodCall('tasks.remove', taskId, { context: { userId } });
assert.equal(TasksCollection.find().count(), 0);
});
it(`can't delete task without an user authenticated`, () => {
const fn = () => mockMethodCall('tasks.remove', taskId);
assert.throw(fn, /Not authorized/);
assert.equal(TasksCollection.find().count(), 1);
});
it(`can't delete task from another owner`, () => {
const fn = () =>
mockMethodCall('tasks.remove', taskId, {
context: { userId: 'somebody-else-id' },
});
assert.throw(fn, /Access denied/);
assert.equal(TasksCollection.find().count(), 1);
});
it('can change the status of a task', () => {
const originalTask = TasksCollection.findOne(taskId);
mockMethodCall('tasks.setIsChecked', taskId, !originalTask.isChecked, {
context: { userId },
});
const updatedTask = TasksCollection.findOne(taskId);
assert.notEqual(updatedTask.isChecked, originalTask.isChecked);
});
it('can insert new tasks', () => {
const text = 'New Task';
mockMethodCall('tasks.insert', text, {
context: { userId },
});
const tasks = TasksCollection.find({}).fetch();
assert.equal(tasks.length, 2);
assert.isTrue(tasks.some(task => task.text === text));
});
});
});
}
-
package.json
ファイルのscripts
セクションに省略形を追加- テストコマンドを簡単に入力できるようになる
- Meteorアプリに付属された、事前構成済みnpmスクリプトを使用/変更
-
サーバー側のテストのみ実行したい
- 標準の
meteor npm test
コマンドを用いる、次のコマンドと等価 $ meteor test --once --driver-package meteortesting:mocha
- すべてのテストに合格したら
0
で終了 -
Travis CI
やCircleCI
など、CI(継続的インテグレーション)環境での実行に適している
- 標準の
-
アプリケーションの開発中にテストを実行したい
-
または、開発サーバーが再起動するたびにテストを再実行したい
-
meteor npm run test-app
を使用、次のコマンドと等価 -
TEST_WATCH=1 meteor test --full-app --driver-package meteortesting:mocha
-
--full-app
を追加することで、アプリケーションコードも読み込まれる
-
-
-
ブラウザからアプリを確認
- ログイン
- ユーザー名: meteorite
- パスワード: password
- ログイン
クライアントテストとサーバーテストの両方を実行しながら、
ブラウザーでアプリを操作できるようになりました。
おわりに
アプリのリグレッションテスト機能を実装しました。
次回も続きます。お楽しみに。