Webアプリケーションのテスト&ドキュメンテーションツールと戯れる
この記事はソフトウェアテスト Advent Calendar 2024 11日目の記事です。
自動テストやドキュメンテーションのツールにはさまざまな種類がありますが、実務上で触れることはそこまで多くないので理解を深めるために最低限のWebアプリケーションを作りつつそれに対して各種ツールを導入しCI/CDに含めるところまでやってみました。
性質上、説明は浅く広くにはなってしまいますがあらかじめご了承ください。少しでも参考になれば幸いです。
今回導入したツールとコード
開発したのは簡単なTodoアプリですが、今回は周辺ツールの方がメインです。導入したツールは以下の通りです。
ドキュメンテーションツール
Swagger(APIドキュメンテーションツール)
Storybook(UIカタログ)
テスト自動化ツール
ESlint(静的解析ツール)
Jest(テストフレームワーク)
Storybook のテストランナー(スナップショットテストツール)
Playwright(E2E自動化ツール)
また、言語はバックエンドをJavaScript(Node.js)、フロントエンドをTypeScript(React)で書きました。アプリケーションはDocker上で動きます。また、CI/CDはGithub Actionsを使用しています。
コードはこちらで公開しています。
ドキュメンテーションツール
Swagger
SwaggerはAPIのドキュメンテーションツールです。コードをフォーマットに沿って書けばドキュメントが自動生成されます。環境ごとにパラメータを変えたりAPIテストの自動化をすることについてはPostmanに軍配が上がりますが、Swaggar上からテストを行うことも可能です。
Swaggerの作成方法で一番メジャーなのはswagger.ymlなどのyaml形式かjson形式でファイルを書くことですが、今回使用したNode.jsのExpress(フレームワーク)では以下のようにAPIコードに直接書き込むことでSwaggerを作成できます。
/**
* @swagger
* /todos:
* post:
* summary: Add a new todo
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* title:
* type: string
* description:
* type: string
* responses:
* 200:
* description: The created todo
*/
router.post('/', addTodo);
SwaggerはDockerを起動して、以下のURLにアクセスすると閲覧可能です。
http://localhost:5000/api-docs/
Storybook
StorybookはUIカタログです。ReactやVue.jsなどで作成したコンポーネント(画面のパーツ)を確認することができます。APIとの疎通が必要ないので、Dockerの起動なしに起動することができます。わざわざStorybookを書く手間が必要、そもそものコードをStorybookが書きやすいように設計する必要があるなどそれなりにハードルもありますが、そこをクリアできればデザインの確認がStorybook上で完結する、なんてこともできるかもしれません。
後述しますが、自動のリグレッションテストとして活用することもできます。
/frontend上から以下のコマンドで起動できるようになっています。
npm run storybook
テスト自動化ツール
ESLint
ESLintはJavaScriptの静的解析ツールで、コードの規約に違反していないかを自動でチェックしてくれます。これにより、コードの品質を一定保つことができます。今回の場合は以下のように実行します。
user@MacBook-Air frontend % npx eslint .
/Users/user/testing-playground/frontend/src/components/TodoForm.tsx
2:21 error 'Text' is defined but never used @typescript-eslint/no-unused-vars
✖ 1 problem (1 error, 0 warnings)
上ではフロントエンドで実行していますが、バックエンドにも導入してあります。
Jest
JestはJavaScript用のテストフレームワークです。単体テストを行ううえでそのコードが依存しているもの(例としてDB)をモックするのが容易です。
user@MacBook-Air backend % npm test
> backend@1.0.0 test
> jest
console.log
Connection has been established successfully.
at log (db.js:14:21)
PASS tests/todoService.test.js
Todo Service
✓ should return all todos (1 ms)
✓ should add a new todo (1 ms)
✓ should update an existing todo
✓ should delete an existing todo
✓ should return null when updating a non-existing todo (1 ms)
✓ should return false when deleting a non-existing todo
Test Suites: 1 passed, 1 total
Tests: 6 passed, 6 total
Snapshots: 0 total
Time: 0.325 s, estimated 1 s
--coverageオプションをつけることでカバレッジを出力することも可能。
user@MacBook-Air backend % npm test -- --coverage
> backend@1.0.0 test
> jest --coverage
console.log
Connection has been established successfully.
at log (db.js:14:21)
PASS tests/todoService.test.js
Todo Service
✓ should return all todos (3 ms)
✓ should add a new todo
✓ should update an existing todo (1 ms)
✓ should delete an existing todo
✓ should return null when updating a non-existing todo
✓ should return false when deleting a non-existing todo
------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------------|---------|----------|---------|---------|-------------------
All files | 85.71 | 100 | 85.71 | 87.5 |
backend | 70 | 100 | 66.66 | 72.22 |
db.js | 70 | 100 | 66.66 | 72.22 | 17-21,28-29
backend/models | 100 | 100 | 100 | 100 |
Todo.js | 100 | 100 | 100 | 100 |
backend/services | 100 | 100 | 100 | 100 |
todoService.js | 100 | 100 | 100 | 100 |
------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 6 passed, 6 total
Snapshots: 0 total
Time: 0.393 s
Ran all test suites.
これはバックエンドですが、フロントエンドにも同様に単体テストを書いています。
user@MacBook-Air frontend % npm run test -- --watchAll
PASS src/components/TodoItem.test.tsx
PASS src/components/TodoList.test.tsx
PASS src/components/TodoForm.test.tsx
Test Suites: 3 passed, 3 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 2.08 s
こちらもカバレッジを出してみたら、バックエンドと比べて結構低かった。
user@MacBook-Air frontend % npm run test -- --watchAll --coverage
PASS src/components/TodoItem.test.tsx
PASS src/components/TodoList.test.tsx
PASS src/components/TodoForm.test.tsx
--------------|---------|----------|---------|---------|----------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------|---------|----------|---------|---------|----------------------
All files | 42.59 | 20 | 30.43 | 44.23 |
TodoForm.tsx | 100 | 50 | 100 | 100 | 13
TodoItem.tsx | 53.33 | 50 | 16.66 | 53.33 | 18,22-23,27-28,39-45
TodoList.tsx | 14.28 | 0 | 15.38 | 15.38 | 41-94
--------------|---------|----------|---------|---------|----------------------
Test Suites: 3 passed, 3 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 1.359 s, estimated 2 s
Storybook のテストランナー
同様のことができるものとして以前はStoryshotというStorybookのアドオンがあったのですが、Storybookのバージョンアップに伴い非推奨になったため、公式から推奨されている方法を用います。
スナップショットテストは、コンポーネントのUIをキャプチャして保存し、後でコードの変更による見た目の差分を自動的に検出するテスト手法です。リグレッションテストとして機能し、意図しないUIの変更を防ぐことができます。
前述したStorybookを書いておくと、スナップショットテストとしても機能します。
Playwright
ここ最近では特に有名になってきているE2Eのフレームワークです。今回はDockerを起動した上で、プロジェクトのルートディレクトリで実行できます。
user@MacBook-Air testing-playground % npm test
> test
> playwright test
Running 3 tests using 3 workers
3 passed (2.6s)
To open last HTML report run:
npx playwright show-report
具体的なことを書き始めると丸々1記事使っても書ききれないのですが、最近発売された以下の書籍で詳しく解説されています。
CI/CDに組み込む
テスト自動化についてはリリースサイクルの中に実行プロセスを含めないとあっという間に使い物にならなくなるのですが、最近はメジャーなソースコード管理ツール「GitHub」が「GitHub Actions」というワークフロー作成ツールを提供してくれているので、そこに組み込むと「プルリクエストを立てた時に自動で実行される」といったワークフローを簡単に実現できます。
今回は、以下のテストをGitHub Actionsに組み込んでいます。
フロントエンド
フロントエンドの単体テスト、および静的解析
スナップショットテスト
バックエンド
バックエンドの単体テスト、および静的解析
統合テスト
E2EをDocker上で実行する
Github Actionsのコードは.github/workflow/以下にいます。
終わりに
本当に触りだけになってしまったのですが、ご興味のある方はぜひコードの方をご参照いただけると幸いです。また、もしもっとご興味があり詳しく解説してほしいなどの需要があればコメントかXでご連絡いただければと思います。
ここまでお読みいただきありがとうございました。