読者です 読者をやめる 読者になる 読者になる

TT-Runnerの記録2:ベータ版

計算機 手慰み Python Bash

前回:


GitHub - fjkz/tt-runner: A Testing Scripts Runner

結合テスト用のテストスクリプトを実行するためのツールを作っている。

シェルスクリプトベタ書きのテスト群を整理された状態にまとめられるものが欲しいという、当初の目的は達成できたように思う。1000 LOC にも満たない小さなプログラムだが、欲しいものの仕様にたどり着くのに時間を要してしまった。

英語のドキュメントは下手なので伝わらないだろうから、日本語のドキュメントも書いた。

https://github.com/fjkz/tt-runner/tree/master/doc/jp

しばらく寝かせて、バグ修正だとか異常系の作り込みだとかをしたら、1.0版としたい。

ディレクトリ構造のスキーマ

計算機 考察

私はファイルシステムとかブロックストレージとかには少しだけ詳しいと思うが、現実に興味があるのはもう少し上の階層だ。RDB でいうとどのようにスキーマを設計すべきかという階層の話に興味がある。昔に書いた以下の記事でいうと「シンタックス層」が関心の対象だ。

データにもOSI参照モデルがある - 超ウィザード級ハッカーのたのしみ

なぜかというと、個人的な動機だが、私は情報を集めるのは好きなのだが、整理するのはあまり得意ではない。例えば本やメモであればフラットな場所、つまり一個の本棚やノート、にすべて放り込んで、目視探索か脳内検索をしてしまっている。そういうのは他人には扱えない。そのためには、体系的に情報を整理する必要があるので、どうやったらそれができるのかということに苦心している。

得意な人は苦労せずに整理してしまっているのだが、それが良い出来かというと疑問で、正直なところ勘で整理されたものも他の人には理解できない。頭の良い人が工夫して整理したもので、はじめてまともに整理されている状態と言える思える。

どのようにしたら情報をうまいこと整理できるのかという問題については、人類は長いこと悩んでいる。どのへんに学問性があるのか私には分からないのだけれども、図書館学というものもある。

近年では、情報はデータと言われてバイト列として計算機に収められている場合が多い。計算機上のデータは、紙面の情報と異なって物理的な制約がなくなった。そのためにデータの量が爆発的に増えてしまった。PC に収められるドキュメントの量では整理しないと人間が追いつけない。目視探索や脳内検索の限界を超えてしまっている。

計算機上にデータを保存する手段はいろいろある:RDBファイルシステムだ。上で引用した昔の記事でいうところの「ウツワ層」に相当するものだ。ウツワ層は多くの人が研究してきて、もうだいたい完成しているように私は思っている。

しかし、その上のシンタックス層――つまりスキーマのこと――に関してはまだ未完成ではないかと思う。

RDB に関しては、何が良い設計で何が悪い設計なのかは概ね共通理解となっている。RDBスキーマを定義しないと使えないし、それを設計するための専門職がいる。彼らのおかげで、デザインパターンというのか、良い設計をするための知見が蓄積されている。

しかし、ファイルシステムに関しては未だ全くダメだ。

ファイルシステムRDB より歴史が長いにも関わらず、いや歴史が長いゆえか、ファイルシステム上のディレクトリ構成はたいていグチャグチャだ。UNIX OS のディレクトリ構成に関しては、Filesystem Hierarchy Standard といって概ね統一されている。あるいは、JavaソースコードApache Maven で標準のディレクトリ構成というのが定義されて統一されつつある。だが、そういうのがないデータはグチャグチャにならざるを得ない。PC 上のデータが整理されていないのは、実害は少ないが、共有フォルダが整理されていないのは目に見えた害がある。

どうしてこういうことになるかというと、ファイルシステムにはスキーマが定義されないからだ。

Filesystem Hierarchy Standard や Maven 標準ディレクトリ構成は一種のスキーマである。賢い人が設計しているので、なんとなく作ったものより使いやすい。また、規格なのでそれに合わせることで、他のプログラムとの親和性が向上する。

