ShellSpec使い方メモ

# ShellSpecとは
- シェルスクリプト用の BDD テストフレームワーク
- テスト前後のフック、テストのグループ化・階層化、モック、パラメータ化テスト、カバレッジ計測など、豊富な機能を持つ
    - shUnit2, Bats-core との比較表 > [ShellSpec | BDD unit testing framework for shell scripts (bash, ksh, zsh, dash and all POSIX shells)](https://shellspec.info/comparison.html)
- 日本の方(@ko1nksm)が作られており、Qiitaにも紹介記事があるので読んでおくとよさげ
    - [シェルスクリプトのテスト、何を使ってる？shUnit2？Bats？ ShellSpec を使ってみませんか？ - Qiita](https://qiita.com/ko1nksm/items/556336797d7e49117842)
    - [ShellSpec - シェルスクリプト用のフル機能のBDDユニットテストフレームワーク - Qiita](https://qiita.com/ko1nksm/items/2f01ff4f50e957ebf1de)

# 環境
- 以下のコマンドで準備した Ubuntu 上

```sh
# Ubuntu のコンテナを起動
> docker run -it --name shell-spec-test ubuntu:22.10

# ShellSpec のインストールに必要なツールをインストール
$ apt-get update

$ apt-get install -y wget git
```

※ `$` って書いてるけど、以後のコマンドも含めすべて root ユーザで実行してる（コメント行と区別しやすくするため）

# インストール
```sh
# ShellSpec のインストール
$ wget -O- https://git.io/shellspec | sh

# PATHが通ってるところに shellspec のシンボリックリンクを配置
$ ln -s /root/.local/lib/shellspec/shellspec /usr/local/bin/

# ShellSpec のバージョン
$ shellspec -v
0.28.1
```

- 任意の場所に手動インストールしたい場合は、 [こちら](https://github.com/shellspec/shellspec#manual-installation) を参照
    - git のリポジトリ落としてきてリンクつくればいいっぽい

# Hello World
```sh
# 作業ディレクトリを作成
$ mkdir -p /work/hello-world

$ cd /work/hello-world

# ShellSpec のプロジェクトを作成
$ shellspec --init
  create   /work/hello-world/.shellspec
  create   /work/hello-world/spec/spec_helper.sh
```

## フォルダ構成・実装
```
/work/hello-world
 |-spec/
 | |-hello_spec.sh *
 | `-spec_helpler.sh
 |-lib/ *
 | `-hello.sh *
 `-.shellspec

* がついてるのは追加で作成したディレクトリ・ファイル
```

```sh:hello.sh
hello() {
  echo "Hello ${1}?"
}
```

```sh:hello_spec.sh
HOGE=hoge

Describe 'hello.sh'
  Include lib/hello.sh

  echo "HOGE=${HOGE}"

  It 'says hello'
    When call hello ShellSpec
    The output should equal 'Hello ShellSpec!'
  End
End
```

## 実行結果
```sh
$ shellspec
HOGE=hoge
Running: /bin/sh [sh]
F

Examples:
  1) hello.sh says hello
     When call hello ShellSpec

     1.1) The output should equal Hello ShellSpec!

            expected: "Hello ShellSpec!"
                 got: "Hello ShellSpec?"

          # spec/hello_spec.sh:11

Finished in 0.03 seconds (user 0.03 seconds, sys 0.00 seconds)
1 example, 1 failure


Failure examples / Errors: (Listed here affect your suite's status)

shellspec spec/hello_spec.sh:8 # 1) hello.sh says hello FAILED
```

## 説明
```
/work/hello-world
 |-spec/
 | |-hello_spec.sh *
 | `-spec_helpler.sh
 |-lib/ *
 | `-hello.sh *
 `-.shellspec
```

- ShellSpec は、**プロジェクトディレクトリ**の直下で実行できる
- `.shellspec` が配置されたディレクトリが、プロジェクトディレクトリのルートとなる（プロジェクトルート）
    - `.shellspec` には、デフォルトで適用するオプションを記載できる
    - デフォルトで適用したいオプションが無くても、空ファイルを配置しておく必要がある
    - 0.28.0 より前はファイルがなくても動いていたけど、 0.28.0 以降はファイルの存在がチェックされるようになった
- テスト仕様を書いたファイルは `spec` ディレクトリの下に配置する
    - デフォルトでは、 `spec` ディレクトリの下の `_spec.sh` で終わるファイルがテスト仕様と見なされる（変更可能。[詳細後述](#実行対象のテスト仕様)）
- `spec/spec_helper.sh` は、すべてのテストで共通する設定をしたり、すべてのテストから参照できるグローバルな関数を定義したりするのに使用する（[詳細後述](#ヘルパー)）

```sh
HOGE=hoge

Describe 'hello.sh'
  Include lib/hello.sh

  echo "HOGE=${HOGE}"

  It 'says hello'
    When call hello ShellSpec
    The output should equal 'Hello ShellSpec!'
  End
End
```

- テスト仕様は、独自の DSL で記述する
- このテスト仕様は、実行前にシェルスクリプトに変換されて実行される
    - `shellspec --translate` を実行すると、変換後の状態が確認できる
- 文法はシェルスクリプトと互換性があるらしい
    - なので、テスト仕様のファイル自体を ShellCheck にかけることができたりする
- 任意のシェルコマンドを途中に記述することが可能
    - ただし、スコープが普通のシェルスクリプトとかと違う（`Describe` でスコープが分けられてたりする）ので注意

# 実行シェル
```sh
$ shellspec
Running: /bin/sh [sh]
...
```

- デフォルトでは、 `/bin/sh` を使ってテストが実行される
- 任意のシェルで実行したい場合は、 `-s` オプションを指定する

```sh
$ shellspec -s bash
Running: /usr/bin/bash [bash 5.2.2(1)-release]
...
```

- 毎回指定するのが面倒な場合は、 `.shellspec` に記載しておく

```text:.shellspec
--require spec_helper
-s bash
```

```sh:実行結果
$ shellspec
Running: /usr/bin/bash [bash 5.2.2(1)-release]
...
```

# 実行対象のテスト仕様

```text:フォルダ構成
<PROJECT-ROOT>
|-.shellspec
`-spec/
  |-spec_helper.sh
  |-test1_spec.sh
  `-subdir/
    `-test2_spec.sh
```

```sh:spec/test1_spec.sh
echo "test1_spec"
```

```sh:spec/subdir/test2_spec.sh
echo "test2_spec"
```

```text:実行結果
$ shellspec
test2_spec
test1_spec
```

- デフォルトでは、プロジェクトルート直下の `spec` ディレクトリの下にある `_spec.sh` で終わるファイルが全て実行対象となる
- ディレクトリはサブディレクトリも再帰的に検索される

## 実行対象のテスト仕様を格納するディレクトリを変える
```text:フォルダ構成
<PROJECT-ROOT>
|-.shellspec
|-spec/
| `-spec_helper.sh
`-test/
  |-test1.sh
  |-no-test.sh
  `-subdir/
    `-test2.sh
```

- `spec` ではなく、 `test` ディレクトリの下にテスト仕様のファイルを配置している
- テスト対象ではないファイルとして `no-test.sh` も置いている
- `spec_helper.sh` は `spec` ディレクトリの下に置いたままにしている
    - `spec_helper.sh` の配置先は別の設定で `spec` ディレクトリが指定されているため、これはこのままにしておく必要がある
    - 場所は設定で変更可能（[詳細後述](#ヘルパーファイルの場所を変更する)）

```sh:test/test1.sh
echo "test1"
```

```sh:test/no-test.sh
echo "no-test"
```

```sh:test/subdir/test2.sh
echo "test2"
```

```sh:実行結果
$ shellspec --default-path test --pattern '**/test*.sh'
test2
test1
```

- `--default-path` で、テスト仕様を読み込む対象となるディレクトリを指定する
- `--pattern` で、対象となるテスト仕様のファイル名のパターンを指定する
    - ここでは、「任意のサブディレクトリにある `test` で始まり拡張子が `.sh` であるファイル」を対象にしている

## 複数のディレクトリにテスト仕様のファイルを格納する
```text:フォルダ構成
<PROJECT-ROOT>
|-.shellspec
|-spec/
| `-spec_helper.sh
|-module1/
| `-spec/
|   |-no-test.sh
|   `-test-module1.sh
`-module2/
  `-spec/
    `-test-module2.sh
```

- テスト仕様のファイルを、各モジュールディレクトリ直下の `spec` ディレクトリに配置している

```sh:test-module1.sh
echo "test-module1"
```

```sh:no-test.sh
echo "no-test"
```

```sh:test-module2.sh
echo "test-module2
```

```sh:実行結果
$ shellspec --default-path '*/spec' --pattern '**/test*.sh'
test-module1
test-module2
```

- `--default-path` で、プロジェクトルート直下のサブディレクトリの直下にある `spec` ディレクトリを指定
- `--pattern` で、任意のディレクトリの下の `test` で始まり拡張子が `.sh` であるファイルを指定している
    - ここで、 `--pattern` に `test*.sh` と指定してもうまくいかない
    - `--pattern` で指定するパターンは、 `--default-path` からの相対パス**ではなく**、プロジェクトルートからの相対パスに対してマッチングする
    - 例えば、 `test-module1.sh` は `module1/spec/test-module1.sh` としてマッチングされる
    - このため、任意のサブディレクトリを指す `**/` が先に必要となる

# 作業ディレクトリ
```text:フォルダ構成
/work/execdir
 |-.shellspec
 `-spec/
   |-spec_helper.sh
   |-test1_spec.sh
   `-subdir/
     `-test2_spec.sh
```

```sh:test1_spec.sh
echo "pwd=$(pwd) in spec dir"
```

```sh:test2_spec.sh
echo "pwd=$(pwd) in subdir"
```

```text:実行結果
$ shellspec
pwd=/work/execdir in subdir
pwd=/work/execdir in spec dir
```

- 各テスト仕様実行時の作業ディレクトリは、テスト仕様の場所に関係なく常にプロジェクトルートになる（デフォルト）

## 作業ディレクトリを変更する
```text:フォルダ構成
/work/execdir
 |-.shellspec
 |-src/ <--- NEW
 `-spec/
   |-spec_helper.sh
   |-test1_spec.sh
   `-subdir/
     `-test2_spec.sh
```

- プロジェクトルートに `src` というディレクトリを作って、そっちを作業ディレクトリにしてみる

```text:実行結果
$ shellspec --execdir @project/src
pwd=/work/execdir/src in subdir
pwd=/work/execdir/src in spec dir
```

- `--execdir` オプションで、実行時の作業ディレクトリを指定できる
- 作業ディレクトリの指定は、あらかじめ用意されたいくつかのロケーションからの相対パスで指定する
- ロケーションには、以下の3つが用意されている
    - `@project`
        - プロジェクトルート
        - `.shellspec` ファイルが配置されているディレクトリ
        - デフォルトはこれ
    - `@basedir`
        - 実行中のテスト仕様のファイルが配置されているディレクトリから親ディレクトリにさかのぼっていき、最初に `.shellspec` または `.shellspec-basedir` ファイルが見つかったディレクトリ
    - `@specfile`
        - 実行中のテスト仕様のファイルが配置されているディレクトリ
- 作業ディレクトリは、必ずプロジェクトディレクトリの中のディレクトリしか指定できない
    - `@project/..` とかしてもエラーになる

**@basedir の場合**

```text:フォルダ構成
/work/execdir
 |-.shellspec
 `-spec/
   |-.shellspec-basedir <--- NEW
   |-spec_helper.sh
   |-test1_spec.sh
   `-subdir/
     `-test2_spec.sh
```

- `spec` ディレクトリ直下に `.shellspec-basedir` ファイルを配置（空ファイル）

```sh:実行結果
$ shellspec --execdir @basedir
pwd=/work/execdir/spec in subdir
pwd=/work/execdir/spec in spec dir
```

**@specfileの場合**

```text:フォルダ構成
/work/execdir
 |-.shellspec
 `-spec/
   |-spec_helper.sh
   |-test1_spec.sh
   `-subdir/
     `-test2_spec.sh
```

```sh:実行結果
$ shellspec --execdir @specfile
pwd=/work/execdir/spec/subdir in subdir
pwd=/work/execdir/spec in spec dir
```

# DSL の文法
## Describe, Context, ExampleGroup
```sh
MESSAGE="default message"

echo "MESSAGE=${MESSAGE} @all begin"

Describe "hoge"
    echo "MESSAGE=${MESSAGE} @hoge begin"
    MESSAGE="hoge"
    Context "fuga"
        echo "MESSAGE=${MESSAGE} @fuga begin"
        MESSAGE="fuga"
        echo "MESSAGE=${MESSAGE} @fuga end"
    End

    ExampleGroup "piyo"
        echo "MESSAGE=${MESSAGE} @piyo"
    End

    echo "MESSAGE=${MESSAGE} @hoge end"
End

echo "MESSAGE=${MESSAGE} @all end"
```

```text:実行結果
MESSAGE=default message @all begin
MESSAGE=default message @hoge begin
MESSAGE=hoge @fuga begin
MESSAGE=fuga @fuga end
MESSAGE=hoge @piyo
MESSAGE=hoge @hoge end
MESSAGE=default message @all end
```

- `Describe`, `Context`, `ExampleGroup` を使うと example グループブロック(example group block)を定義できる
    - example グループブロックには、他の example グループブロックや、 example ブロックを含めることができる
        - 要するに `Describe` や `It` を入れ子にできる、ということ
    - `Describe` と `Context` は、 `ExampleGroup` のエイリアス
- `Describe "グループの説明"` で開始し、 `End` で終了する
- グループ内は独自のスコープになる
    - スコープ外で定義された変数は参照できるが、スコープ内で設定した値はスコープを出ると参照できなくなる

## It, Specify, Example
```sh
It "hello"
    When call echo hello
    The output should equal HELLO
End
```

```text:実行結果
Examples:
  1) hello
     When call echo hello

     1.1) The output should equal HELLO

            expected: "HELLO"
                 got: "hello"
```

- `It`, `Specify`, `Example` を使うと、 example ブロック(example block)を定義できる（要するに、1つのテストケース）
    - `It`, `Specify` は、 `Example` のエイリアス
- `It "説明"` で開始し、 `End` で終了する
- example ブロックは、以下のもので構成される
    - 1つの評価(evaluation)
        - 上の例だと、 `When` の行が該当する
    - 複数の期待結果(expectations)
        - 上の例だと、 `The` の行が該当する

## When
```sh
It "When"
    When call echo hello
    The output should equal hello
End
```

- `When` を使うことで、検証したいシェル関数やコマンドの実行を宣言できる
    - `When` は、 `It` で定義した example ブロックの中で使用する
- `When <call | run> <function | command> [arguments...]` という感じで指定する
- `When call` は現在のシェルで関数またはコマンドを実行し、 `When run` はサブシェルで関数またはコマンドを実行する
    - シェルスクリプトの場合は `When call` でも別プロセスとして起動された

```sh:callとrunの違い
hoge() {
    MESSAGE=hoge
    echo 1
}

It "When call"
    MESSAGE=hello
    echo "before MESSAGE=${MESSAGE} @ When call"

    When call hoge
    The output should equal 1
    
    echo "after MESSAGE=${MESSAGE} @ When call"
End

It "When run"
    MESSAGE=hello
    echo "before MESSAGE=${MESSAGE} @ When run"

    When run hoge
    The output should equal 1
    
    echo "after MESSAGE=${MESSAGE} @ When run"
End
```

```text:実行結果
before MESSAGE=hello @ When call
after MESSAGE=hoge @ When call

before MESSAGE=hello @ When run
after MESSAGE=hello @ When run
```

- `call` の方は同じシェルで hoge 関数が実行されるため、変数の変更が呼び出し元にも影響している

### When run のバリエーション
- `When run` には、さらにいくつかのバリエーションがある

```sh:tesh.sh
#!/bin/bash

echo "MESSAGE=${MESSAGE} SHELL=${SHELL} @ test.sh"
```

```sh:test_spec.sh
MESSAGE="Hello World"

It "When run"
    When run ./test.sh
    The output should equal "test"
End

It "When run command"
    PATH=${PATH}:/work/hello-world
    When run command test.sh
    The output should equal "test"
End

It "When run script"
    When run script test.sh
    The output should equal "test"
End

It "When run source"
    When run source ./test.sh
    The output should equal "test"
End
```

```text:実行結果
Running: /bin/sh [sh]
.FFFF

Examples:
  1) When run
     When run ./test.sh

     1.1) The output should equal test

            expected: "test"
                 got: "MESSAGE= SHELL=/bin/bash @ test.sh"

          # spec/test_spec.sh:5

  2) When run command
     When run command test.sh

     2.1) The output should equal test

            expected: "test"
                 got: "MESSAGE= SHELL=/bin/bash @ test.sh"

          # spec/test_spec.sh:11

  3) When run script
     When run script test.sh

     3.1) The output should equal test

            expected: "test"
                 got: "MESSAGE= SHELL= @ test.sh"

          # spec/test_spec.sh:16

  4) When run source
     When run source ./test.sh

     4.1) The output should equal test

            expected: "test"
                 got: "MESSAGE=Hello World SHELL= @ test.sh"

          # spec/test_spec.sh:21
