← コラム一覧

「10MB以上」を作ったら11MBだった。アップロード制限の境界値テストを作り直した話

BEETLEのQA支援・無料QAテストツールのバナー

自分で作ったダミーファイル生成ツールに、「以上」と「未満」のボタンがありました。10MBを入れて「以上」を押すと、できあがるのは11MBのファイルでした。

気づいたのは、容量以外のアップロード制限も試せるようにしようと、コードを開き直した日です。サイズを決める関数に、指定値に1.1を掛ける行と、0.9を掛ける行がありました。「未満」なら9MB。FAQには「10MB以上と10MB未満の2つを作って確かめられます」と書いていました。QA支援を名乗っている会社のツールで、これはさすがに恥ずかしかったです。

11MBと9MBでは、境界値テストにならない

11MBのファイルが弾かれて、9MBのファイルが通る。これを確かめても、分かるのは「だいたい上限のあたりで止めている」ことまでです。境界値テストで本当に見たいのは、上限ちょうどのファイルが通るのか弾かれるのか、のほうでした。

実装で起きやすいのは、<= と書くべきところを < と書いてしまうミスです。そうすると、10MBちょうどだけが弾かれます。9MBと11MBのファイルは、どちらの実装でも同じ結果になるので、このミスは見つかりません。1MBも離れた場所から境目を眺めていたわけです。

いまは、指定したバイト数ぴったりにファイルを作るように直しています。そのうえで、制限の値を1つ入れると「1つ少ない・ちょうど・1つ多い」の3つをまとめて作る境界値セットを足しました。

「10MB」が何バイトなのか、仕様書は教えてくれない

ぴったり作るように直そうとして、最初に手が止まったのがここです。10MBは10,000,000バイトなのか、10,485,760バイトなのか。差は485,760バイト、だいたい474KBあります。10,200,000バイトのファイルは、片方の数え方では上限を超え、もう片方では余裕で通ります。

困るのは、1つのシステムの中でも数え方が混ざることです。PHPの upload_max_filesize = 10M や nginx の client_max_body_size 10m は1024で数えます。一方で、仕様書を書いた人やエラーメッセージの「10MBまで」を読む人は、たぶん1000で考えています。画面側のJavaScriptで 10 * 1000 * 1000 と書かれていたら、画面では弾かれるのにサーバーなら通るファイルが生まれます。なのでツールには「1KBを1024バイトと数えるか、1000バイトと数えるか」の切り替えを付けました。

ちょうど10MBのファイルは、送ると10MBを超える

もうひとつ、作りながら気になったことがあります。ブラウザがファイルを送るとき、中身の前後には区切りの行や Content-Disposition のヘッダーが付きます。数百バイトですが、ファイルそのものより確実に大きくなります。

nginx の client_max_body_size は、ファイルの大きさではなく、この送られてくる本文全体で数えます。つまりアプリ側が「10MBちょうどはOK」と正しく作っていても、手前のnginxが同じ10MBで設定されていると、ちょうど10MBのファイルは413で先に弾かれることがあります。返ってくるのはアプリのエラーメッセージではなく、nginxのエラーなので、画面の文言も用意したものとは違ってきます。

この手のずれは、ちょうどのファイルを実際に投げてみるまで分かりません。上限ちょうどのファイルが大事なのは、実装の < と <= のためだけではなかった、と思っています。

アップロード制限は、容量だけではなかった

最初に「容量以外の制限も試したい」と思ったのは、アップロード欄の注意書きを思い浮かべたからです。「4000×4000pxまで」「350dpi以上」「ファイル名は100文字まで」「一度に30枚まで」。どれも境目があって、どれも容量と同じ「1つ少ない・ちょうど・1つ多い」で試せます。

画像の大きさとdpi

幅・高さ・dpiのうち、入れた値だけを1つずつずらした画像を作ります。幅4000px・高さ3000pxなら、3999×3000、4000×3000、4001×3000と、高さ側の2枚です。画像の中に大きさとdpiを書き込むので、開けばどれか分かります。

