LTI実装のテスト工程を経験して考えたこと

# はじめに
日本1EdTech協会 LTI部会の今村です。
日本1EdTech協会は、教育システム間のデータ連携の国際標準化団体 1EdTech Consortiumが管理している色々な技術標準の日本国内のユーザコミュニティです。
LTI部会は、様々な1EdTech関連技術標準のうち、特にLearning Tools Interoperability（以降LTI）のユーザグループになっています。

この記事では、LTI開発時のTIPSとして、過去に筆者が担当していたプロダクトで行ったテスト工程とベストプラクティスについて書きます。  
PlatformとToolの双方の開発に当てはまる内容になっています。
LTIはシステム間連携のための技術なので自システム単体でのテストは難しかったり、認証系なのでプログラム品質にはそれなりに気を使う必要があったりと、開発フローの中でもテスト工程は割と手がかかった記憶があります。

筆者が関わったシステムではLTI Coreに加えてDeep Linkingと一部AGSにも対応したのですが、この記事ではLTI Coreのテストを中心に書いていきます。
これからLTI対応を検討する方の参考になれば幸いです。

## 目次
[連携先システムの確保](連携先システムの確保)
[テストツールの準備](テストツールの準備)
[連携値の確認（準ホワイトボックステスト）](連携値の確認（準ホワイトボックステスト）)
[負荷テスト](負荷テスト)
　[負荷テスト用スクリプトの作成](負荷テスト用スクリプトの作成)
　[負荷テストの実施](負荷テストの実施)
[その他TIPS](その他TIPS)
[おわりに](おわりに)

# 連携先システムの確保
LTIはシステム間連携のための技術なので、テストや動作確認をするにはまず「連携先」となる相手方のシステムが必要です。
連携先の用意の仕方は、大きくは
1. Webで公開されているテストツールを使う。もしくは自前でスタブを作る
2. 想定している連携先に環境を貸してもらい、それを相手にテストを行う

の2通りがあるかと思います。

おすすめは「1から始めて、OKになったら2に進む」です。最初から2で開発やテストを始めてしまうのも手っ取り早くはあるのですが、「あれ？想定と違う挙動だな？」となった時に、自分達に問題があるのか、相手先システム固有の挙動なのか判別がつきにくいからです。
相手先システムの担当者に聞けばいいのですが、相手先の体制等によっては回答が得られるまで待たされてしまったりもします。

従って、まずテストツールでLTI連携の大筋の動きはできているのを確認した上で、実際の連携先システムを相手に、実ユーザシナリオに沿って確認していくのが、結局はいちばんスムーズかと思います。