```

- `When run command ...`
    - シェルスクリプトまたはコマンドを実行する
        - パスが通っている必要がある
    - シェルスクリプトの場合、シバンで指定したシェルでスクリプトが実行される
- `When run script ...`
    - シェルスクリプトを実行する
    - シバンは無視され、 ShellSpec を実行しているシェルの別インスタンスで実行される
- `Whne run source ...`
    - シェルスクリプトを実行する
    - シバンは無視される
    - 呼び出し元で宣言した変数などが引き継がれた状態で実行される
        - [ドキュメント](https://github.com/shellspec/shellspec#using-run-source)には、「Can be refer to variables inside the shell script.（シェルスクリプト内の変数を参照できる）」と書かれている
        - しかし、実際に試すと呼び出したシェルスクリプト内で設定した変数の値は呼び出し元からは参照できない（バグ？）
    - モックを利用したいときはこれで実行する必要がある（詳細は[こちら](#シェルスクリプト内で使用されているコマンドをモックにする)）


まとめると、以下のような感じ。

|比較項目|run|run command|run script|run source|
|---|---|---|---|---|
|シェル関数の実行|○|×|×|×|
|シェルスクリプトの実行|○|○|○|○|
|コマンドの実行|○|○|×|×|
|シバン|有効|有効|無視|無視|
|変数等の継承|×|×|×|○|

## The
```sh
It "test"
    When call echo hello
    The output should equal hello
