BASHプログラミング
 Computer >> コンピューター >  >> プログラミング >> BASHプログラミング

BATSを使用したBashのテスト

Java、Ruby、Pythonなどの言語でアプリケーションを作成するソフトウェア開発者は、ソフトウェアの整合性を長期にわたって維持するのに役立つ高度なライブラリを備えています。彼らは、構造化された環境で一連の実行を通じてアプリケーションを実行するテストを作成し、ソフトウェアのすべての側面が期待どおりに機能することを確認します。

これらのテストは、継続的インテグレーション(CI)システムで自動化されるとさらに強力になります。このシステムでは、ソースリポジトリにプッシュするたびにテストが実行され、テストが失敗すると開発者にすぐに通知されます。この迅速なフィードバックにより、アプリケーションの機能の整合性に対する開発者の信頼が高まります。

Bash自動テストシステム(BATS)を使用すると、Bashスクリプトとライブラリを作成する開発者は、Java、Ruby、Python、およびその他の開発者が使用するのと同じ手法をBashコードに適用できます。

BATSのインストール

BATS GitHubページには、インストール手順が含まれています。より強力なアサーションを提供する、またはBATSで使用されるTest Anything Protocol(TAP)出力形式のオーバーライドを許可する2つのBATSヘルパーライブラリがあります。これらは標準の場所にインストールでき、すべてのスクリプトから入手できます。テストするスクリプトまたはライブラリの各セットのGitリポジトリにBATSとそのヘルパーライブラリの完全なバージョンを含める方が便利な場合があります。これは、gitサブモジュールを使用して実行できます。 システム。

次のコマンドは、BATSとそのヘルパーライブラリをテストにインストールします。 Gitリポジトリのディレクトリ。

git submodule init
git submodule add https://github.com/sstephenson/bats test/libs/bats
git submodule add https://github.com/ztombol/bats-assert test/libs/bats-assert
git submodule add https://github.com/ztombol/bats-support test/libs/bats-support
git add .
git commit -m 'installed bats'

Gitリポジトリのクローンを作成し、そのサブモジュールを同時にインストールするには、

-recurse-submodulesを使用します git cloneへのフラグ 。

各BATSテストスクリプトは、バットによって実行される必要があります 実行可能。 BATSをソースコードリポジトリのtest/ libsにインストールした場合 ディレクトリの場合、次のコマンドでテストを呼び出すことができます:

./test/libs/bats/bin/bats <path to test script>

または、各BATSテストスクリプトの先頭に以下を追加します。

#!/usr/bin/env ./test/libs/bats/bin/bats
load 'libs/bats-support/load'
load 'libs/bats-assert/load'

およびchmod+x<テストスクリプトへのパス> 。これにより、a) ./ test / libs / batsにインストールされたBATSで実行可能になります。 b)これらのヘルパーライブラリを含めます。 BATSテストスクリプトは通常、テストに保存されます ディレクトリとテスト対象のスクリプトにちなんで名付けられていますが、 .bats 拡大。たとえば、 bin / buildをテストするBATSスクリプト test / build.batsと呼ばれる必要があります 。

