Software Design 202402
テストの設計
分析
テストは 4 つの段階からなる。
- 分析 / 何をテストするか決める
- 設計 / それをどのようにテストするか決める
- 実装 / テスト実行に必要なものを準備する
- 実行 / テストスイートを実行する
テストの分析とは妥当性確認のことで、仮に要件として明文化されていなかったとしても、ステークホルダーのニーズ を満たしているのかどうかを考えることを指す。例えば「パスワードの文字列長が妥当であるか」など、高い抽象度で作成する。実装が始まっていない早い段階から取り組める。
一方で、プロジェクトが進行し、具体的な要件や機能が明確になると、検証のフェーズに移行する。この段階では、既に定義された要件が満たされているかどうかを確認する。テスト設計のフェーズで行われる作業である。「パスワードが 4 文字以上であるかどうか」といった抽象度の低いテストとして作成する。
早い段階でテストの考えを注入することをシフトレフトテストという。より早い段階で誤りに気がつくことができれば、コストを指数関数的に下げることができる。
設計
テスト技法には大きく 3 つの種類がある。
- ブラックボックステスト
- ホワイトボックステスト
- 経験ベースのテスト
ブラックボックステスト
同値分割法とは、結果的に同じ振る舞いをするはずの入力値をグループ化し、そのグループの中から 1 つずつ任意の値を選んでテストする手法である。このグループのことを同値パーテーションと呼ぶ。有効な値だけ でなく無効な値も含めてテストすることが重要。
境界値分析とは、同値分割法の拡張版であり、境界値周辺をより詳細にテストする方法である。境界値ごとに 2 つもしくは 3 つのテストケースを作成するため、同値分割法よりもテストケースは増える。
デシジョンテーブルテストとは、複数の条件とその結果を表にまとめ、その表からテストケースを作成する方法である。テーブルは条件記述分、動作記述分、条件指定部、動作指定部の 4 つの部分から構成される。条件記述部にはY/N/-(動作に影響しない)
などを書く。各列がテストケースとなる。
1 | 2 | 3 | 4 | |
---|---|---|---|---|
🟢 条件 | ||||
メールアドレスが正しい | Y | N | - | N |
パスワードが正しい | Y | - | N | N |
🟢 動作 | ||||
ホーム画面に遷移する | X | |||
エラーを表示する | X | X | ||
怒る | X |
状態遷移テストとは、システムの状態遷移をテストする手法である。状態遷移図を作成し、その図からテストケースを作成する。有効な遷移だけをテストするパターン、無効な遷移を含めてテストするパターン、間に N 個の状態を経由する全ての遷移をテストするパターンの 3 つのパターンがあり、要件に応じて選択する。
ペアワイズテストとは、複数の入力値の組み合わせをテストする手法 である。全ての組み合わせをテストすると膨大な数のテストケースが必要になる場合に最適。
ホワイトボックステスト
ステートメントテストとは、プログラムの各ステートメントを 1 回以上実行するテストケースを作成する手法である。ステートメントを網羅したとしても、全てのケースを網羅しているとは限らないので注意。
ブランチテストとは、プログラムの分岐を網羅するテストケースを作成する手法である。分岐を網羅するためには、真と偽の両方のケースをテストする必要がある。全てのケースをカバーできる。
経験ベースのテスト
エラー推測とは、過去の経験からエラーが発生しそうな箇所を推測し、テストケースを作成する手法である。
探索的テストとは、テストケースを事前に作成せず、実際にシステムを操作しながら知見を深め、次のテストケースを作成していく手法である。
チェックリストベースドテストとは、チェックリストに掲げた項目をカバーするようにテストケースを作成する方法のこと。チェックリストは、経験、標準または知識に基づいて作成される。
探索的テスト
探索的テストとは、学習、テスト設計、テスト実行を同時に行うことである。仕様を見るだけでは洗い出しきれない、テストすべき弱点を見つけることに向いている。テストの技法というよりは、テストのアプローチ・進め方の一つである。
対義語は記述式テストである。記述式テストは、事前にテストケースを作成し、それに従ってテストを実行すること。
探索的テストを行うには、以下の知識を持っていることが前提となる。
- 基本的なテスト技法が既に自在に使えること
- 参考になるソフトウェアの仕様を把握していること
- e.g. 同じソフトウェアの似たようなほかの部分の仕様
- e.g. デファクトとされている他社ソフトでの仕様
- ドメイン知識を持っていること
- 過去に発生したバグを知っていること
進め方
まずは情報を収集する。対象の機能が何を解決しようとしているのか、開発者として不安なところがあるか、起きてほしくないことは何か、など。
次にチャーターを作成する。これは迷子にならないための道しるべであり、探索対象、探索で使用するリソース、結果として手に入れたい情報を記載する。
大量のデータや特殊なデータなどが必要な場合は、事前にデータを準備しておく。
準備ができたらテストを実施していく。このとき、正常系、純正常系、異常系の順にテストを実施していく。怪しい点は深掘りしていく。気づいたことや疑問点は記録しておき、後でチームに共有する。
(参考)セッションベースドテストという、複数人が集まって限られた時間内に同時に探索的テストを行う手法がある。
Web API セキュリティ
Web API のセキュリティリスク
セキュリティには、機密性 / Confidentiality、完全性 / Integrity、可用性 / Availability の 3 つの観点がある。
以下、Web API に対する代表的な攻撃を列挙する。
機能レベルの認可不備
例えば、管理者権限がないユーザーが管理者機能を利用できるなど。
オブジェクトレベルの認 可不備
例えば、本来見えてはいけない他のユーザーの情報を取得できるなど。
オブジェクトプロパティレベルの認可不備
これには大きく「過剰なデータ露出」と「マスアサインメント脆弱性」がある。前者は、プロパティに本来見えてはいけない他のユーザーの情報が意図せず入り込んでいる例などである。後者は、マスアサインメント機能の不適切な利用により、ユーザー入力を未検証のまま DB に入れ込んでしまうような例が挙げられる。
制限のないリソース消費
例えば、SMS のワンタイムパスワードを大量にリクエストできるなど。
機密性の高いビジネスフローへの無制限のアクセス
例えば、人気ゲーム機の販売サイトで Bot を使って買い占められるなど。
サーバーサイドリクエストフォージェリ
サーバーに対し意図しないリクエストを送信させることで、本来アクセスできないはずのリソースにアクセスする。例えば、添付画像を URL としてリクエストに含めることができる API の場合に、サーバサイドで無邪気にその URL にアクセスしている場合に発生する。
不適切なインベントリ管理
使われていないエンドポイントや古いバージョンのエンドポイントが残っているなど。
外部 API の安全ではない利用
外部 API を無邪気に信頼することで、自身のシステムにリスクを持ち込むなど。
Web API セキュリティのポイント
シフトレフトの考え方にもとづき、上流からセキュリティについて考えていくことが大切。
JWT はセッション ID を Cookie に保持する伝統的な方法に場合に比べて、スケールアップに強いが、状態(セッション)を持たないので強制ログアウトができない。
セッション ID やトークン類を Cookie に保存するのと localStorage に保存する場合があるが、どちらもセキュリティに大差はない。
認可の基本は以下のとおり。
- 次の直前に権限を確認する
- 秘密情報の表示
- 権限の必要な機能の実行
- その他、権限を必要とする操作
- 権限の確認は以下のいずれかにより行う
- セッション変数に保存したログイン ID
- JWT から取得した ID
ユーザーごとに権限を抽象化した「ロール」を定義した上で、各ロールが持つ権限を権限表としてまとめておくとよい。
認証・認可
認証認可でも最も使われているのは OpenID Connect 1.0 と OAuth 2.0 である。
OAuth2.0 は認可(not 認証) を扱うフレームワークで、RFC6749 で定義されている。ユーザーがあるリソースへのアクセスを第三者アプリケーションに許可するメカニズムを提供する。たとえば、あるサービスがユーザーの Google カレンダーにアクセスするための認可をユーザーから得る場合などである。アクターは以下の 4 つ。
- リソースオーナー
- 保護対象のリソースへのアクセスを許可できるエンティティ
- e.g. 人間
- リソースサーバ
- 保護されたリソースをホストする
- アクセストークンを用いたリソース要求を受け入れ、応答する
- e.g. Google Calendar API
- クライアント
- リソースオーナーの代わりに保護対象のリソースに対してリソース要求を行うアプリケーション
- e.g. 会議 SaaS アプリとか?
- 認可サーバ
- リソースオーナーを認証して許可を得た後に、クライアントに対してアクセストークンを発行するサーバのこと
- アクセストークンの実装方法は定義されていない
- e.g. Google の認可サーバ
OpenID Connect 1.0 は OAuth2.0 をベースに認証の機能を加えたである。アクターは以下の 4 つ。
- エンドユーザー
- 認証の対象となるユーザー
- UserInfo エンドポイント
- エンドユーザーの情報を提供する API
- 保護対象のリソースの一つ
- OpenID プロバイダ
- OAuth2.0 認可サーバの機能に加えて、認証も行うサーバ
- リライングパーティーに ID トークンを提供する
- ID トークンの実装方法は標準化されている
- リライングパーティー
- OAuth2.0 クライアント
- OpenID プロバイダが認証した結果である ID トークンを検証してから使用する
OIDC や OAuth には拡張様式がたくさんある。例えば OIDC に即時ログアウトの機能を加えた OpenID Connect RP-Initiated Logout など。
認証・認可を統合する方法はいくつかある。
まずは自前で実装する方法がある。ただしこれは相当にハードルが高い。
次にオープンソースなソリューションを使う方法がある。たとえば Keyclaoak など。
最後に、IDaaS を使う方法がある。IDaaS とは、Identity as a Service の略で、認証・認可をクラウド上で提供するサービスのことである。たとえば Auth0 や Okta など。
ドメイン解体新書 / 基礎知識
TLD とはトップレベルドメインのこと。勝手に作ることはできず、ICANN が管理している。2,3 文字のものが多いがそれ以上のものもある。gTLD(generic TLD) と新 gTLD(2012 年運用開始) がある。企業名の TLD を作ることは可能ではあるが、維持のハードルは高い。
SLD は TLD のひとつ前の部分のこと。.co.jp
ならco
、example.com
ならexample
を指す。あいまいな概念で、単体で使うことはあまりない用語。
ccTLD (country code TLD) は国別に指定された TLD と、場合によって組織のカテゴリなどによってあらかじめ決められた SLD が組み合わさ れたドメインのこと。.co.jp
や.ac.jp
など。.go.jp
のように取得に条件が課されている場合がある。
レジストリ、レジストラ、リセラーの違いは以下のとおり。
レジストリは 1 つの TLD につき 1 社。例えば .com
のレジストリは Verisign である。価格決定権がある一方で、DNS 維持の義務もある。ICANN で規定された条件をクリアしている必要がある。
レジストラは、レジストリに対してドメインの登録や更新、廃止の手続きを行う組織。場合によってはエンドユーザーへの販売も行うが、まれ?こちらも ICANN の認定が必要。
リセラーは、レジストラの API を利用してエンドユーザーにドメインの販売を行う業者。お名前ドットコムや NameCheap など。
ドメイン名は、あらかじめ決められた TLD や ccTLD に自分の好みの文字列を追加して作られる。
FQDN はホスト名 + (あれば)サブドメイン名 + TLD または ccTLD
のこと。目的のサーバに接続するための情報をすべて含んでいるドメインのこと。
FQDN の頭にプロトコルスキーマを追加すると、URI の一種である URL になる。https://www.google.com
など。
IDN は、国際化ドメイン名のこと。日本語ドメインなど、ASCII 以外の文字を含むドメイン名のこと。Punycode というエンコードの仕組みにより、従来の DNS の枠組みの中でマルチバイト文字を使えるようにしている。
開発者体験
Four Keys の指標と会社の業績には 因果関係があることは統計データから証明されている。
- デプロイの頻度
- 変更のリードタイム (PR の最初のコミットから本番適用までの時間。変更の粒度やレビュー速度に起因する。)
- 変更障害率
- サービス復元時間
Four Keys だけが銀の弾丸というわけではなく、ほかにも最近では Availability や Relibability などの指標があったり、開発者の満足度やドキュメントの充実度から計ることもある。
NewsPicks ではデプロイ頻度のみを計るところから改善を始めた。計測しやすいし、4 つの指標は相関関係があるから、一つ計れば十分やろ、と。1 年目にデプロイエンジニアリングに取り組み、最小コストで最大効果をあげることができた。
データベースリファクタリング / UI の重力
「UI の重力」というアンチパターンがある。これは、UI の見た目をそのままテーブル設計にした結果、問題が生じることを指す。たとえば、RoR のアクティブレコードパターンは、UI と DB が 1 対 1 で結びつくため、このアンチパターンに陥りやすい。簡単/Easy だが単純/Simple ではない。
対策としては、「事実(情報の源泉)」と「情報(事実を加工したもの)」を分けることが大事。例えば、生年月日は事実で、年齢は情報である。情報ではなく、事実を適切な粒度で別テーブルに分割(正規 化)していくとよい。
ただしどの粒度で正規化するかの判断は難しかったり時間がかかる場合もあるので、少なくともリファクタしながら進められる状況を確保することが寛容。
一般的な正規化のポイントは、事実だけを保存する(情報は保存しない)、重複をなくす、不整合をなくす、null を許容しない、など。さらに、以下のにも取り組むと良い。
- イミュータブルなデータモデルにする
- エンティティをリソースとイベントに分ける
- イベントは追加のみ可能
- リソースは更新可能ではあるものの、更新日時や削除日時は原則として持たせない
- 詳細: https://scrapbox.io/kawasima/イミュータブルデータモデル
- ライフサイクルによってエンティティを分割する
- ライフサイクルが違えば、null になるカラムが出てきたり、必要なカラムが異なってきたりする
- だから、たとえば記事一覧と記事詳細でテーブルを分ける?
- 1 つのテーブルに 4 つ以上の手動インデックスを作らない
- 例えば公開済みの記事とドラフトの記事でテーブルを分ける?
Go 言語 / ドキュメンテーション
GoDoc と いうコメントを書くことができる。パッケージや、大文字から始まる、公開型、公開関数、公開定数および公開変数には、すべてコメントを書くべきとされている。
Go では同じフォルダの中にあるファイルは同じパッケージに属する。パッケージへのコメントはdoc.go
という専用のファイルを作ってそこに書くと迷わないのでよい。
Example テストという仕組みがある。これは、一定の命名規則にしたがって作成したテストコードが、自動的にドキュメントに埋め込まれるという仕組みである。
- Example - パッケージ全体のテスト
- ExampleFoo - Foo という関数のテスト
- ExampleFoo_one, ExampleFoo_two - Foo という関数のテストを複数書きたい場合
- ExampleBar_Qux - Bar 型の Qux メソッドのテスト
pkgsite
というツールを使うと、GoDoc から生成されたドキュメントをブラウザ上で閲覧することができる。
AWS Organizations
複数の AWS アカウントを一元管理するサービス。組織の統制を取るためのもの。
- 論理グループ(OU, Organizational Unit)でアカウントをまとめる
- ポリシーによる制御ができる(ユーザー権限、バックアップ、タグほか)
- 利用費の一括請求
Organizations を管理するアカウントを管理アカウントと呼ぶ(親)。Organizations 管理下になるアカウントをメンバーアカウントと呼ぶ(子)。
アカウントを分離することで、アクセス権限・セキュリテ ィ・コスト・クォータを分離することができて便利である。
アカウントを分離する単位は利用目的(システムの種類やワークロード)とステージ(本番・開発)で分けるのがオススメ。例えば以下のような構成。
- システム A 本番アカウント
- システム A 開発アカウント
- システム B 本番アカウント
- システム B 開発アカウント
- 個人用サンドボックスアカウント
OU は組織図ではなく、なるべく変わらない単位でつくるとよい。ベストプラクティスはトップレベルがワークロード、セカンドレベルが本番 or 開発、という構成になっている。しかし、必ずしも従う必要はない。本番・開発で権限に差をつけたことで事故ることもままあるので。