End
```

- `The` を使うことで、 `When` で実行した結果を検証できる
- `The` は、以下のような文法で記述する

```text:Theの文法
The [Modifiers...] <Subjects> should <Matchers> <expected value>
```

### Subjects
```
The output should equal hello
```

- Subjects では、検証対象を指定する
- 上記例だと、 `output` が Subject になる
- Subjects には、以下のようなものが用意されている(非推奨となっているものは割愛)

|Subject|説明|
|---|---|
|`stdout` / `output`|標準出力|
|`line <N>`|標準出力のN行目を対象にする|
|`word <N>`|標準出力のN番目の単語を対象にする|
|`stderr` / `error`|標準エラー出力|
|`status`|終了ステータス|
|`path` / `file` / `directory`|任意のパス|
|`function <関数名>` <br>/ `<関数名>()`|関数を対象にする(Modifers の `result` と組み合わせて使うっぽい)|
|`variable <変数名>`|変数の値|

#### stdout / output

```sh
It "test"
    When call echo hello world
    The output should equal "hello world"
End
```

#### line

```sh
myfunc() {
    cat << EOS
hoge
fuga
piyo
EOS
}

It "test"
    When call myfunc
    The line 2 should equal "fuga"
End
```

#### word

```sh
It "test"
    When call echo hello world
    The word 2 should equal "world"
