QA が Notion API を使ってちょっとしたシステムを作った話

こちらの記事は [NewsPicks Advent Calendar 2024](https://qiita.com/advent-calendar/2024/newspicks) の3日目の記事です。

# はじめに

こんにちは、ニューズピックスでQAエンジニアをやっています西薗です。最近まで育休をとっていたこともあって、ここ1年くらいブログを書いたり登壇したりといったことをしておらず、もともと知られてないのにさらに業界から忘れられていっているのでは感が出ています。次男ももうすぐ1歳になるので、また頑張ります。

<!--
## 仕様とテストとカバレッジ

我がQAチームでは、リグレッションテストが穴だらけでモグラ叩き状態になっていた、という課題を解決するために[仕様を一覧化し、テストカバレッジを計測しました](https://tech.uzabase.com/entry/2022/12/09/000000)。これらはスプレッドシートで管理していたのですが、特に紐付け（カバレッジ）を管理するのが結構面倒でして、メンテナンスが追いつかず、最新の数字を出すのに作業が必要でした。

想像してみてください、せっかく作ったダッシュボードがポータルサイトの奥の方に隠されていて、表示するのにあれこれ入力が必要で、という状況を。それでも関係者は見てくれるでしょうか？多分、半年後も見てるのは作った本人だけですね。

そこで今年、仕様一覧とテストケース、およびその紐付け（カバレッジ）をスプレッドシートからNotionに移行しました。本記事ではその話をします。
-->

<!--
Jestのコードカバレッジを測る機能って実は結構使えるのでは？と言う気がしたので、調べておく。
-->

# 我々にはデータベースが必要だ

普段の業務で、データベースとフロントを持つアプリケーションを作りたい、と思うことが結構あります。開発エンジニアとして成長したい！とかそういう話ではなく、実需の話です。

例えば私は最近、APIの一覧とテストコードがあって、その紐付けをデータベースに格納し、APIのカテゴリごとにテストカバレッジをユーザーに見せるようなアプリケーションが欲しいな、と思いました。スプレッドシートでも管理はできるんですが、カテゴリごとに表示したり、そこからテストコードに飛ばしたり、って色々考え出すと逆に面倒なんですよね。

それこそ、データベースを用意して、そこにフロントを作るか、という話になってしまう。データベースは`SQLite`にして、フロントは……ってことを私がやりだすとテストケースの更新などが疎かになりそうなので、さくっとやりたい。

<!--
作るのは大変なので既製品を買おうか、というと、それって１ライセンス何十万もするようなテスト管理ツールなんですよ。いやぁお高いなぁと。やっぱりゼロから作ってみます？
-->

# Notion とは

私の理解では、Wikiというフロントを持ったデータベースです。複数のテーブルを定義でき、各レコードはwikiページとして編集が可能。また全てのページどころか、ページ内の全ての要素にいたるまでが個別のURLを持ちます。

自動化の機能も豊富で、後述するAPIを使う方法を含め、いろんな外部ツールとも連携が可能。いまどきの、多種多様なSaaS製品を使う職場環境とよくマッチしたのでしょう、ここ数年で一気に利用が広がったようです。

私も、大抵の用事はNotionで出来ちゃう、と思い始めました。これは令和のエクセルですよ。私が勝手にそう呼んでるだけですが。

# ということで Notion を使おう

色々できちゃうNotionですが、あくまでGUIのドキュメンテーションツールです。画面左端にツリー構造があって、そのうちの一つを開くとWikiページが表示されて、ページの中で新たなテーブル定義を作ったり（データベースの作成）、そこにレコードを挿入したり（ページの挿入）、レコードを開くとまたWikiページが表示されたりします（レコード＝ページなので、それもまたWikiページとして編集が可能）。

![Screenshot 2024-12-02 at 14.46.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2963124/ceed193a-7e42-2a2f-dc53-2cfb3bc9e395.png)



これまでのWiki系製品と比べると十分に自由度が高くて便利だなと思います[^1]が、これを「システム」として使うとなると、レコードの挿入や削除、変更といった操作はプログラムから行いたいですね。

# Notion API を使う

Notion は利用者に API を公開してくれておりまして、これを使うとデータベースの定義やレコードの挿入、更新などがHTTPメッセージを投げることで行えます。JavaScriptからNotion APIを叩くためのライブラリも公開されています。

https://developers.notion.com/

https://www.npmjs.com/package/@notionhq/client

# 実装

ここからは、先述したAPIテストのカバレッジを見るためのシステムをさらっとご紹介します。

## カバレッジの取得方法を決める

NewsPicks Server は Swagger(OpenAPI) 対応しており、開発環境であれば API のエンドポイント一覧が取得できます。またAPIテストは [@openapitools/openapi-generator-cli](https://www.npmjs.com/package/@openapitools/openapi-generator-cli) で生成した API Client を使って実装をしていますから、以下の関係を引っ張ってくればカバレッジを計測できそうです。

```
API endpoint の一覧 -(1)-> API Client のメソッド -(2)-> テストコード内でのメソッド呼び出し
```

## (1) API endpoint の一覧 -> API Client のメソッド

`openapi3.json` から、`jq` を使って欲しい情報を抽出します。具体的には、`paths` 直下のキー名がエンドポイントで、その下にキー名としてHTTPメソッド（`get`なのか`post`なのか）があり、その中の `operationId` が API Client のメソッド名として使われます。

```json
{
    "openapi": "3.0.0",
    "paths": {
        "/path/to/endpoint": {
            "get": {
                "tags": [
                    "qa-controller"
                ],
                "summary": "JavaMethod: QAController/getTestCase",
                "operationId": "getTestCaseUsingGET",
```

例えば`operationId`ならこんな感じで抽出ができますね。この要領で必要な情報をCSVなどに落とします。

```bash
cat ../openapi3.json | jq -r '..|.operationId?|strings'
```

## (2) API Client のメソッド -> テストコード内でのメソッド呼び出し

本当はASTなど使えば良いのでしょうが、さくっとやりたかったので、テストコード内でそのメソッドが呼び出されているか否かは文字列を検索することで確認します（従って、コメントアウトなどは考慮しません）。以下、シェルスクリプトの抜粋です。

```bash
# ${METHOD}がテストコードに含まれていることを確認する
ISCOVERED=$(grep -ro "${METHOD}\W" "${PR_HOME}/../src/test" | wc -l | tr -d ' ')
if [[ $ISCOVERED -gt 0 ]]; then
    ISCOVERED="TRUE"
else
    ISCOVERED="FALSE"
fi
```

## (3) API一覧と、テストコードで呼び出されているか否かを Notion DB に書き込む

なんだかんだあってAPI名と、それがテストコードで呼び出されているかがズラッと書かれたCSVファイルが出来上がりまして、今度はそれを Notion API を使って Notion に書き込みます。

```text
controller,apiEndpoint,apiTestMethod,isCovered
qa-controller,get /path/to/endpoint,getTestCaseUsingGET,TRUE
qa-controller,post /path/to/endpoint,createTestCaseUsingPOST,FALSE
```

Notion 側で予め、API一覧と、それがテストでカバーされているかという情報を格納するデータベースを用意します。

![Screenshot 2024-12-03 at 10.25.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2963124/2fc5cea1-6bff-cff4-4304-79b5d33ccfb3.png)

型定義っぽく書くならこんな感じ（こちらはあくまで擬似コードですが）。
```typescript
const APIs = {
    "apiEndpoint": TITLE,
    "controller": RICH_TEXT,
    "apiTestMethod": RICH_TEXT,
    "isCovered": CHECKBOX,
}
```

そして、これをNotion APIで送信します。下記コードではわかりやすいように値を直接入れてます。

```typescript
await client.pages.create({
    parent: {
        database_id: "<データベースのIDを記載>",
        type: "database_id",
    },
    properties: {
        "apiEndpoint": { title: [{ text: { content: "get /path/to/endpoint" } }] },
        "controller": { rich_text: [{ text: { content: "qa-controller" } }] },
        "apiTestMethod": { rich_text: [{ text: { content: "getTestCaseUsingGET" } }] },
        "isCovered": { checkbox: true },
    },
});
```

と、こんな感じで初めましてのSDKを使って試行錯誤した結果、何となく格好がつきました。下図では２レコードですが、実際にはスクロールするのも大変な量のレコードがあります。これが勝手に入ってくれると気持ちいいですね。

![Screenshot 2024-12-03 at 10.41.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2963124/40af04bc-fc40-63a4-659a-78e84481d398.png)

ちなみにカバレッジ（一番左の`50%`がそれです）を計算する都合上、レコードの親子関係と、Rollup propertyというものを使っています。子レコードの特定のプロパティ（`isCovered`）を一通りみて、`true`になっているレコードの割合を計算することでカバレッジを算出しました。このあたりはGUIの設定だけで実現可能です。

## (4) 以上の仕組みをGitHub Actionsで呼び出す

もう一つやったことがありました。ここまでお話しした仕組みを手で動かすのではなく、GitHub Actionsに組み込みました。こちらはymlファイルですね。

リポジトリのテストフォルダ以下が編集され、mainブランチにマジされたら勝手に動いてくれます。
```yml
on:
  push:
    branches:
      - main
    paths:
      - 'test/api/**'
jobs:
  <略>
```

# おわりに

今回はとにかく「Notionいいよ！」ということを伝えたかったのと、「QAエンジニアのみんな、Notion使えば少しの開発でもそれっぽいものが出来るからぜひやってみてね！」ということが言いたかったのでした。一旦さらっとご紹介、というところで。

今回紹介した仕組みについては、もうちょっとその周辺でやっていることがあるので、別途また紹介しようかなと思います。

[^1]: ということをQiitaで書くなよ、と後から思いましたが、Notionでブログを書きたいかというとまた違うんですよね。