ShellScript開発のCI導入に使えそうなツールまとめ（CI設定済サンプルあり）

Shell Scriptの開発に使えそうなCI構成についてまとめました:point_up:

次のツールを使います。

* コンポーネントテスト(ユニットテスト)：https://github.com/sstephenson/bats
* 静的解析：https://github.com/koalaman/shellcheck
* CircleCI

CI設定込みのサンプルコードはこちら -> https://github.com/NA070/sh-ci-sample


# bats
[bats](https://github.com/sstephenson/bats)はshellスクリプトやコマンドラインツール用のテスティングフレームワークです。

## インストール
https://github.com/bats-core/bats-core#installation を参照してください。

## 使い方
### @test
テストケースは@test〜で記述を開始します。

```bash:test/sample.bats
#!/usr/bin/env bats

@test "addition using bc" {
  result="$(echo 2+2 | bc)"
  [ "$result" -eq 4 ]
}
```
ディレクトリまたはファイル名指定で実行します。

```bash
bats test/ # or bats test/sample.bats
 ✓ addition using bc

1 tests, 0 failures
```


### [run](https://github.com/sstephenson/bats#run-test-other-commands)
>Many Bats tests need to run a command and then make assertions about its exit status and output. Bats includes a  `run`helper that invokes its arguments as a command, saves the exit status and output into special global variables, and then returns with a  `0`  status code so you can continue to make assertions in your test case.

> 多くのBatsテストでは、コマンドを実行して、その終了ステータスと出力についてアサーションを行う必要があります。 Batsには、その引数をコマンドとして呼び出し、終了状態と出力を特別なグローバル変数に保存した後、0のステータスコードを返す実行ヘルパーが含まれているので、テストケースでアサーションを続けることができます。 ~ google翻訳 ~

runでコマンドを実行すると下記の変数に値がセットされます。

 - $status : 終了ステータスコード
 - $output :  標準出力・標準エラー出力をつないだもの
 - $lines：出力を配列で格納。参照が容易

これらの変数を検証に利用できます。

```bash
#!/usr/bin/env bats

@test "invoking ls with a nonexistent file prints an error" {
  run ls hoge
  [ "$status" -eq 1 ]
  [ "$output" = "ls: hoge: No such file or directory" ]
}

@test "invoking mkdir without arguments prints usage" {
  run mkdir
  [ "$status" -eq 64 ]
  [ "${lines[0]}" = "usage: mkdir [-pv] [-m mode] directory ..." ]
}
```

### [load](https://github.com/sstephenson/bats#load-share-common-code)

環境変数やテストフィクスチャをbatsのテストコード以外のファイルから読み込みます。

```bash:test/test_helper.bash
SUCCESS_CODE=0
```
```bash:test/sample.bats
#!/usr/bin/env bats

load test_helper

@test "assert with global variable" {
  run ls -la
  [ "$status" -eq "$SUCCESS_CODE" ]
}
```

### setup / teardown
各ケースの前後に行う共通処理を定義します。

```bash
#!/usr/bin/env bats

setup(){
  mkdir test/hoge
  touch test/hoge/fuga.txt
}
teardown(){
  rm -rf test/hoge
}
```

# shellcheck
[shellcheck](https://github.com/koalaman/shellcheck)はShellスクリプト用のLintツールです。

## インストール
https://github.com/koalaman/shellcheck#installing を参照してください。

##  実行方法

```bash:lib/sample.sh
ls -ltra
```

```bash
shellcheck lib/sample.sh

In lib/sample.sh line 1:
ls -ltra
^------^ SC2148: Tips depend on target shell and yours is unknown. Add a shebang.

For more information:
  https://www.shellcheck.net/wiki/SC2148 -- Tips depend on target shell and y...
```

## 悪いコード例
下記の内容に該当するとチェックNGになります。
https://github.com/koalaman/shellcheck#gallery-of-bad-code

## おまけ
Editorで自動チェックするようにしておくとFBループが早くなるので効率的になると思います。

> https://github.com/koalaman/shellcheck#in-your-editor
-   Vim, through  [ALE](https://github.com/w0rp/ale),  [Neomake](https://github.com/neomake/neomake), or  [Syntastic](https://github.com/scrooloose/syntastic)
-   Emacs, through  [Flycheck](https://github.com/flycheck/flycheck)  or  [Flymake](https://github.com/federicotdn/flymake-shellcheck)
-   Sublime, through  [SublimeLinter](https://github.com/SublimeLinter/SublimeLinter-shellcheck)
-   Atom, through  [Linter](https://github.com/AtomLinter/linter-shellcheck)
-   VSCode, through  [vscode-shellcheck](https://github.com/timonwong/vscode-shellcheck)


# CI環境構築
circle ciで実行します。
それぞれdocker imageが用意されているので簡単です。

```yml:.circleci/config.yml
version: 2
jobs:
  component-testing:
    docker:
      - image: bats/bats:latest
    steps:
      - checkout
      - run: bats ./test/
  lint:
    docker:
      - image: koalaman/shellcheck-alpine
        # Dockerfileのentrypoint実行によって
        # circleci上ではうまくビルドできないので空文字で上書きます
        entrypoint: '' 
    steps:
      - checkout
      - run: shellcheck ./lib/*

workflows:
  version: 2
  component-test-and-lint:
    jobs:
      - component-testing
      - lint
```

これでpushするたびに自動実行されるようになります。

# Reference
* https://github.com/sstephenson/bats
* https://github.com/koalaman/shellcheck
* https://kakakakakku.hatenablog.com/entry/2018/05/14/000633 
* https://qiita.com/5t111111/items/c4a382c7dd896c353d03