End
```

#### stderr / error

```sh
myfunc() {
    echo hello >&2
}

It "test"
    When call myfunc
    The error should equal "hello"
End
```

#### status

```sh
myfunc() {
    return 2
}

It "test"
    When call myfunc
    The status should equal 2
End
```

#### path / file / directory

```sh
It "test"
    touch /work/hoge
    The file /work/hoge should be exist
End
```

#### function

```sh
myfunc() {
    echo hoge
}

It "test"
    The function myfunc should equal myfunc
    The result of function myfunc should equal hoge

    The "myfunc()" should equal myfunc
    The result of "myfunc()" should equal hoge
End
```

- `function` だけだと、ただ関数名を検証するだけになってよくわからないが、 Modifiers の `result` と組み合わせることで関数の結果を検証対象にできるようになる
    - カスタムの検証を行いたい場合に利用できるっぽい
- `<関数名>()` という省略した書き方ができる


#### variable

```sh
It "test"
    MESSAGE=hoge
    The variable MESSAGE should equal "hoge"
End
```

### Modifiers
```sh
It "test"
    When call echo hello world
    The word 2 of output should equal "world"
End
```

- Modifiers は、 Subjects で指定した対象をさらに限定するために用いる修飾要素になる
- 上記例でいうと、 `word 2 of` が該当する
    - output の2単語目に検証対象を絞り込んでいる
- 以下の Modifier が用意されている

|Modifer|説明|
|---|---|
|`line <N>`|Subjectの N 行目|
|`lines`|Subjectの行数|
|`word <N>`|Subjectの N 単語目|
|`length`|Subjectの文字数|
|`contents`|Subjectが指すファイルの内容|
|`result`|Subjectが指す関数の実行結果|

#### line

```sh
myfunc() {
    cat << EOF
hoge
fuga
piyo
EOF
}

It "test"
    When call myfunc
    The line 2 of output should equal "fuga"
End
```

#### lines

```sh
myfunc() {
    cat << EOF
hoge
fuga
piyo
EOF
}

It "test"
    When call myfunc
    The lines of output should equal 3
End
```

#### word

```sh
It "test"
    When call echo hello world
    The word 2 of output should equal "world"
End
```

#### length

```sh
It "test"
    When call echo hello world
    The length of output should equal 11
End
```

#### contents

```sh
It "test"
    echo Hello World > test.txt
    The contents of file test.txt should equal "Hello World"
End
```

#### result

```sh
myfunc() {
    echo Hello World
}

It "test"
    The result of function myfunc should equal "Hello World"
End
```

#### 連鎖させる
```sh
myfunc() {
    cat << EOF
hoge fuga
foo bar
fizz buzz
EOF
}

It "test"
    The word 1 of line 3 of result of function myfunc should equal "fizz"
End
```

- Modifier は連鎖させることができる
    - `myfunc` 関数の実行結果の (`result of function`)
    - 3行目の (`line 3 of`)
    - 1単語目 (`word 1 of`)

#### 序数詞を使う

```sh
It "test"
    When call echo Hello World
    The second word of output should equal "World"
End
```

- Modifer に渡す引数が数値の場合は、序数詞を用いた表現に置き換えることができる

### Matchers

```sh
It "test"
    When call echo Hello World
    The output should equal "Hello World"
End
```

- Matchers は、 Subject の内容を検証する方法を定義する要素になる
- 上記例でいうと、 `equal` が該当する
- いっぱい用意されている

#### 否定

```sh
It "test"
    When call echo hello
    The output should not equal "hello"
End
```

- `should` の後ろに `not` をつければ、各 Matcher の否定を検証できる

#### satisfy
```sh
myfunc() {
    test ${myfunc} = "hoge"
}

It "test"
    When call echo hoge
    The output should satisfy myfunc
End
```

- `satisfy <関数>` で指定した関数が 0 を返したら検証 OK になる
- 検証対象の Subject は、 `satisfy` に指定した関数と同じ名前の変数に格納されている

#### ファイル系の Matcher

Subjectで指定されたパスが条件を満たしているかどうかを検証する Matcher。

|Matcher|説明|
|---|---|
|`be exist`|ファイルやディレクトリが存在することを検証する[^1]|
|`be file`|ファイルであることを検証する|
|`be directory`|ディレクトリであることを検証する|
|`be empty file`|空のファイルであることを検証する|
|`be empty directory`|空のディレクトリであることを検証する|
|`be symlink`|シンボリックリンクであることを検証する|
|`be pipe`|パイプ[^2]であることを検証する|
|`be socket`|ソケット[^3]であることを検証する|
|`be readable`|読み取り可能であることを検証する|
|`be writable`|書き込み可能であることを検証する|
|`be executable`|実行可能であることを検証する|
|`be block_device`|ブロックデバイス[^4]であることを検証する|
|`be character_device`|キャラクタデバイス[^4]であることを検証する|
|`have setgid`|SGID[^5]が設定されていることを検証する|
|`have setuid`|SUID[^6]が設定されていることを検証する|

[^1]: ドキュメントでは非推奨扱いになっていて `exist` を使うように書かれているけど、実際に `exist` を使うとそんなもんはねぇって怒られるので `be exist` を使うしかない(0.28.1 で確認)
[^2]: [「名前付きパイプ」（FIFO/Named Pipe）を使ってプロセス間通信を試してみる | ゲンゾウ用ポストイット](https://genzouw.com/entry/2019/03/18/120025/1209/)
[^3]: [Linuxのファイルの種類 - Qiita](https://qiita.com/angel_p_57/items/1faafa275525469788b4#%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88)
[^4]: [デバイスファイル - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB)
[^5]: [SGID（Set Group ID） - 特殊なアクセス権](https://kazmax.zpp.jp/linux_beginner/setgroupid.html)
[^6]: [SUID（Set User ID） - 特殊なアクセス権](https://kazmax.zpp.jp/linux_beginner/setuserid.html)

```sh:例
It "test"
    The path ./test.sh should be exist
    The path ./test.sh should be file
    The path ./spec should be directory
