ソフトウェアエンジニアで、自身のHOMEディレクトリ以下に設置するファイル群(いわゆるdotfiles)をGitで管理している人はそこそこ居ると思う。
ポピュラーなやり方は、GitHubに代表されるGitリポジトリをホストできるサービスで管理し、手元に git clone
して持ってきて setup.sh
や install.sh
のようなスクリプトを実行し、Git管理下のファイル群をHOMEディレクトリにコピーしたりシンボリックリンクを作成する流れである。
長くGitで管理していると、時々ふっと改善したい意欲が湧き上がってきて、かなりの量を書き直した結果、インストールスクリプトの結果が壊れてしまった経験は無いだろうか。これはなかなかにつらい経験である。壊れないことをテストで保証したいと考えるのは、エンジニアの自然な欲求だ。
いわゆるユニットテストというよりはAcceptance Test(受け入れテスト)に相当するものである。この記事ではCLIツールgossを使って、YAMLベースで「インストールスクリプトを実行後になっていて欲しい状態」を宣言的に記述し、テストさせる手法を紹介する。
gossはGoで書かれたCLIツールで、Serverspecに影響を受けたとされている。YAMLベースで宣言的に記述できる点に加えて、Goでコンパイルされているため、Rubyなどの実行環境を必要とせず、バイナリを手元に持ってくればすぐ使える点も便利である。
# 手元の~/bin以下にgossコマンドをダウンロードして配置
$ curl -fsSL https://goss.rocks/install | GOSS_DST=~/bin sh
リファレンス的な内容なgoss manualに譲るが、dotfilesの受け入れテストを書く上で、専ら使うのは file
テストである。
例えば、HOME以下に .bashrc
がシンボリックリンクとして作成されていることを期待するのであれば、次のように記述する。
file:
~/.bashrc:
exists: true
filetype: symlink # cpしているのであれば「symlink」でなく「file」と記述
YAMLを goss.yaml
という名前で保存し、次のようにコマンドラインから実行すると、YAMLでの期待値に対して検査が実行され、結果が出力される。検査結果が期待通りであればコマンドは成功し、YAMLの記述と違っていれば終了コードが 1
となりコマンドは失敗する。
$ ~/bin/goss --gossfile goss.yaml validate --format documentation
File: ~/.bashrc: exists: matches expectation: [true]
File: ~/.bashrc: filetype: matches expectation: ["symlink"]
Total Duration: 0.002s
Count: 2, Failed: 0, Skipped: 0
gossファイルは分割にも対応している。例えば、Gitの設定ファイルだけ集約した git.yaml
をインクルードして検査するなら、 goss.yaml
は次のように記述する。
gossfile:
git.yaml: {}
分割したGitの設定ファイル群に関する受け入れテストでは、新規セットアップしたサーバーなどでやりがちな git config
やり忘れを防止するために file
テストの contains
と組み合わせて、中身も期待するものが配置されているか検査してみよう。例えば、エンジニアBobが自分のコミットログに記述される名前がちゃんと .gitconfig
に入っているか検査したいなら、 git.yaml
側はこう記述できる。
file:
~/.gitconfig:
exists: true
filetype: symlink
contains:
- "name = Bob"
- "email = bob@example.com"
~/.config/git/ignore:
exists: true
filetype: symlink
検査の実行時は、 goss.yaml
のみを指定すれば、インクルード先の git.yaml
で宣言した記述についても一緒に検査される。
$ goss --gossfile goss.yaml validate --format documentation
File: ~/.gitconfig: exists: matches expectation: [true]
File: ~/.gitconfig: filetype: matches expectation: ["symlink"]
File: ~/.gitconfig: contains: patterns expectation: [name = Bob, email = bob@example.com]
File: ~/.config/git/ignore: exists: matches expectation: [true]
File: ~/.config/git/ignore: filetype: matches expectation: ["symlink"]
Total Duration: 0.006s
Count: 6, Failed: 0, Skipped: 0
ここまで来たら、あとはCIツール、いわゆるCI-as-a-Serviceで自動実行させて、面倒なことは機械に任せよう。dotfilesをガシガシと改善したい欲が高まってリファクタリングした結果、もしインストールスクリプトの結果が壊れてしまっても、CIツールが通知してくれる。
例としてTravis CIでの記述を掲載する。CircleCIやGitHub Actionsなど、Travis以外のCIツールを使っている人は適宜で読み替えて欲しい。
language: sh
addons:
apt:
packages:
- git
script:
- ./setup.sh
- "curl -fsSL https://goss.rocks/install | GOSS_DST=~/bin sh"
- "${HOME}/bin/goss --gossfile goss.yaml validate --format documentation"
基本的な流れは同じなので、どのCIツール上で実行させるにしても、次の順になる。
setup.sh
を実行)goss.yaml
で追加セットアップしたいファイルを宣言してテストを失敗させ、グリーンになるよう実装を変えるテスト駆動的なアプローチも可能になる。