dpiは、検算していて少し驚きました。PNGはdpiを「1メートルあたりのピクセル数」で持つので、350dpiは13,780で保存されます。読み取る道具によっては、これが350.01dpiとして返ってきます。サーバー側で dpi == 350 と比べていたら、ちょうど350dpiで作ったはずの画像が弾かれます。>= 350 なら通ります。どちらに作られているかは、ちょうどの画像を投げれば分かります。

ファイル名は、文字数かバイト数か

ここも容量と同じで、数え方が割れます。「100文字まで」が、文字の数なのか、UTF-8のバイト数なのか。ひらがなは1文字3バイト、絵文字は4バイトです。JavaScriptの name.length だと、絵文字は1つで2と数えられます。半角英数・ひらがな・絵文字のどれで埋めるかと、拡張子を数に含めるかどうかを選べるようにしました。

あわせて、意地悪な名前もまとめて入れています。test.php.png のような二重拡張子、大文字の .PNG、拡張子なし、PNGの中身なのに拡張子が .jpg のファイル、中身がHTMLの .png。それから、濁点が分かれた「が」です。Macで付けたファイル名は「か」と濁点の2文字で保存されることがあり、文字数チェックや検索で、Windowsで付けた名前とずれます。

枚数・ページ数・行数・音声の長さ

同時にアップロードする枚数は、29枚・30枚・31枚のようにフォルダを分けて入れます。PDFはページごとに「Page 3 / 10」と番号を書き、CSVとExcelは見出しの行を数に含めるかどうかを選べます。音声は、1秒ごとに「ピッ」と鳴る本当に再生できるWAVを作ります。

使い方は、制限の値を入れるだけ

ダミーファイル生成ツールの「境界値セットを作る」で、容量・画像の大きさ・ファイル名と拡張子・長さと件数の4つから試したい制限を選びます。上限の値を入れると、作られるファイルの一覧が下に出るので、確かめてからボタンを押します。

できるのは1つのZIPです。ファイル名の頭に「1_1つ少ない」「2_ちょうど」「3_1つ多い」と付けているので、展開して上から順に投げていけば、そのままテストの手順になります。容量の合計が512MBを超えるときだけは、ZIPにまとめるとブラウザのメモリが足りなくなるので、1つずつ保存します。

無料で、登録も要りません。ファイルはすべてブラウザの中で作っていて、サーバーには何も送っていません。

作っていて、決めきれなかったこと

ファイル名のセットには、Windowsで使えない記号(\ / : * ? " < > |)を入れていません。<script>.png のような名前は、画面にファイル名を出すところのエスケープ確認にちょうどいいのですが、1つでも入っているとWindowsでZIPごと展開できなくなります。MacやLinuxで試す人には物足りないはずで、ここはまだ迷っています。

動画の再生時間も、今回は見送りました。このツールの作り方だと、動画は実際の時間どおりに録画するしかなく、60秒の上限を試すのに3分近く待つことになります。そのうえ、できた動画の長さは録画のタイミングで少しずれます。「ちょうど」を名乗れないものを境界値セットに入れるのは違うと思ったので、いまは音声のWAVだけです。

画像は、1辺16,384pxを超えるとブラウザでは作れません。Safariやスマホではもっと手前で失敗することがあり、そのときは画面に出るようにしています。

境界値は「値」より先に「数え方」を決める作業だった

作り直してみて思ったのは、境界値テストの準備でいちばん時間がかかるのは、ファイルを作ることではなかった、ということです。10MBは何バイトか。100文字は何を数えるのか。350dpiはどちら向きに丸められるのか。ここを決めないまま作ったファイルは、1バイト単位で正確でも、どこの境目にも立っていません。

11MBのファイルを作っていた頃の自分は、「境界の近く」を「境界」だと思い込んでいました。ツールが雑だったというより、数え方を決めずに作り始めたのが原因だったと思います。

あなたのチームの仕様書に書いてある「10MBまで」は、何バイトのことでしょうか。

無料・登録不要。制限の値を入れると、1つ少ない・ちょうど・1つ多いのファイルをまとめて作れます。

境界値セットを作ってみる

関連記事