しかし、設計とかせずになんとなくディレクトリを作っちゃうことも可能で、ほとんどの場合はそうなっている。自由であるとも言えるのだが、自由にできたところで、間違った選択肢が増えるだけだ。正しい選択肢は少ないが、間違った選択肢は無数にある。最初にディレクトリを作ったときには、なにかしらの意図があるものだが、その意図も曖昧模糊としているし、時間が経過するとその意図を無視してファイルが配置されたり、新たなディレクトリが作られたりする。結果、整理されていないデータが氾濫することとなる。

ファイルシステムの構成にもスキーマを定義できたらよいと思う。スキーマが定義できれば、いつの間にかディレクトリ構成がグチャグチャになることが防げるだろう。

テストスクリプトディレクトリ構成を規定するツール「TT-Runner」というのを作っている*1。作ってから気づいたが、これもファイルシステムの構成のスキーマを定義しようとしているのだ。

これを敷衍してもう少し一般的な形でディレクトリ構成を定義できるようにしたい。XML にはスキーマを定義できる XSD というものがある。このようなイメージでディレクトリ構成のスキーマを Well-Defined に規定できる手段が欲しい。ディレクトリ構成には慣例があって、空気読めや――みたいに現状はなっている。今は人の手に頼るかたちになっているが、厳密に定義することができれば、機械によって整理することも可能だ。賢い人がスキーマを定義すれば、いろんな場所で使いまわせるようになるだろう。

以上のようなことを思いついたが、どうやって実現したらいいのか今は答えがない。ちょっと考えてみようと思う。

TT-Runner の記録 1

手慰み 計算機 Python

TT-Runner: テストスクリプトのディレクトリ構造フレームワーク - 超ウィザード級ハッカーのたのしみ

GitHub - fjkz/tt-runner: A directory structure framework and a runner for testing scripts.

TT-Runner (Test scripTs Runner あるいは Tree Tests Runner) というのをシコシコと作っています。*1

以下、作業記録。

スクリプトのある場所に cd するようにした。

シェルスクリプトはカレントディレクトリ、つまりどこの場所からスクリプトを実行したのか、にシビアである。スクリプトが存在するディレクトリから実行することを期待するのが多いかと思うので、スクリプトを実行する前にそのディレクトリがあるディレクトリに cd するようにした。

カレントディレクトリを変えないオプション (--no-chdir) も念のために用意した。

--stop-on-failure, --skip-all オプションの追加

一つでも operation が失敗したら、残りはすべて skip するオブション --stop-on-failure と、すべての operation を skip するオプション --skip-all を追加した(--dry-run の方がわかりやすいかもしれない)。主にテストスクリプトデバッグ用です。実行にある程度時間がかかるようなテストを想定しているので、こういうオプションは欲しい。

スクリプトの出力をファイルに保存できるようにした

-o オプションで指定されたディレクトリに各スクリプトの結果を保存するようにした。ok/ngだけですべてが分かればいいが、そうはいかないので、ログが残せないと不便だ。result.txt に ok/ng のテスト結果の出力も残す。一応、TAP っぽく出力はしているが、今の出力は本当に TAP として正しいのか。

今後すること

ファイルに結果を保存できるになったら、コンソールへの出力は再考の余地がある。一つ一つのオペレーションが重いことを想定しているので、今のままでは見づらい。何%終わっていて、経過時間はどのくらいかで、失敗があるのかは、コンソールから簡便に確認したい。

JUnit XML も出力できるようにしたい。

テストツールなのにテストがないので、書く必要がある。

ドキュメントは一応書いてみたが、読めないのでまともにしたい。添削してくれる人が欲しい。

可能であれば、TAPを介して、他のテスティングフレームワークと連携できるようにしたいが、それをし始めると大変だろうか。

*1:陽の目は見ないがやる。これが「でも、やるんだよ」ってやつか?

CAP定理について

計算機 ノート 考察

CAP定理という分散ストレージシステムの設計において非常に重要な定理がある。まだ、以下の元の論文を読んでいないので、正確な理解かどうかは保証できないが、理解している範囲で考えることを記す。