End
```

#### 終了ステータスの Matcher

|Matcher|説明|
|---|---|
|`be success`|ステータスが成功(`0`)であることを検証する|
|`be failure`|ステータスが失敗(`1` - `255`)であることを検証する|

```sh:例
myfunc() {
    return 1
}

It "test"
    When call myfunc
    The status should be failure
End
```

#### 文字列の Matcher

|Matcher|説明|
|---|---|
|`equal <STRING>` / `eq <STRING>`|`<STRING>`と一致することを検証する|
|`start with <STRING>`|`<STRING>`で始まることを検証する|
|`end with <STRING>`|`<STRING>`で終わることを検証する|
|`include <STRING>`|`<STRING>`を含むことを検証する|
|`match pattern <PATTERN>`|`<PATTERN>`にマッチすることを検証する|

```sh:例
It "test"
    When call echo Hoge
    The output should eq "Hoge"
    The output should start with "H"
    The output should end with "e"
    The output should include "og"
    The output should match pattern "H*"
End
```

- `matche pattern` で使えるパターンの例
    - `*` : 任意の文字列
    - `?` : 任意の一文字
    - `[abc]` : `[]` 内のいずれかの文字
        - `[a-z]` のように、 `-` で範囲指定も可能
        - `[!abc]` のように先頭に `!` をつけることで否定（いずれでもない）にできる
    - `abc|edf` : いずれかにマッチ
- [実装を見ると](https://github.com/shellspec/shellspec/blob/0.28.1/lib/general.sh#L518)、内部的には `case` 文のマッチが使われている

#### successful

```sh
myfunc() {
    return 0
}

It "test"
    The result of function myfunc should be successful
End
```

- `result of function` の結果が成功(`0`)かどうかを検証する場合は、 `be successful` を使用する
    - `be success` だとエラーになる

#### 変数の Matcher

|Matcher|説明|
|---|---|
|`be defined`|変数が定義されていることを検証する|
|`be undefined`|変数が定義されていないことを検証する|
|`be present`|変数に値が設定されている（空文字ではない）ことを検証する|
|`be blank`|変数に値が設定されていない（未定義か空文字である）ことを検証する|
|`be exported`|変数が export されていることを検証する|
|`be readonly`|変数が読み取り専用であることを検証する|

```sh
It "test"
    HOGE=
    PIYO=piyo
    export FOO=foo
    BAR=bar
    readonly BAR

    The variable HOGE should be defined
    The variable FUGA should be undefined
    The variable PIYO should be present
    The variable HOGE should be blank
    The variable Fuga should be blank
    The variable FOO should be exported
    The variable BAR should be readonly
End
```

# 他のシェルスクリプトを読み込む
```sh:test.sh
#!/bin/bash

myfunc() {
    echo Hello World
}
```

```sh:test_spec.sh
Include ./test.sh

It "test"
    When call myfunc
    The output should equal "Hello World"
End
```

- `Include <読み込むシェルスクリプト>` で、指定したシェルスクリプトを現在のシェルで実行する
    - `source` している感じ
- テスト対象の関数が定義されているシェルスクリプトとかをこれで読み込んでテストする感じになる

# テスト前後の処理
```sh
beforeAll1() {
    echo beforeAll1
}
afterAll1() {
    echo afterAll1
}
beforeEach1() {
    echo beforeEach1
}
afterEach1() {
    echo afterEach1
}
beforeAll2() {
    echo beforeAll2
}
afterAll2() {
    echo afterAll2
}
beforeEach2() {
    echo beforeEach2
}
afterEach2() {
    echo afterEach2
}

Describe "Describe1"
    BeforeAll beforeAll1
    AfterAll afterAll1
    BeforeEach beforeEach1
    AfterEach afterEach1

    It "Test1"
        echo "Test1"
    End

    It "Test2"
        echo "Test2"
    End

    Describe "Describe2"
        BeforeAll beforeAll2
        AfterAll afterAll2
        BeforeEach beforeEach2
        AfterEach afterEach2

        It "Test3"
            echo "Test3"
        End
    End
End
```

```text:実行結果（見やすいように整形）
beforeAll1
  beforeEach1
    Test1
  afterEach1

  beforeEach1
    Test2
  afterEach1

  beforeAll2
    beforeEach1
      beforeEach2
        Test3
      afterEach2
    afterEach1
  afterAll2
afterAll1
```

- `BeforeAll`, `AfterAll` に関数名を渡すことで、 example グループブロックの前後に処理を入れることができる
- `BeforeEach`, `AfterEach` に関数名を渡すことで、 example ブロックの前後に処理を入れることができる
    - 入れ子にした example グループブロック内の example ブロックにも適用される
- すべてのテスト仕様に共通で適用したい場合は、ヘルパーファイルで設定可能
    - 詳細は [こちら](#全テスト仕様共通の前処理後処理を登録する)


# 標準入力のデータを定義する
```sh
myfunc() {
    read ans
    if [ "${ans}" = "y" ]; then
        echo yes
    else
        echo no
    fi
}

It "Test"
    Data "y"
    When call myfunc
    The output should equal "yes"
End
```

- `When` の前に `Data <STRING>` をという形で、 `When` で実行する処理に流し込む標準入力の値を定義できる
    - 上の例では、 `read` コマンドで読み取る値に対して `y` という値を標準入力を流し込むようにしている
- 複数行の定義も可能

```sh:複数行の標準入力を定義する場合
It "Test"
    Data
    #|hoge
    #|fuga
    #|piyo
    End
    When call cat
    The line 2 of output should equal "fuga"
End
```

- `Data` から `End` までの間に、複数行の標準入力値を記載する
- `#|` から後ろが実際に使用される値になる
- 上記例の場合、値の中に変数を埋め込むことはできない
- 変数を埋め込む場合は以下のようにする

```sh:複数行入力の中に変数を埋め込みたい場合
It "Test"
    MESSAGE="Hello World"
    Data:expand
    #|hoge
    #|MESSAGE=${MESSAGE}
    #|piyo
    End
    When call cat
    The line 2 of output should equal "MESSAGE=Hello World"
End
```

