>& STDOUT

主にソフトウェアに関する日々の標準出力+標準エラー出力

「lsコマンド」をテストする

VeriServeのカレンダー | Advent Calendar 2022 - Qiita の記事です。

みんな大好き ls (list segments) に対してテストを考えてみました。例えばこういうアプローチがあるよね、ということで、テスト開発プロセスを一定カバーする形になっています。

概要はこちら:
https://github.com/snsk/ls_testing
テスト開発プロセスの概要はこちら:
https://github.com/snsk/ls_testing/wiki/test_development

本稿は上記*1の解説記事です。

なお、すべてのテストは自動化されており、Github Actions上で動作します。ファジングなどとても時間がかかるテストは並列で実行されるように設定しています。


テストアーキテクチャ設計

テスト要求は「任意のディストリビューション、バージョンのlsコマンドがGNUドキュメントに記載の仕様を満たしているか」と暗黙に置いています。

今回のテスト開発では、要求に従った機能テストと、日常的に利用されるコマンドであるがゆえの頑健性のふたつを柱として掲げています。これらの特性に基づいて、どんなテストタイプをどんな優先度で、どれぐらいのボリュームで行うのかを示す図を考えてみました(今回は、柱として挙がりそうな Security/Performance/Usability については対象外としています)。テストアーキテクチャをどのように表現すると良いのか、については議論がたくさんあると思いますが、個人的には「どんなテストタイプを、どんな優先度で、どれぐらいやる?」が分かると嬉しいなと考えます。

テスト設計から実装

機能仕様たち

このアーキテクチャ図では末端の要素がテストタイプになっており、最優先の(左上に位置する)ものは 機能テスト::仕様準拠::基本的なふるまい(Functional_SpecConfirmance_BasicBehavior) となっています。仕様準拠のコンテナは自然言語で書かれた仕様をCFDで分解して解釈し、デシジョンテーブルに変換(これはGIHOZがやってくれる)してからテストケースまで落とす、という形にしています。High Level Test Cases をCFD->DTという流れですね。Low Level Test Cases は behave というPythonのBDDフレームワークを使って処理系で実行可能な形で実装しています。CLIアプリケーションは自動化しやすくて良きです。


引数の組み合わせテスト

linuxコマンドの多くは引数で指定されるオプションでそのふるまいを変化させます。lsコマンドにも無数のオプションがあり、実は今回一番lsコマンドを選んで後悔した部分です。GNU仕様には、複数の引数を組み合わせたときにどう振る舞う?という仕様が一部しかありません(仕様に書かれているものは仕様準拠でカバーしています)。つまり、大部分は期待動作がない形になります。そんなときには、ということで代表的なテストオラク錬金術である「現在の振る舞いを一旦の期待動作とする」形でアプローチしました。
具体的には

arg_combi_list = list(itertools.combinations(args ,2))

としてふたつの水準同士の組み合わせを作って特定のディストリビューションに搭載のlsコマンドで出力を確認。これを期待動作リストとして、コマンド実行と出力比較をイテレートするという実装になっています。このへんはテスト設計の戦略部分を含み、検討の余地があるなあと思っています。

頑健性たち

僕が大好きファジング無双のコーナーです。lsコマンドに対する「入力」は、「ディレクトリやファイルの構造」になるかなと考え、さまざまなディレクトリ、ファイル構造を生成してlsコマンドに食べさせて、クラッシュやフリーズが発生しないこと、という頑健性の基本的な要素を確認するテストを考えて実装しています。また、引数の組み合わせによる動作不良や、OSの権限周り、存在しないディレクトリの指定などのノイズ、通常想定されない長い長いパスなどのアクティブノイズなどをノイズ系としてテストタイプに配置しています。

「任意のディレクトリ、ファイル構造」をどうやって生成しようかと悩んでいたところ、randomfiletree という今回の目的に対して神がかったライブラリを発見できたので、このファザーの実装が大変楽になりました。ありがとうございます。今回は、この神ライブラリで生成したディレクトリ構造を、なるべく表示量の多い引数構成のlsコマンドに食べさせる、という単純な構成でしたので、ファザーは自前で実装しています。

シノプシス社によるファジングの成熟度レベル*2としてはレベル1とレベル2の間ぐらいです。どちらも一定時間、あるいは10万~100万回の実行が求められますが、Github Actionsの時間を使い切ってしまうので、1000件~10000件に抑えています。機構的には実行可能です。

ノイズの洗い出しは、ラルフチャートを使ってこれまでにフォローされている入力種別を一旦整理したうえで、ノイズ、アクティブノイズにはどんな入力が利用できそうかをまとめています。これは、機能テストでも利用したBDDフレームワークを使って実装しています。


まとめ

「lsコマンド」に対してテスト開発プロセスの基本設計から実装までを一通り流してみました。こういうテストタイプにはこういうテスト技法が利用できるというのを一概に決めにくいのがテスト開発プロセスの悩みでもあり、創造的な部分でもあるのかなと思います。一方、テストタイプはあくまでテストアーキテクチャ設計(上流)のいち要素でしかないので、そこからモデルを起こしてカバレッジクライテリアを考えていかないと技法(実装技術)の選択は難しいよね、というプログラミングであっても同様に発生する課題でもあるかなと思います。

linuxコマンドはソースコードもドキュメントも誰でも触れるので、テスト開発プロセスを個人で気軽に回してみるためのSUTとして優れていると思います。ただ、lsコマンドはちょっと複雑すぎたかも知れません。もしこれを読んで自分もやってみようかな、と思われたかたは、wcコマンドあたりが仕様の大きさや、機能の明確さの面からもおすすめです。

*1:Github側が英語になっているのはテストベースが英語だったから、です。日本語で書けば良かったです

*2:https://www.synopsys.com/content/dam/synopsys/sig-assets/whitepapers/fuzz-testing-maturity-model.pdf