https://www.comp.nus.edu.sg/~gilbert/pubs/BrewersConjecture-SigAct.pdf

CAP定理によると、分散システムでは以下の3つを同時に満たすことはできない:

  • Consistency 一貫性、
  • Availability 可用性、
  • Partition tolerance 分断耐性。

それぞれ、

  • all nodes see the same data at the same time,
  • every request receives a response about whether it succeeded or failed,
  • the system continues to operate despite arbitrary partitioning due to network failures

という意味である。*1

分散ストレージシステムはいろいろあるのだけれど、C・A・Pのいずれかを捨てるという選択を行っている。この選択がいわゆる設計思想というものなので、どの選択をしているかでシステムの特徴が分かる。

システムとして、Availability はほとんどの場合必須なので、

  • Consistency + Availability (CA)、 か
  • Partition tolerance + Availavility (PA)

の組み合わせが選択される。Consistency + Partition tolerance を選択している例は私は知らない。*2CA あるいは PA が選択されているシステムとしては以下が挙げられる:

列挙すると分かるが、CA は一貫性をとっているのだから当然だが、厳密でお固いものが並んでいる。一方で、PA の方は柔らかい印象だ。耐障害性・ロバスト性は PA の方が強い。柔らかいから、逆に壊れにくいという感じでしょうか。

どちらを選択するかは用途によるとしか言いようがないが、個人的には PA が好きだ。主観だが、PA の選択の方が設計思想としては新しい。一貫性を保証するというのも難しいので、初期はいかにして一貫性を担保するかを頑張ってきた。しかし、一貫性の問題が解決されたら、実は完全な一貫性はオーバスペックなのでは?――となってきた。一時的に不整合が起こるかもしれないが、それもほとんどないし、仮に不整合がおきても、時間がたてば辻褄が合うから別にいいじゃん――というある意味で割り切った態度を PA を選択したシステムはとることが多い。これを、結果整合性という。

例えば、SMB(Windowsの共有フォルダのこと)*4はネットワークに繋がっていなければ使えないし、遅い。しかし、現実同じファイルを複数人が同時に編集することはあまりしないので、常に最新である必要もない。その用途だと Windows の共有フォルダは非常に使いにくい。MS Office は整合性を保つために編集中のファイルをロックする機能があるが、あれはイライラする。競合が少ないなら、ちょっとぐらい不整合があってもいいじゃんと、Dropbox は割り切っており、この割り切りは合理的だと思う。個人用のネットワークドライブなんかは、私はセンスねえなと思う。

PA が好きなのは、どこか割り切ったところが入っている点で、言葉として変だが、何を割り切るかは割り切れないものだ。完全に Art の領域だ。どこを割り切ったらいいのか、つまり何を捨てて何を取るかということについて、まだ最もよいバランスというのは合意がとれていない。工夫の余地がまだ残っていて、興味深い。

*1:英語版 Wikipedia から引用。https://en.wikipedia.org/wiki/CAP_theorem。日本語版の記事はどうも間違っている気がする。

*2:もちろん、C・A・Pのうち一つだけしか満たしていないものもある。Consistency だけの場合が多いかな? 例えば、初期の HDFS は Consistency しか満たしていなかった。なお、C・A・P以外にもスケーラビリティという別の話があって、Consistency とスケーラビリティは相反するところがある。HDFS は Consistency を満たしつつ、スケーラビリティも持たせるために、非常に大胆な割り切りをしている。

*3:例えば日米間の光ファイバーがサメに食われても、日本のサイトは覗けるのでインターネット全体として止まるわけではないという意味です。一つのサイトと多数のクライアントという風に見れば、CA です。インターネットの強力な耐障害性は PA を選択しているといった「ゆるさ」が理由だが、このネタだけでいくらでも語れるので踏み込まない。気が向けばいつか書く。

*4:クライアントキャッシュがないことを前提としている。SMBのクライアントキャッシュの取り扱いってどうなっているのかよく知らない。

TT-Runner: テストスクリプトのディレクトリ構造フレームワーク