# テストツールの準備
開発にあたって既に何かしら使われているかもしれませんが、以下におすすめを書いておきます。
- [1EdTech LTI Reference Implementation](https://lti-ri.imsglobal.org/)
    - 1EdTech本家がWebで公開しているテストツールです。「参照実装」と名がついていますが、どちらかというとテストツールとしての性格が強い気がします。LTI Coreであれば、1EdTech会員でなくても使えます。
- [saLTIer](https://saltire.lti.app/)
    - LTI Advanceの機能も一部制約はあるものの基本は誰でも使えます。筆者が関わったプロダクトでは、Platform側、Tool側ともsaLTIerをメインの動作確認用ツールとして使っていました。

ただ、これらのツールもUIがこなれていなくて分かりにくく、使い方のガイドもすべて英語だったりします。
テスト工程から加わるメンバーがいる場合は、テストツールの使い方やLTI連携設定の方法など、今回の開発～テストで必要になる箇所をピックアップして手順化しておくのがおすすめです。

# 連携値の確認（準ホワイトボックステスト）
LTI CoreやDeep Linkingなどブラウザを介する処理については、PlatformとToolのHTTPでのやりとりを覗けるツールを使って、設計通りに様々な値が連携されているか確認するのがおすすめです。
内容的にはブラウザの開発者ツールでも確認できるのですが、LTI Coreの場合、はじめに起動したPlatform上のページとは別のブラウザタブ／ウィンドウが立ち上がり、そちらでLTIのやりとりがされることがあります。
その場合、ブラウザの開発者ツールでは、はじめに起動したウィンドウの方しか確認できないので、別途HTTPをキャプチャするツールを入れたほうが確認しやすいです。

おすすめのツールは以下です。
- [Fiddler](https://www.telerik.com/fiddler)
    - HTTPデバッグプロキシです。筆者はWindowsを利用しているのでFiddlerを使いました。
- LTI Debugger
    - 執筆時点でバージョンは0.0.2で、[Chrome拡張](https://chromewebstore.google.com/detail/lti-debugger/cpjdeioljkbgkldnbojoagdoiggnlhll)と[Firefox拡張](https://addons.mozilla.org/af/firefox/addon/lti-debugger/)があります。
タイミングとしては、概ね実装ができた後ブラックボックス的な機能テストに先立って、スモークテストの一部として利用すると手戻りが少なくなります。

# 負荷テスト
もっとも悩ましいのが負荷テストでした。

筆者の担当していた製品は、使われる時は「せーの」で一気にアクセス数が伸びてオートスケールでは追い付かない、逆に使われない時はガラガラ…といったようなサービスで、さらにクライアントにもキャパシティ提示しなければならなかったので、負荷テストは必須でした。

負荷テストにしても自システム単体では実施できず、「連携先」が必要なのがここでもハードルです。
自前でスタブを用意する手もありますが、もし想定している連携プロダクトの相手方に相談できるようであれば、調整して負荷をかけさせてもらうことでもよいと思います。筆者の場合は、幸いにも連携先に協力してもらえることになりました。

技術面でハードルがあったのが負荷テストスクリプトの準備です。

## 負荷テスト用スクリプトの作成 
ここでは、パケットキャプチャツールを用いて負荷テストシナリオの中で発生するHTTPリクエストを取得し、負荷テスト用のスクリプトを書いていく工程について記述します。テストシナリオの作り方など、負荷テストに関する一般論的な話題はここでは割愛します。  

LTIローンチの負荷テストを行う場合、ここまで書いてきたように、連携先のアプリケーションに対してもHTTPリクエストを行うことになります。
そのため、負荷テストスクリプト作成者が連携先アプリケーションのAPIについて詳しくないということが起こり得ます。  
加えて今回の開発チームでは、LTIに詳しくないメンバーがスクリプト作成を担当したため、単一のシステムの負荷テストスクリプトを作成するのと比べて、LTIローンチのスクリプト作成は大きな障害となりました。  
ここでは、LTIやOpenID Connectに明るくないメンバーを含む開発チームが、LTI連携の負荷テストシナリオをスクリプトに起こす際に取った方法を記します。

### HTTPリクエストに含まれるパラメータをまとめる
スクリプト化を始めて直面した障壁は、シナリオ内のHTTPレスポンスに含まれるパラメータにどのようなものがあり、またそれらが可変であるものなのかを把握できていないことでした。
LTIローンチ時のOpenID Connect認証部分では、クライアントとサーバー間で多くのパラメータがやり取りされるため、一度それらパラメータを全てまとめてからスクリプトに起こすことにしました。

スクリプト作成時にまとめたやり方を以下の表に示します。  
負荷生成ツールが送信するHTTPリクエストと、ホストからのレスポンスの内容を交互に記述しています。
また、HTTPリクエストで送信するパラメータを「↑」、レスポンスで取得するパラメータを「〇」で記載しました（「↑」はこれまでの処理で受け取ったパラメータを用いるという意味です）。

|動作|リクエスト|URL|ホスト|login_hint|target_link_url|client_id|...|
|:---:|:---:|:---|:---:|:---:|:---:|:---:|:---:|
|ツール起動|GET|https://platform.url/lti-launch/...|platform||||...|
||レスポンス|||〇||〇|...|
|OIDC (tool→platform)|GET|https://tool.url/Lti/...|tool|↑||↑|...|
||レスポンス||||||...|
|OIDC (platform→tool)|GET|https://platform.url/lti/oidc/auth?...|platform|↑||↑|...|
||レスポンス||||||...|
|ResourceLinkRequest認証|POST|https://tool.url/Lti/...|tool||||...|
||レスポンス|||〇|〇|〇|...|
|︙|︙|︙|︙|︙|︙|︙|︙|

実際にはこの後もOpenID Connectによる認証処理が続きます。  
表ではOpenID Connect認証の触りしか記述しませんでしたが、ツールが起動された後の部分についても同様に表にまとめ、ツールが独自に出力しているパラメータもまとめていきます。

### 表にまとめたパラメータを変数にしてスクリプトを作成する

各HTTPリクエストで扱われるパラメータをまとめた表をもとに、負荷テストスクリプトを書きます。
筆者は負荷生成ツールに[k6](https://k6.io/) (https://k6.io/) というサービスを利用しており、負荷テストスクリプトはJavaScriptで書いています。  
テストシナリオに相当する動作を一度実際にブラウザ上で実行して、デベロッパーツールでHARファイルを取得して、k6上で負荷テストスクリプトを自動生成します。
自動生成したスクリプトのままでは、負荷生成ツールがスクリプトを実行するごとにパラメータを変えることができませんので、通信ごとにパラメータが可変になるように修正していきます。  

参考までに、表中の「ツール起動」から「OIDC (tool→platform)」に当たる部分のサンプルコードを示します。

```javascript
// ツール起動
message = 'https://platform.url/lti-launch/...';
response = http.get(
    message, 
    {
        headers: {
            Host: 'platform.url',
            ...(中略)...
        },
    }
  )

// レスポンスで受け取ったパラメータを変数に入れ込む
let doc = parseHTML(response.body);
login_hint = doc.find('input').get(1).value();
target_link_uri = encodeURIComponent(doc.find('input').get(2).value());
client_id = doc.find('input').get(3).value();

// OIDC (tool→platform)
response = http.get(
    // 1つ前のHTTPレスポンスで受け取ったパラメータをURLに入れ込む
    'https://tool.url/Lti/...?login_hint=' + login_hint + '&target_link_uri=' + target_link_uri + '&client_id=' + client_id + ...,
    {
      headers: {
        Host: 'tool.url',
        ...(中略)...
      },
    }
  )
```

ちなみに、POSTの場合は以下のようになります。
```javascript
varObj.response = http.post(
    'https://tool.url/Lti/...',
    {
        id_token: id_token,
        state: state,
    },
    {
        headers: {
                Host: 'tool.url',
                ...(中略)...
            },
    }
)
```

このようにして、「HTTPリクエスト → レスポンスで受け取ったパラメータを変数化 → パラメータをURLに入れ込んで次のHTTPリクエスト」といった流れでスクリプト化します。

## 負荷テストの実施
負荷テストの実施にあたっては、事前に負荷をかける日を決め、相手方と調整の上テスト実施しました。
相手方がボトルネックにならないようにしないといけないので、かける負荷も事前にある程度計画し、「この日は最大これくらいの負荷をかけます。そちらの事前スケールアップよろしくお願いします」と調整します。

PlatformとToolから構成されるLTIの負荷テストは、計測 → 負荷生成ツール側やシステム内データなどの方に不備があれば修正 → 相手方と再調整 → 計測 → チューニング…といった具合になるので、単一システムの性能・負荷テストに比べて、期間は長めにかかりました。
もともと期間的には余裕を持って着手していたので何とか必要最低限の性能測定を完了できた、というのが正直な所です。

# その他TIPS
そのほか細々したTIPSです。

LTI Coreだけでなく、AGSやNRPS等と組み合わせて使う場合には一連のシナリオでの確認もマストだと思います。当たり前といえば当たり前ですが、でき上がったテスト項目書を見ると意外と抜けていたりするポイントだったりします。

また、もし外部の脆弱性診断を受ける場合は、クローリングの前に事前情報として「OpenID Connectベースのシングルサインオンをします」等の情報提供をしておくと、そこをしっかり見てくれたりするのでおすすめです。

# おわりに
過去にPlatform側、Tool側の開発をした時のテスト工程の経験をまとめてみました。

当時は、テスト工程だけの助っ人メンバーもいましたし、そうでなくてもLTIの細かいプロトコルについては知らないメンバーが大多数でした。
振り返ってみると基本的なことも多いですが、基本的なことの積み重ねで「少しずつキャッチアップしてもらいつつ、細かいことは知らないままでも進められる」という進め方が何とかうまく開発できたコツだったのかなと感じています。