- `Data:expand` で開始するように書くと、変数の埋め込みが可能になる
- 他にも、関数の実行結果を渡すことも可能

```sh:関数の実行結果を標準入力にする
myfunc() {
    echo "Hello $1"
}

It "Test"
    Data myfunc world
    When call cat
    The output should equal "Hello world"
End
```

- `Data <関数> [arguments...]` で、関数の実行結果を標準入力として利用できる
- ファイルの内容を入力することも可能

```sh:ファイルの内容を標準入力にする
echo "Hello World" > input.txt

It "Test"
    Data < input.txt
    When call cat
    The output should equal "Hello World"
End
```

- `Data < <入力ファイル>` で、指定したファイルの内容を標準入力にできる

# パラメータ化テスト
```sh
Parameters:value 1 2 "hoge"

It "test $1"
    echo "test1 param=$1"
End

It "test $1"
    echo "test2 param=$1"
End

Describe "Describe"
    Parameters:value 3 4 "fuga"
    It "test $1"
        echo "test3 param=$1"
    End
End
```

```text:実行結果
test1 param=1
test1 param=2
test1 param=hoge
test2 param=1
test2 param=2
test2 param=hoge
test3 param=1
test3 param=2
test3 param=hoge
test3 param=3
test3 param=4
test3 param=fuga

Examples:
  1) test 1
    ...
  2) test 2
    ...
  3) test hoge
    ...
  4) test 1
    ...
  5) test 2
    ...
  6) test hoge
    ...
  7) Describe  test 1
    ...
  8) Describe  test 2
    ...
  9) Describe  test hoge
    ...
  10) Describe  test 3
    ...
  11) Describe  test 4
    ...
  12) Describe  test fuga
    ...
```

- `Parameters:value <params...>` で、テストの実行ごとに渡すパラメータを定義できる
- `Parameters` を定義した example グループブロック内のすべての example ブロックに適用される
- パラメータは、位置パラメータとして参照できる
- `It` に渡す説明の中でも参照できる

## 1回に複数のパラメータを渡す
```sh
Parameters
    1 2 "hoge"
    3 4 "fuga"
End

It "test"
    echo "$#: $@"
End
```

```text:実行結果
3: 1 2 hoge
3: 3 4 fuga
```

- `Parameters`～`End` で、各テスト実行ごとに複数のパラメータを渡すように定義できる
- 各行に、スペース区切りで渡すパラメータを記載する

## 組み合わせでパラメータを渡す
```sh
Parameters:matrix
    child adult
    male female "not known" "not applicable"
    japanese foreigner
End

It "test"
    echo "$@"
End
```

```text:実行結果
child male japanese
child male foreigner
child female japanese
child female foreigner
child not known japanese
child not known foreigner
child not applicable japanese
child not applicable foreigner
adult male japanese
adult male foreigner
adult female japanese
adult female foreigner
adult not known japanese
adult not known foreigner
adult not applicable japanese
adult not applicable foreigner
```

- `Parameters:matrix` を使うと、列挙した値の組み合わせをパラメータにしてテストを実行できる

## 動的に定義する
```sh
Parameters:dynamic
  for i in 1 2 3; do
    %data "#$i" 1 2 3
  done
End

It "test"
    echo "$@"
End
```

```text:実行結果
#1 1 2 3
#2 1 2 3
#3 1 2 3
```

- `Parameters:dynamic` を使うと、パラメータを動的に定義できる
- `%data` は動的にパラメータを定義するときに使用する特殊なディレクティブで、この引数に渡した値がパラメータとして利用される