./ test / lib / bats / bin / bats test/*。batsのように、正規表現をBATSに渡すことで、BATSテストファイルのセット全体を実行することもできます。 。

BATSカバレッジ用のライブラリとスクリプトの整理

Bashスクリプトとライブラリは、内部の動作をBATSに効率的に公開するように編成する必要があります。一般に、呼び出されたり実行されたりするときに多くのコマンドを実行するライブラリ関数とシェルスクリプトは、効率的なBATSテストに適していません。

たとえば、build.shは、多くの人が作成する典型的なスクリプトです。それは本質的にコードの大きな山です。このコードの山をライブラリの関数に入れる人さえいるかもしれません。しかし、BATSテストで大量のコードを実行し、個別のテストケースで発生する可能性のあるすべてのタイプの障害をカバーすることは不可能です。十分なカバレッジでこのコードの山をテストする唯一の方法は、コードを多くの小さく、再利用可能で、最も重要なことに、独立してテスト可能な関数に分割することです。

ライブラリに関数を追加するのは簡単です。追加の利点は、これらの関数のいくつかがそれ自体で驚くほど便利になる可能性があることです。ライブラリ関数を多数の小さな関数に分割したら、ソースを実行できます。 BATSのライブラリは、他のコマンドと同じように関数をテストして実行します。

Bashスクリプトは、複数の関数に分割する必要もあります。これらの関数は、スクリプトの実行時にスクリプトの主要部分が呼び出す必要があります。さらに、BATSを使用してBashスクリプトをテストするのをはるかに簡単にする非常に便利なトリックがあります。スクリプトの主要部分で実行されるすべてのコードを取得し、run_main<などの関数に移動します。 / strong> 。次に、スクリプトの最後に以下を追加します。

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]
then
  run_main
fi

この余分なコードは何か特別なことをします。スクリプトとして実行された場合と、ソースを使用して環境に取り込まれた場合とでは、スクリプトの動作が異なります。 。このトリックにより、スクリプトをソーシングして個々の関数をテストすることにより、ライブラリをテストするのと同じ方法でスクリプトをテストできます。たとえば、BATSのテスト容易性を向上させるためにbuild.shをリファクタリングします。

テストの作成と実行

上記のように、BATSは、JUnit、RSpec、Jestなどの他のTAP準拠のテストスイートを使用したことがある人にはおなじみの構文と出力を備えたTAP準拠のテストフレームワークです。そのテストは、個々のテストスクリプトに編成されています。テストスクリプトは、1つ以上の説明的な @testに編成されています。 テストされるアプリケーションのユニットを説明するブロック。各@test ブロックは、テスト環境を準備し、テスト対象のコマンドを実行し、テストされたコマンドの終了と出力についてアサーションを作成する一連のコマンドを実行します。多くのアサーション関数はコウモリとともにインポートされます 、コウモリ-主張 、およびコウモリ-サポート ライブラリ。BATSテストスクリプトの開始時に環境にロードされます。典型的なBATSテストブロックは次のとおりです。

@test "requires CI_COMMIT_REF_SLUG environment variable" {
  unset CI_COMMIT_REF_SLUG
  assert_empty "${CI_COMMIT_REF_SLUG}"
  run some_command
  assert_failure
  assert_output --partial "CI_COMMIT_REF_SLUG"
}

BATSスクリプトにセットアップが含まれている場合 および/または分解 関数は、各テストブロックの実行の前後にBATSによって自動的に実行されます。これにより、環境変数やテストファイルを作成したり、1つまたはすべてのテストに必要なその他のことを実行したり、各テストの実行後にそれらを破棄したりすることができます。 Build.bats 新しくフォーマットされたbuild.shの完全なBATSテストです 脚本。 ( mock_docker このテストのコマンドについては、以下のモッキング/スタブのセクションで説明します。)

テストスクリプトが実行されると、BATSは execを使用します 各@testを実行するには 別のサブプロセスとしてブロックします。これにより、1つの @testで環境変数や関数をエクスポートすることが可能になります。 他の@testに影響を与えることなく sまたは現在のシェルセッションを汚染します。テスト実行の出力は、人間が理解でき、TAPコンシューマーがプログラムで解析または操作できる標準形式です。 CI_COMMIT_REF_SLUGの出力例を次に示します。 失敗したときのテストブロック:

 ✗ requires CI_COMMIT_REF_SLUG environment variable
   (from function `assert_output' in file test/libs/bats-assert/src/assert.bash, line 231,
    in test file test/ci_deploy.bats, line 26)
     `assert_output --partial "CI_COMMIT_REF_SLUG"' failed

   -- output does not contain substring --
   substring (1 lines):
     CI_COMMIT_REF_SLUG
   output (3 lines):
     ./bin/deploy.sh: join_string_by: command not found
     oc error
     Could not login
   --

   ** Did not delete , as test failed **

1 test, 1 failure

成功したテストの出力は次のとおりです。

✓ requires CI_COMMIT_REF_SLUG environment variable
ヘルパー

他のシェルスクリプトやライブラリと同様に、BATSテストスクリプトには、テスト間で共通のコードを共有したり、その機能を強化したりするためのヘルパーライブラリを含めることができます。 bats-assertなどのこれらのヘルパーライブラリ およびコウモリ-サポート 、BATSでテストすることもできます。

ライブラリは、BATSスクリプトと同じテストディレクトリまたは test / libsに配置できます。 テストディレクトリ内のファイルの数が扱いにくくなった場合は、ディレクトリ。 BATSは負荷を提供します テスト対象のスクリプトに関連するBashファイルへのパスを取得する関数(例: test 、この場合)およびそのファイルのソース。ファイルはプレフィックス.bashで終わる必要があります 、ただし、ロードに渡されたファイルへのパス 関数にプレフィックスを含めることはできません。 build.bats bats-assertをロードします およびコウモリ-サポート ライブラリ、小さな helpers.bash ライブラリ、および docker_mock.bash ライブラリ(以下で説明)。テストスクリプトの先頭のインタープリターマジックラインの下に次のコードが配置されています。

load 'libs/bats-support/load'
load 'libs/bats-assert/load'
load 'helpers'
load 'docker_mock'
テスト入力のスタブと外部呼び出しのモック

Bashスクリプトとライブラリの大部分は、実行時に関数や実行可能ファイルを実行します。多くの場合、終了ステータスまたは出力( stdout )に基づいて特定の方法で動作するようにプログラムされています。 、 stderr )これらの関数または実行可能ファイル。これらのスクリプトを適切にテストするには、特定のテスト中に特定の方法で動作するように設計されたこれらのコマンドの偽のバージョンを作成する必要があります。これは「スタブ」と呼ばれるプロセスです。また、テスト対象のプログラムをスパイして、特定のコマンドを呼び出すことを確認したり、特定の引数を使用して特定のコマンドを呼び出すことも必要になる場合があります。これは「モッキング」と呼ばれるプロセスです。詳細については、RubyRSpecでのモックとスタブに関するこのすばらしい議論をチェックしてください。これはすべてのテストシステムに適用されます。

Bashシェルは、BATSテストスクリプトでモックとスタブを実行するために使用できるトリックを提供します。すべてBashのエクスポートを使用する必要があります -fを使用したコマンド 元の関数または実行可能ファイルをオーバーライドする関数をエクスポートするためのフラグ。これは、テストされたプログラムを実行する前に実行する必要があります。これは、をオーバーライドする簡単な例です。 実行可能ファイル:

function cat() { echo "THIS WOULD CAT ${*}" }
export -f cat

このメソッドは、同じ方法で関数をオーバーライドします。テストするスクリプトまたはライブラリ内の関数をテストでオーバーライドする必要がある場合は、関数をスタブまたはモックする前に、テストしたスクリプトまたはライブラリを入手することが重要です。それ以外の場合、スタブ/モックは、スクリプトのソース時に実際の関数に置き換えられます。また、テストするコマンドを実行する前に、必ずスタブ/モックを作成してください。これがbuild.batsの例です それはレイズを嘲笑します build.shで説明されている関数 ログイン機能によって特定のエラーメッセージが表示されるようにするには:

@test ".login raises on oc error" {
  source ${profile_script}
  function raise() { echo "${1} raised"; }
  export -f raise
  run login
  assert_failure
  assert_output -p "Could not login raised"
}

export なので、通常、テスト後にスタブ/モック関数の設定を解除する必要はありません。 exec中の現在のサブプロセスにのみ影響します 現在の@test ブロック。ただし、コマンドをモック/スタブすることは可能です(例: cat sed 、など)BATSが主張する *関数は内部で使用します。これらのモック/スタブ関数は未設定である必要があります これらのassertコマンドが実行される前に、または正しく機能しません。これがbuild.batsの例です そのモックはsed build_deployableを実行します 機能し、 sedの設定を解除します アサーションを実行する前:

@test ".build_deployable prints information, runs docker build on a modified Dockerfile.production and publish_image when its not a dry_run" {
  local expected_dockerfile='Dockerfile.production'
  local application='application'
  local environment='environment'
  local expected_original_base_image="${application}"
  local expected_candidate_image="${application}-candidate:${environment}"
  local expected_deployable_image="${application}:${environment}"
  source ${profile_script}
  mock_docker build --build-arg OAUTH_CLIENT_ID --build-arg OAUTH_REDIRECT --build-arg DDS_API_BASE_URL -t "${expected_deployable_image}" -
  function publish_image() { echo "publish_image ${*}"; }
  export -f publish_image
  function sed() {
    echo "sed ${*}" >&2;
    echo "FROM application-candidate:environment";
  }
  export -f sed
  run build_deployable "${application}" "${environment}"
  assert_success
  unset sed
  assert_output --regexp "sed.*${expected_dockerfile}"
  assert_output -p "Building ${expected_original_base_image} deployable ${expected_deployable_image} FROM ${expected_candidate_image}"
  assert_output -p "FROM ${expected_candidate_image} piped"
  assert_output -p "build --build-arg OAUTH_CLIENT_ID --build-arg OAUTH_REDIRECT --build-arg DDS_API_BASE_URL -t ${expected_deployable_image} -"
  assert_output -p "publish_image ${expected_deployable_image}"
}

時々同じコマンド、例えばfooは、テストされている同じ関数内で、異なる引数を使用して複数回呼び出されます。これらの状況では、一連の関数を作成する必要があります。

  • mock_foo:期待される引数を入力として受け取り、それらをTMPファイルに保持します
  • foo:コマンドのモックバージョン。予想される引数の永続化されたリストを使用して各呼び出しを処理します。これは、export-fを使用してエクスポートする必要があります。
  • cleanup_foo:ティアダウン機能で使用するためにTMPファイルを削除します。これは、削除する前に@testブロックが成功したことを確認するためにテストできます。

この機能はさまざまなテストで再利用されることが多いため、他のライブラリと同じようにロードできるヘルパーライブラリを作成することは理にかなっています。

良い例はdocker_mock.bashです 。 build.batsに読み込まれます Docker実行可能ファイルを呼び出す関数をテストするテストブロックで使用されます。 docker_mockを使用した一般的なテストブロック 次のようになります:

@test ".publish_image fails if docker push fails" {
  setup_publish
  local expected_image="image"
  local expected_publishable_image="${CI_REGISTRY_IMAGE}/${expected_image}"
  source ${profile_script}
  mock_docker tag "${expected_image}" "${expected_publishable_image}"
  mock_docker push "${expected_publishable_image}" and_fail
  run publish_image "${expected_image}"
  assert_failure
  assert_output -p "tagging ${expected_image} as ${expected_publishable_image}"
  assert_output -p "tag ${expected_image} ${expected_publishable_image}"
  assert_output -p "pushing image to gitlab registry"
  assert_output -p "push ${expected_publishable_image}"
}

このテストは、Dockerが異なる引数で2回呼び出されるという期待を設定します。 Dockerへの2回目の呼び出しが失敗すると、testedコマンドが実行され、終了ステータスとDockerへの予想される呼び出しがテストされます。

mock_docker.bashによって導入されたBATSの1つの側面 $ {BATS_TMPDIR}です 環境変数。BATSが最初に設定して、テストとヘルパーが標準の場所でTMPファイルを作成および破棄できるようにします。 mock_docker.bash ライブラリは、テストが失敗した場合に永続化されたmocksファイルを削除しませんが、ファイルが配置されている場所を印刷して、表示および削除できるようにします。このディレクトリから古いモックファイルを定期的に削除する必要がある場合があります。

モック/スタブに関する注意事項: build.bats テストは、次のようなテストの規定に意識的に違反しています。所有していないものをあざけるな!このディクタムでは、 docker のように、テストの開発者が作成しなかったコマンドを呼び出す必要があります。 、 sed などは、それらを使用するスクリプトのテストでモックする必要がある独自のライブラリにラップする必要があります。次に、外部コマンドをモックせずにラッパーライブラリをテストする必要があります。

これは良いアドバイスであり、無視するとコストがかかります。 Docker CLI APIが変更された場合、テストスクリプトはこの変更を検出しないため、テストされた build.shまで明示されない誤検知が発生します。 スクリプトは、新しいバージョンのDockerを使用した本番環境で実行されます。テスト開発者は、この標準をどの程度厳守するかを決定する必要がありますが、決定に伴うトレードオフを理解する必要があります。

結論

ソフトウェア開発プロジェクトにテスト体制を導入すると、a)コードとテストの開発と保守に必要な時間と組織の増加、およびb)開発者がアプリケーションの存続期間にわたってアプリケーションの整合性を信頼できるようになるというトレードオフが生じます。テスト体制は、すべてのスクリプトとライブラリに適しているとは限りません。

一般に、次の1つ以上を満たすスクリプトとライブラリは、BATSでテストする必要があります。

  • ソース管理に保存する価値があります
  • これらは重要なプロセスで使用され、長期間一貫して実行されることに依存しています
  • 機能を追加/削除/変更するには、定期的に変更する必要があります
  • 他の人が使用しています

1つ以上のBashスクリプトまたはライブラリにテスト規律を適用することが決定されると、BATSは他のソフトウェア開発環境で利用できる包括的なテスト機能を提供します。

謝辞:BATSテストを紹介してくれたDarrinMannに感謝します。


  1. このBashスクリプトを使用して画像処理を自動化する

    作家は言葉だけでなく、画像を扱う必要があります。テクニカルライティングでは、テクノロジーとプロセスを伝えるために多くのスクリーンショットを提示します。公開プラットフォームが異なれば、画像の形式やファイルサイズなど、画像に関するさまざまな要件があります。 ITコンサルタントおよびシステムエンジニアとして、私はクライアントの成果物として多くの技術文書を作成しました。通常、必要な形式はMicrosoft Word(.doc)です。コンテンツが追加されると、どのドキュメントも急速に成長する可能性があります。初期のスクリーンショットはビットマップ(.bmp)であることが多く、ファイルサイズが非常に大き

  2. このパズルの本でBashを学ぶ

    コンピューターは私の趣味であり、職業でもあります。私のアパートには約10個が散らばっていて、すべてLinux(Macを含む)を実行しています。コンピューターとコンピュータースキルのアップグレードを楽しんでいるので、バッシュアウトに出くわしたとき Sylvain Lerouxによって、私はそれを買うチャンスに飛びつきました。私はDebianLinuxでコマンドラインをよく使用しますが、Bashの知識を広げる絶好の機会のように思えました。著者が序文で私のお気に入りの2つのディストリビューションの1つであるDebianLinuxを使用していると説明したとき、私は微笑んだ。 Bashを使用するとタス