計算機 手慰み Python

以前書いたテストツールがある程度できたので、公開する。TT-Runner と名付ける。

GitHub - fjkz/tt-runner: A runner for testing scripts

結合テスト以降のユニットテストフレームワークがうまいこと使えなくて、スクリプトをだらだらと書いているような場合を想定したツールです。テストスクリプトの中身はシステムによって大きく異なるので、ディレクトリ構造の方を規定してしまおうという考えです。

以下のような、テストスクリプト群があったとする。

sample/test-before-after
├── after1.sh
├── after2.sh
├── before1.sh
├── before2.sh
├── test1.sh
└── test2.sh

これを実行すると、JUnit風に実行してくれるという、単純なツールです。

$ ./bin/tt-runner.py sample/test-before-after 2>/dev/null
1..10
ok 1 before1.sh # 0.0 sec
ok 2 before2.sh # 0.0 sec
ok 3 test1.sh # 0.0 sec
ok 4 after2.sh # 0.0 sec
ok 5 after1.sh # 0.0 sec
ok 6 before1.sh # 0.0 sec
ok 7 before2.sh # 0.0 sec
ok 8 test2.sh # 0.0 sec
ok 9 after2.sh # 0.0 sec
ok 10 after1.sh # 0.0 sec

実行ファイルをディレクトリにしてテストをネストさせることもできて、いくつかの既存のテストスイートをまとめて実行したりすることを想定している。

用途が限定的で明確だから、もしかしたら他の人にとっても使いやすくなるかもしれない。少なくともベタ書きシェルスクリプトが増え続けるよりはマシになるだろう。

自分で使ってみて、使いにくいところがあったら、ちょこちょこと直していく予定である。


次回: TT-Runner の記録 1 - 超ウィザード級ハッカーのたのしみ

コード型ログ(5) Bashで自分のディレクトリを知る

Bash 計算機 ノート

シェルスクリプトで自分のディレクトリを知りたいことがあります。

. ./functions.sh

例えば、上のように他のシェルスクリプトを読み込みたいときに相対パスを使うのはよくないときがあります。なぜならば、./の場所はシェルスクリプトを実行するカレントディレクトリであって、シェルスクリプトのファイルがある場所ではないからです。シェルスクリプトは、どのディレクトリから実行しても動くようにしたいものです。

シェルスクリプトのファイルが存在するディレクトリを知るには以下のようにします。

MYNAME="${BASH_SOURCE:-$0}"
MYDIR=$(cd -P -- $(dirname -- "${MYNAME}"); pwd)

${MYNAME}には今のファイルの相対パスが入っています。

${BASH_SOURCE:-$0}となっているのは、$0にはBash以外では今のファイルの名が入るのですが、Bashだとsourceもしくは.でファイルを呼んだ実行ファイルの名前が入るからです。Bashには今のファイル名を表す変数BASH_SOURCEというのが定義されているので、BASH_SOURCEが定義されている場合にはそれを使い、BASH_SOURCEが定義されていない、つまりBash以外のシェルの場合は$0を使います。

"${A:-a}"というのは変数Aが定義されているときは${A}を返し、そうでないときはaを返すという意味です。なお、:-のセミコロンは省略して${A-a}でも可能です。*1

${MYDIR}には今のファイルのディレクトリの絶対パスが入っています。

dirname -- "${MYNAME}ディレクトリ名を取得します。--はファイル名が-で始まっているような場合にコマンドラインオプションとしてみなされないようにするためです。慣例として--以降の引数はオプションとしてみなされません。

しかし、このままでは相対パスかもしれません。なので、絶対パスが欲しいときには、$(cd -P -- ${RELATIVE_DIR}; pwd)として、絶対パスに変化します。-P${RELATIVE_DIR}シンボリックリンクが含まれていたときにそれを解くオプションです。

. ${MYDIR}/functions.sh

とすれば、どこから実行ファイルを呼んでもfunctions.shを見つけることができます。


追記

上では、実行ファイルがシンボリックリンクとなっている場合は、実体のあるディレクトリは分からない。その場合には、