# ディレクティブ
- シェルスクリプトだと書きにくいものとかを書きやすくするちょっとした組み込みの命令（ディレクティブ）が用意されている
- パッと見で使いそうな気がしたものだけピックアップ
    - 全体は [ドキュメント](https://github.com/shellspec/shellspec#directives) を参照
- ディレクティブはシェル関数ではないので、使用できる箇所に制限がある
    - 関数定義の冒頭か、行の先頭でのみ使用できる

## テキストの埋め込み

```sh
MESSAGE=$(
    %text
    #|hoge
    #|fuga
    #|piyo
)

It "test"
    When call echo "$MESSAGE"
    The line 2 of output should equal "fuga"
End
```

- `%text` ディレクティブを使うと、テキストの埋め込みができる
    - `%text` の下に埋め込むテキストを記述する
    - `#|` から後ろが埋め込まれるテキストになるので、インデントの影響を受けずに複数行のテキストが書ける
- ヒアドキュメントよりもきれいに複数行のテキストを記述できる
- `%text` は行の最初に存在する必要があるので、 `$(%text` とは書けない
- `%text` は、実行時に以下のように変換される

```sh:%textの変換後の状態
MESSAGE=$(
shellspec_cat <<'DATA-DELIMITER-1667056487-735'
hoge
fuga
piyo
DATA-DELIMITER-1667056487-735
)
```

- `shellspec_cat` が具体的にどういう関数かはわからないが、おそらく `cat` コマンド的なものだと思われる
- `%text` ディレクティブは `cat` を使ったヒアドキュメントに置き換えられる、と考えればどこでどういう書き方をすれば利用できるか、おのずとイメージできると思う

## 変数の保存
```sh:test.sh
#!/bin/sh

MESSAGE=hello
```

```sh:test_spec.sh
It "test"
    When run source ./test.sh
    The variable MESSAGE should equal "hello"
End
```

- `test.sh` を実行し、定義された `MESSAGE` 変数の値を検証しようとしている

```text:実行結果
Examples:
  1) test
     When run source ./test.sh

     1.1) The variable MESSAGE should equal hello

            expected: "hello"
                 got: <unset>
```

- テストは失敗する
- `When run source` を使っても、シェルスクリプトはサブシェルで実行されるっぽくて、宣言した変数などは呼び出し元では参照できない
- これを、呼び出し元でも参照できるようにするために `%preserve` ディレクティブがある

```sh:変数を参照できるようにする
It "test"
    preserve() {
        %preserve MESSAGE
    }
    AfterRun preserve
    
    When run source ./test.sh
    The variable MESSAGE should equal "hello"
End
```

- `AfterRun` で `When run` の実行後に処理を差し込んで、 `%preserve` ディレクティブで保存する変数名を指定する
    - 変数名は一度に複数指定可能 (`%preserve AAA BBB CCC`)
- これで、呼び出し元でも宣言された変数の値を参照できるようになる

# ヘルパー
- デフォルトで生成される `spec/spec_helper.sh` には、すべてのテスト仕様で共通の設定や、グローバルな関数を定義することができる
- 細かい話は[公式ドキュメント](https://github.com/shellspec/shellspec#spec_helper)を参照

## グローバル関数を定義する
```text:フォルダ構成
<PROJECT-ROOT>
|-.shellspec
`-spec/
  |-spec_helper.sh
  |-test1_spec.sh
  `-subdir/
    `-test2_spec.sh
```

```sh:spec_helper.sh
# 他はデフォルト出力のまま
...

my_global_func() {
  echo "global function from $1"
}
```

- `spec_helper.sh` に、独自関数(`my_global_func`)を定義している

```sh:test1_spec.sh
my_global_func "test1"
```

```sh:test2_spec.sh
my_global_func "test2"
```

```sh:実行結果
$ shellspec
global function from test2
global function from test1
```

- `spec_helper.sh` に関数を定義すれば、その関数はすべてのテスト仕様から参照できる

## 全テスト仕様共通の前処理・後処理を登録する
```text:フォルダ構成
<PROJECT-ROOT>
|-.shellspec
`-spec/
  |-spec_helper.sh
  |-test1_spec.sh
  `-subdir/
    `-test2_spec.sh
```

```sh:spec_helper.sh
# 他はデフォルト出力のまま
...

spec_helper_configure() {
  # Available functions: import, before_each, after_each, before_all, after_all
  : import 'support/custom_matcher'

  before_all "global_before_all"
  after_all "global_after_all"
  before_each "global_befor_each"
  after_each "global_after_each"
}

global_before_all() {
  echo "global before all"
}

global_after_all() {
  echo "global after all"
}

global_befor_each() {
  echo "global before each"
}

global_after_each() {
  echo "global after each"
}
```

- `spec_helper_configure` 関数の中で、全テスト仕様で共通に適用する前後処理を登録できる

|登録用関数|説明|
|---|---|
|`before_all`|各テスト仕様のファイルが実行される前の処理を登録する|
|`after_all`|各テスト仕様のファイルが実行された後の処理を登録する|
|`before_each`|各 example ブロックが実行される前の処理を登録する|
|`after_each`|各 example ブロックが実行された後の処理を登録する|

```sh:test1_spec.sh
Describe "describe1"
    It "test1-1"
        echo "test1-1"
    End

    It "test1-2"
        echo "test1-2"
    End

    Describe "describe1-1"
        It "test1-1-1"
            echo "test1-1-1"
        End
    End
End
```

```sh:test2_spec.sh
It "test2"
    echo "test2"
End
```

```text:実行結果（見やすいように整形）
global before all
  global before each
    test2
  global after each
global after all

global before all
  global before each
    test1-1
  global after each
  global before each
    test1-2
  global after each
  global before each
    test1-1-1
  global after each
global after all
```

## ヘルパーファイルの場所を変更する
```text:フォルダ構成
<PROJECT-ROOT>
 |-.shellspec
 |-shellspec/
 | `-spec_helper.sh
 `-spec/
   |-test1_spec.sh
   `-subdir/
     `-test2_spec.sh
```

- `shellspec` ディレクトリを作成し、その下にヘルパーファイルを配置

```sh:spec_helper.sh
# 他はデフォルト出力のまま
...

my_global_func() {
  echo "my global fun"
}
```

```sh:test1_spec.sh
my_global_func
```

```sh:test2_spec.sh
my_global_func
```

```sh:実行結果
$ shellspec --helperdir shellspec
my global fun
my global fun
```

- `--helperdir` で、ヘルパーファイルが配置されているディレクトリを指定する

# モック
```sh
Describe "d1"
    Describe "d2"
        # pwd コマンドのモックを定義
        pwd() {
            echo "mocked pwd"
        }
        It "test1"
            When call pwd
            # モック化された pwd が呼ばれる
            The output should equal "mocked pwd"
        End
    End

    It "test2"
        # こっちは pwd のモックを定義していないので、従来の pwd コマンドが実行される
        When call pwd
        The output should equal "/work/hello-world"
    End
End
```

- スコープの中でモック化したいコマンドやシェル関数と同名の関数を定義することで、そのサブシェルの中ではモック関数が代わりに呼ばれるようなる
    - スコープを出たら、本来のコマンドやシェル関数の動きに戻る
- この方法は関数ベースのモック(function-based mock)と呼び、基本的にはこの方法が推奨される
    - 関数名として利用できない文字が含まれているコマンドをモックしたいときなどは、もう１つのモック化の方法であるコマンドベースのモック(command-based mock)を使用する（後述）
    - ハイフンは関数名に使用できないので、例えば `docker-compose` とかをモック化したいならコマンドベースのモックが必要になる

## シェルスクリプト内で使用されているコマンドをモックにする

さきに、うまくいかないケース。

```sh:test.sh
#!/bin/sh

pwd
```

```sh:test_spec.sh
pwd() {
    echo "mocked pwd"
}

It "test"
    When run ./test.sh
    The output should equal "mocked pwd"
End
```

```sh:実行結果
Running: /bin/sh [sh]
F

Examples:
  1) test
     When run ./test.sh

     1.1) The output should equal mocked pwd

            expected: "mocked pwd"
                 got: "/work/hello-world"

          # spec/test_spec.sh:7
```

- `When run` でシェルスクリプトを実行した場合、シェルスクリプト内で参照されているコマンドはモックには差し替えられない
- この場合は、 `When run source` でシェルスクリプトを実行することでモックが有効になる

```sh:test_spec.sh
pwd() {
    echo "mocked pwd"
}

It "test"
    When run source ./test.sh
    The output should equal "mocked pwd"
End
```

```sh:実行結果
Running: /bin/sh [sh]
.

Finished in 0.03 seconds (user 0.02 seconds, sys 0.00 seconds)
1 example, 0 failures
```

- モック化できている

## コマンドベースのモック
```sh
Mock docker-compose
    echo "mocked docker-compose"
End

It "test"
    When run docker-compose
    The output should equal "mocked docker-compose"
End
```

- `Mock` を使用することで定義できる
    - `Mock <モック化するコマンドの名前>` と指定する
    - `Mock` から `End` の間に、モックの処理を記述する
- 関数ベースのモックに比べて良い点は以下
    - 関数名に使用できない文字(`-`など)を含むコマンドもモックとして定義できる
    - 外部コマンドからもモックのコマンドが実行されるようになる（らしい。試してはいない）
- シェル関数やシェル組み込みの関数はモック化できない
    - `help` コマンドで出てくる奴ら(`echo` とか `if` とか)
- そのほかにもいくつか制約があるので、詳細は[ドキュメント](https://github.com/shellspec/shellspec#command-based-mock)を参照

# カバレッジ
- [kcov/src at master · SimonKagstrom/kcov · GitHub](https://github.com/SimonKagstrom/kcov/tree/master/src) を使ったカバレッジの取得ができる
- 事前に kcov をインストールしておく必要がある

```text:kcovのインストール
$ apt-get install -y kcov
```

```sh:test.sh
#!/bin/sh

if [ "$1" = "y" ]; then
    echo "yes"
else
    echo "no"
fi
```

```sh:test_spec.sh
It "test"
    When run script ./test.sh "y"
    The output should equal "yes"
End
```

```sh:実行結果
$ shellspec -s bash --kcov
Running: /usr/bin/bash [bash 5.2.2(1)-release]
.

Finished in 0.25 seconds (user 0.10 seconds, sys 0.01 seconds)
1 example, 0 failures

Code covered: 66.67%, Executed lines: 2, Instrumented lines: 3
```

- カバレッジ取得を有効にするには、 `--kcov` オプションを指定する
- カバレッジを取得できるのは、 bash, zsh, ksh のいずれかに限られるので、 `-s bash` で bash を使うようにしている
- 収集されたカバレッジの情報は `coverage` ディレクトリの下に出力される
- ↓のように、HTML形式で出力されている
    - 同じディレクトリに json や xml でも出力されている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/3bfb52cf-7db5-1c96-e008-82fd7a48ca0a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/0637fb5a-c187-1147-5bb4-30262eb98eec.png)

## カバレッジ計測の対象

デフォルトでは、以下のコードがカバレッジ計測の対象となる。

- `Include` で読み込んだシェルスクリプト
- `When` で評価され実行されたシェル関数
- `When run script` で評価され実行されたシェルスクリプト
- `When run source` で評価され実行されたシェルスクリプト

## カバレッジレポートの対象

- デフォルトでは、 shellspec のプロジェクトディレクトリ配下に存在する `.sh` で終わるファイルがレポートの対象となる

```text:フォルダ構成
<PROJECT-ROOT>
 |-.shellspec
 |-spec/
 | |-spec_helper.sh
 | `-test_spec.sh
 |-src/
 | |-foo.sh
 | |-bar.sh
 | `-hoge
 `-fuga.sh
```

```sh:foo.sh
#!/bin/sh
echo "foo"
```

```sh:bar.sh
#!/bin/sh
echo "bar"
```

```sh:hoge
#!/bin/sh
echo "hoge"
```

```sh:fuga.sh
#!/bin/sh
echo "fuga"
```

```sh:test_spec.sh
It "test1"
    When run script ./src/foo.sh
    The output should equal "foo"
End

It "test2"
    PATH=${PATH}:${SHELLSPEC_PROJECT_ROOT}/src
    When run command bar.sh
    The output should equal "bar"
End
```

- `src/foo.sh` は、 `When run script` で実行してテストしている
- `src/bar.sh` は、 `When run command` で実行してテストしている

```sh:実行結果
$ shellspec -s bash --kcov
Running: /usr/bin/bash [bash 5.2.2(1)-release]
..

Finished in 0.35 seconds (user 0.10 seconds, sys 0.04 seconds)
2 examples, 0 failures

Code covered: 33.33%, Executed lines: 1, Instrumented lines: 3
```

**カバレッジレポート**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/2f574e2a-1bf2-cf48-77c9-1ab21c31f54e.png)

- カバレッジレポートの対象となっているのは以下の3ファイル
    - `fuga.sh`
    - `src/foo.sh`
    - `src/bar.sh`
- `src/hoge` は、拡張子 `.sh` で終わらないのでレポートの対象に入っていない
- 一方、カバレッジが計測されているのは `src/foo.sh` のみとなっている
    - `src/bar.sh` は `When run command` で実行したため、カバレッジ計測の対象外となっている

## カバレッジのレポート対象を変更する

```sh
$ shellspec -s bash --kcov --kcov-options "--include-path=src --include-pattern="
Running: /usr/bin/bash [bash 5.2.2(1)-release]
..

Finished in 0.32 seconds (user 0.08 seconds, sys 0.05 seconds)
2 examples, 0 failures

Code covered: 33.33%, Executed lines: 1, Instrumented lines: 3
```

- `--kcov-options` で、 kcov のオプションを指定する
- `--include-path` で、レポート対象のディレクトリを指定し、 `--include-pattern` で対象のファイルパターンを指定する
    - 空にしたら全ファイルが対象になるっぽい（よくわかってない）
    - デフォルトは `--include-path=.`, `--include-pattern=.sh` が設定されているので、これを書き換える必要がある
    - デフォルト値は自動生成される `.shellspec` に書かれている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28302/c86fc2ca-a453-2710-116e-dcf43598f528.png)

- `fuga.sh` がレポート対象から外れ、 `src/hoge` がレポート対象に追加されている

# 特別な環境変数

プロジェクトディレクトリなど、いくつかのメタ情報的なものを環境変数から参照できるようになっている。
全容は以下に記載されている。

[Special environment Variables | shellspec/references.md at master · shellspec/shellspec · GitHub](https://github.com/shellspec/shellspec/blob/master/docs/references.md#special-environment-variables)

ここでは、比較的よく使いそうな気がするものだけピックアップ。

|変数名|説明|
|---|---|
|`SHELLSPEC_PROJECT_ROOT`|プロジェクトルート|
|`SHELLSPEC_SPECFILE`|現在実行中のテスト仕様ファイルのパス|
|`SHELLSPEC_SPEC_NO`|現在実行中のテスト仕様ファイルの番号|
|`SHELLSPEC_GROUP_ID`|現在の example グループブロックの ID (例: `1-2`)|
|`SHELLSPEC_EXAMPLE_ID`|現在の example ブロックの ID (例: `1-2-3`)|
|`SHELLSPEC_EXAMPLE_NO`|現在の example ブロックの連番|


# 参考
- [GitHub - shellspec/shellspec: A full-featured BDD unit testing framework for bash, ksh, zsh, dash and all POSIX shells](https://github.com/shellspec/shellspec)