対象: root/programs/CS(C# 側)
配置: root/programs
本書は、リリース時に行っていた「サンプルを幾つか見繕って手動で疎通を行う」を、
合否が出る形に機械化するための手順と判定基準を記述する(#513 段階 3)。
cd root\programs
# 全対象の疎通を確認する
.\3_SmokeTest.ps1
# 一部だけ(動作確認用)
.\3_SmokeTest.ps1 -Only "net48"
.\3_SmokeTest.ps1 -Only "Rerunnable" -SkipBuild終了コードは 0 = 全対象 OK、1 = NG あり。
| オプション | 内容 |
|---|---|
-Only <文字列> |
対象名の部分一致で絞る |
-SkipBuild |
ビルドを省略し、既存のバイナリで疎通のみ行う |
-OutputDir <パス> |
ログの保存先(既定 %TEMP%\OpenTouryoSmokeTest) |
| 見るもの | 期待値 | |
|---|---|---|
1_BuildAll.ps1(段階 2) |
ビルドが通るか | エラー 0 件 |
2_RunAllTests.ps1(段階 1) |
出力が前回と同じか | HEAD の Result*.txt |
3_SmokeTest.ps1(段階 3) |
起動して想定どおり動くか | 定義側に書いた判定条件 |
段階 1 は回帰テスト、段階 3 は疎通テストで、目的が違う。
疎通テストは期待結果ファイルを持たず、判定条件を 3_SmokeTest.ps1 の対象定義に書く。
実行順は 1_BuildAll.ps1 → 2_RunAllTests.ps1 → 3_SmokeTest.ps1。
リリース時の作業全体は RELEASE.md を参照。
| 対象 | 判定 |
|---|---|
SimpleBatch_sample (net48 / net10.0) |
〇件のデータがあります が出力される |
RerunnableBatch_sample (net48 / net10.0) |
Orders2 の件数が Orders と一致する |
RerunnableBatch_sample2 (net48 / net10.0) |
同上 |
RerunnableBatch_sample3 (net48 / net10.0) |
同上 |
RerunnableBatch 系は Orders(830 件) を読み Orders2 へ INSERT する。3 本の違いは
INSERT の方法(1 件ずつ/SQL 連結/INSERT 文組み立て)で、いずれも結果は同じになる。
実行前に Orders2 を空にする必要がある。 OrderID が主キーのため、
残っていると重複で落ちる。スクリプトが DELETE FROM [Orders2] を行ってから実行する。
実行後は 830 件=初期状態に戻るため、後始末は不要。
| 対象 | 判定 |
|---|---|
Simple_CLI (net10.0) |
cmd1 --an-int 123 が Sub command cmd1: 123 を出力する |
net48 版は System.CommandLine / Sharprompt の .NET Framework サポート終了により
ドロップされている(5_Build_CLI_sample.bat 参照)。
interactive サブコマンドは対話プロンプトを使うため対象外。
#508 で追加された CUI。net48 / net10.0 それぞれ 3 件。
ツール自体の使い方は
CS/Frameworks/Tools/DaoGen_Tool/README.mdが一次情報。 実行ファイルの場所、ヘルプの出し方、エージェントが踏みやすい罠を記載している。
| 対象 | 判定 |
|---|---|
/HELP |
ヘルプの見出しが出力される |
/CUI /MODE DAODEFGEN |
DB のスキーマから定義 CSV が生成され、対象テーブルが含まれる |
/CUI /MODE DAOSQLGEN |
定義 CSV から Dao(.cs)・動的 SQL(.xml)・静的 SQL(.sql) が生成される |
2 モードは連続して実行する。 DAODEFGEN が出力した定義 CSV を
DAOSQLGEN の入力に使うため、ツール単体ではなく
「DB → 定義 → 生成」の一連の流れを確認できる。
対象テーブルは Shippers,Orders の 2 つに絞っている。
全テーブルを回しても時間がかかるだけで、疎通としては同じことを見ているため。
生成先は %TEMP%\OpenTouryoSmokeTest\daogen_*。
実行前に前回の生成物を消す。残っていると「生成された」の判定が甘くなる。
GUI 側の確認は手作業に残る。 引数なしで起動すると
Application.Run(new Form1())になる。
コマンドライン解析(StringVariableOperator.GetCommandArgs)は
\ をエスケープ文字として扱うため、パスの区切りは / にする。
OK : /OUTPUT "C:/temp/out"
OK : /OUTPUT "C:\\temp\\out"
NG : /OUTPUT "C:\temp\out" ← \ が消える
ツール自身の /HELP にも記載されている。
DaoGen_Tool は WinExe で、CUI 時は AttachConsole(-1) でコンソールに接続する。
このためリダイレクトの方法によって出力が取れない。
| 方法 | 結果 |
|---|---|
PowerShell の & $exe ... *>&1 | Out-File |
取れる(68 行) |
cmd /c "... > file" |
取れない(0 行) |
3_SmokeTest.ps1 は前者で実行している。
| 対象 | ホスト | 認証の実装 | 判定 |
|---|---|---|---|
MVC_Sample (net48) |
IIS Express | FormsAuthentication | ログイン後 /Crud1/Index が 200 |
WebForms_Sample (net48) |
IIS Express | FormsAuthentication | ログイン後 menu.aspx が 200 |
MVC_Sample (net10.0) |
Kestrel | Cookie 認証 | ログイン後 /Crud1/Index が 200 |
3 つとも確認の深さを揃えている。 入口ページが 200 を返すだけでは ホスティングと構成しか確認できないため、いずれもログインを通し、 認証が要る画面に到達できることまで見る。
- ログイン画面を GET … 画面が出ること。ページが発行した状態を取り出す
(MVC は
__RequestVerificationToken、WebForms は__VIEWSTATE等) - ログインを POST … 偽造防止の検証を通ること
- 認証が要る画面を GET … 未認証なら 302 になるので、200 なら認証が通っている
これでホスティング・構成・ルーティング・認証・セッションまでを一度に確認できる。 いずれのサンプルも「ユーザー名が空でなければ認証する」実装のため、資格情報は不要。
未認証時に 302 が返ることは実測で確認済み。 200 が偶然でないことの裏付けになる。
MVC_Sample (net48) 未認証で /Crud1/Index → 302 WebForms_Sample 未認証で /Aspx/start/menu.aspx → 302
WebForms_Sample は Web.config の <deny users="?" /> で全画面が要認証になっており、
ログイン後は <forms defaultUrl="Aspx/Start/menu.aspx"> の画面へ遷移する。
ポストバックには画面が発行した __VIEWSTATE / __VIEWSTATEGENERATOR / __EVENTVALIDATION を
そのまま返す必要があり、コントロール名はマスタ ページ配下のため
ctl00$ContentPlaceHolder_A$ が付く。
| 対象 | 理由 |
|---|---|
2CS_sample 系(11 本) |
WinForms / WPF。UI Automation が必要で、画面変更に弱く維持費が高い |
WSClient_sample 系(7 本) |
同上 |
Web サービス(ASPNETWebService) |
別リポジトリへ移設済み。本リポジトリにホストが無い |
CS/Samples/WS_sample/WSServer_sample はクラス ライブラリ(B層・D層)で、
これを載せる Web サービスは
OpenTouryoProject/ResourceServerTemplates
へ移設されている。このため本リポジトリだけでは HTTP 疎通ができない。
WinForms / WPF 系は、リリース チェックリスト(段階 4)の手作業項目として残す。
- SQL Server の Northwind に接続できること
- 接続文字列は
CS\Samples\Bat_sample\SimpleBatch_sample\App.configのConnectionString_SQLを読む。ここで別途ハードコードすると追随できなくなるため
- 接続文字列は
Orders2テーブルが存在すること- Northwind 標準ではない。無い場合は
CS\Samples\Bat_sample\RerunnableBatch_sample\CREATE ORDERS2.sqlを実行する
- Northwind 標準ではない。無い場合は
- IIS Express がインストールされていること(net48 の Web アプリ)
- ASP.NET 状態サービスが開始されていること(net48 の Web アプリ)
MVC_Sample / WebForms_Sample の Web.config は StateServer を使う。
<sessionState cookieName="mvc_session" timeout="20" cookieless="false"
mode="StateServer" stateConnectionString="tcpip=127.0.0.1:42424"/>サービスが止まっていると、ログインの POST が 500 になる。
System.Web.HttpException: セッション状態要求をセッション状態サーバーに対して作成できませんでした。
3_SmokeTest.ps1 は実行前に確認し、止まっていれば「前提未達」として対処方法を示す。
Start-Service aspnet_state # 管理者権限が必要スクリプトはサービスを自動起動しない。 システムの状態を変える操作であり、 リリース判定のために黙って環境を書き換えるべきではないため。
対象ごとに次のいずれかで判定する。
| 種別 | 判定 |
|---|---|
| 出力の照合 | 標準出力が Expect の正規表現に一致するか |
| 追加検証 | Verify のスクリプト ブロックが $true を返すか(DB の件数など) |
| Web の疎通 | Flow のスクリプト ブロックが Ok = $true を返すか |
いずれの場合も、出力に未処理例外が含まれていれば NG。ただし次は除外する。
Console.ReadKey()由来の例外 … サンプルは末尾にConsole.ReadKey()を持つものがあり、出力をリダイレクトすると 必ず例外で終わる。テスト内容とは無関係
リポジトリ既定のビルド バッチを呼ぶ。 理由は 2_RunAllTests.ps1 と同じで、
csproj を直接 MSBuild すると nuget.exe restore が行うネイティブ DLL の配置が漏れ、
ビルドは成功するのに実行時に落ちる(TESTING.md の「ビルドをバッチに委ねている理由」)。
0_ExecAllBat.bat は途中で 1_DeleteDir.bat を繰り返し実行し、
配下の bin / obj / packages 等を再帰的に削除する。
… net48 サンプルをビルド …
Clean (core サンプル) ← ここで net48 サンプルの bin も消える
… Core サンプルをビルド …
このため 1_BuildAll.ps1 の完走後に残るのは最後にビルドされた Core サンプルだけで、
net48 サンプルのバイナリは残らない。3_SmokeTest.ps1 が自分でビルドするのはこのため。
着手時、net48 のバッチ サンプル 4 本がすべて起動直後に落ちていた。
System.DllNotFoundException
場所 Microsoft.Data.SqlClient.SNINativeManagedWrapperX64.SNIInitialize(IntPtr)
原因は、5_Build_Bat_sample.bat と 6_Build_WSSrv_sample.bat に
nuget.exe restore が無かったこと。Microsoft.Data.SqlClient は SNI を
ネイティブ DLL で持つため、restore を経ないと bin に配置されない。
ビルドは成功するため、段階 2(ビルドの合否判定)では検出できない。 疎通テストで初めて表面化する種類の不具合である。
他の 11 バッチと同じ形式で nuget.exe restore ... %NUGET_MSBUILD% を追加した。
対象 結果 内容
---- ---- ----
SimpleBatch_sample (net48) OK 3件のデータがあります
RerunnableBatch_sample (net48) OK
RerunnableBatch_sample2 (net48) OK
RerunnableBatch_sample3 (net48) OK
SimpleBatch_sample (net10.0) OK 3件のデータがあります
RerunnableBatch_sample (net10.0) OK
RerunnableBatch_sample2 (net10.0) OK
RerunnableBatch_sample3 (net10.0) OK
Simple_CLI (net10.0) OK Sub command cmd1: 123
DaoGen_Tool /HELP (net48) OK DaoGen_Tool(D層自動生成ツール/墨壺)
DaoGen_Tool DAODEFGEN (net48) OK 生成が完了しました。
DaoGen_Tool DAOSQLGEN (net48) OK 生成が完了しました。
DaoGen_Tool /HELP (net10.0) OK DaoGen_Tool(D層自動生成ツール/墨壺)
DaoGen_Tool DAODEFGEN (net10.0) OK 生成が完了しました。
DaoGen_Tool DAOSQLGEN (net10.0) OK 生成が完了しました。
MVC_Sample (net48) OK ログイン後 /Crud1/Index = 200
WebForms_Sample (net48) OK ログイン後 menu.aspx = 200
MVC_Sample (net10.0) OK ログイン後 /Crud1/Index = 200
全対象 OK
全 18 件(ビルド 7 バッチ + 疎通 18 件)で 約 2.4 分。
Invoke-WebRequest は -MaximumRedirection 0 で 3xx を受け取ると、
-SkipHttpErrorCheck を付けていても
「The maximum redirection count has been exceeded」で終了エラーになる。
ログイン成功時は FormsAuthentication が 302 を返すため、これに該当する。
3_SmokeTest.ps1 は Invoke-Http で捕まえ、3xx を正常な結果として扱う。
素の Invoke-WebRequest を使うと、判定は通るのにエラーが表示される状態になる。
3_SmokeTest.ps1 の $targets に定義を足す。
| 項目 | 内容 |
|---|---|
Name |
表示名 |
Bat |
ビルドに使うバッチ(root\programs\CS 配下) |
Exe |
実行ファイル。.dll なら dotnet で実行する |
Args |
コマンドライン引数 |
Pre |
実行前の準備(スクリプト ブロック) |
Expect |
標準出力に対する正規表現 |
Verify |
追加の検証(スクリプト ブロック) |
Kind |
Web を指定すると Web アプリ扱い |
WebHost |
IISExpress または Kestrel |
Site / Port / Flow / Need |
Web アプリ用 |
判定条件は「動いていれば必ず満たす」ものにする。 実行のたびに変わる値(件数以外の可変値、日時など)を条件に入れると、 環境差で落ちるだけの脆いテストになる。
/DAP のように / で始まる引数は -- で区切って渡す必要がある。
一方 System.CommandLine を使う CLI では -- 以降が未解析トークン扱いになり、
サブコマンドが認識されなくなる。3_SmokeTest.ps1 は引数を見て自動で切り替える。
両方で動くこと。 開発時に pwsh(7)だけで確認すると、
利用者が powershell.exe(5.1)で実行したときに落ちる。
規約の実体は
CS/Frameworks/ANALYSIS.mdの 8.4 節にある。 落とし穴の一覧と対処方法は、そちらを参照すること。
本スクリプト群は、次の 4 点を実際に踏んだうえで対処してある。 同種のスクリプトを追加・変更するときの実例として挙げる。
| 事象 | 踏んだ箇所 |
|---|---|
| BOM 無しで構文エラー・文字化け | 5 本すべて(0_RunAll.ps1 ほか) |
Get-Content の既定エンコード差で、同じファイルなのに差分が出る |
CompareResult.ps1 |
-SkipHttpErrorCheck が 5.1 に無く、HTTP が常に失敗 |
3_SmokeTest.ps1 の Invoke-Http |
chcp による画面クリアと、ログの文字化け |
3 本すべて(冒頭でコード ページを切り替え) |
TESTING.md と同じく、NoDefaultCurrentDirectoryInExePath を解除している。
解除しないと、バッチ内でパス区切りを含まない名前で起動している exe が動かない。