MYNAME=$(readlink -f "${BASH_SOURCE:-$0}")

とする。

*1:ややこしいので個人的には"${A:-a}"で統一してほしいです。

構造化されたテストスクリプト群の実行ツール

計算機 Python 手慰み

テストコードってどんどん増えていく。だらだらスクリプトを書くのは簡単だが、すぐに収拾が付かなくなり、経済的に耐えられないレベルで混乱してくる。何のテストをしているのかももちろんわからないが、動かし方も分からないし、どうなったらpassなのかも分からないような、テストスイートが乱立して、もう全部すてればと思ってしまうほどになる。

テストの自動化を整備するなら、アーキテクチャが必要だ。ユニットテストは、JUnitやSpockやBatsなどの便利なフレームワークがあるので、散らかりっぷりが抑えられる。フレームワークの良さは、それに従えば勝手に整理されるということだ。

システムテストになってくると、システムによってテストも大きく異なってくるので、テスティングフレームワークと呼べるほどに親切なフレームワークを作るのは難しい。だが、既存のテスティングフレームワークの思想は参考にできることが多い:

  • テストケースで共通化できる処理は、Before / After でまとめる;
  • テストケースは独立である;
  • テストは繰り返し実行可能である。

乱立して増え続けるテスト用のスクリプト群もこれを守るように構造化されれば、経済的なメリットは大きいだろう。

そこで、構造化されたテストスクリプト群を実行するツールを作っている。構造化されていることを期待して実行するので、構造化されていなければテストを実行できず、構造化された状態が保たれるはず――という寸法だ。

test
├── after
│   └── run_destroy.sh
├── before
│   └── run_deploy.sh
├── testsuite1
│   ├── setup_db.sh
│   ├── test1.sh
│   └── test2.sh
└── testsuite2
    ├── post-run.sh
    ├── pre-run.sh
    ├── run1.sh
    └── run2.sh

上のようなディレクトリ構造のテストがあったときに、テストを実行すると以下のようにディレクトリ構造にしたがってスクリプトが実行される。

1..14
ok 1 before/run_deploy.sh # 0.0 sec
ok 2 testsuite1/setup_db.sh # 0.0 sec
ok 3 testsuite1/test1.sh # 0.0 sec
ok 4 testsuite1/setup_db.sh # 0.0 sec
ok 5 testsuite1/test2.sh # 0.0 sec
ok 6 after/run_destroy.sh # 0.0 sec
ok 7 before/run_deploy.sh # 0.0 sec
ok 8 testsuite2/pre-run.sh # 0.0 sec
ok 9 testsuite2/run1.sh # 0.0 sec
ok 10 testsuite2/post-run.sh # 0.0 sec
ok 11 testsuite2/pre-run.sh # 0.0 sec
ok 12 testsuite2/run2.sh # 0.0 sec
ok 13 testsuite2/post-run.sh # 0.0 sec
ok 14 after/run_destroy.sh # 0.0 sec

run, testの前後に同じ階層のbefore/after, pre/post, setup/teardownが実行されていく。

JUnitとちがって、before/afterも1ケースとしている。これはスクリプトファイルを独立して扱いたいという意図である。

それぞれのスクリプトは、シェルスクリプトをベタ書きしても良いし、そこから Ansible や他のテスティングフレームワークなどの別のツールを呼んだりする。

Jenkinsとの組み合わせを意図してTAPで結果を出力するようにしている。おそらく世間では Jenkins のビルドプロジェクトの設定を工夫して、今回したいようなことを実現しているのだろうが、Jenkins と業務が密結合になってしまう。また、Jenkinsを駆使するならば、シェルスクリプトベタ書きと変わらない。間にこういうのをかませたら便利かと思っている。

ある程度できたら公開したい。単純なものは逆に難しかったりする。本当に、JUnit は良く出来ている。


追記 2016-07-24

できた。

TT-Runner: テストスクリプトのディレクトリ構造フレームワーク - 超ウィザード級ハッカーのたのしみ