Serverspecの効果的活用に向けたTips
テストコード内で利用する変数を別ファイルで管理
第2回の記事中で紹介したテストコードの例では、テストコードの中で変数を定義していました。しかし各テストコードの中で変数を定義していると、場合によっては同じ変数を複数のファイルの中に記載することになり、メンテナンスが煩雑になります。そこで、変数を別ファイルに切り出し、各テスト対象サーバ共通の変数設定と、あるサーバ固有の変数設定に分けて管理できるようにします。
- YAMLファイルに変数に格納したい情報を記載
- spec_helper.rbを変更し変数情報をセット
- テストコード内で変数を展開
YAMLファイルに変数に格納したい情報を記載
先ほど作成したhosts.ymlに、変数として格納したい情報を記載します。ここでは、第2回で紹介したMySQLのテストコード中で利用したデータベースへの接続ユーザ名とパスワードを別ファイルに切り出して管理する場合の例です。
server-01: :roles: - base - web server-02: :roles: - base - db :db_user: root :db_password: password
DBサーバ(server-02)に対してのみdb_user、db_passwordの値を記述しています。
spec_helper.rbを変更し変数情報をセット
次に、hosts.ymlに記載した変数情報をServerspecのテストコード中で扱えるように、変数に登録します。ServerspecではSpecInfra::Helper::Propertiesというモジュールにset_propertyというメソッドが定義されています。このset_propertyというメソッドを利用すると、テストコード中で利用できる変数情報を登録できます。
変更後のspec_helper.rbは以下になります。
require 'serverspec' require 'pathname' require 'net/ssh' require 'yaml' # YAMLファイルを処理するライブラリの読込み include SpecInfra::Helper::Ssh include SpecInfra::Helper::DetectOS include SpecInfra::Helper::Properties # 変数処理用Propertiesモジュールの読込み properties = YAML.load_file('hosts.yml') # YAMLファイル読込み RSpec.configure do |c| if ENV['ASK_SUDO_PASSWORD'] require 'highline/import' c.sudo_password = ask("Enter sudo password: ") { |q| q.echo = false } else c.sudo_password = ENV['SUDO_PASSWORD'] end c.host = ENV['TARGET_HOST'] set_property properties[c.host] # テスト実行対象ホストの設定配下にあるProperty情報をセット options = Net::SSH::Config.for(c.host) user = options[:user] || Etc.getlogin c.ssh = Net::SSH.start(c.host, user, options) c.os = backend.check_os c.request_pty = true end
全サーバで共通の情報やweb、dbなど役割に対して共通の情報を管理したい場合には、以下のようなYAMLファイル(properties.yml)を1つ追加し、spec_helper.rbを変更することで対応できます。
base: :dns_server: 172.19.1.34 db: :db_port: 3306 web: :web_port: 80
require 'serverspec' require 'pathname' require 'net/ssh' require 'yaml' include SpecInfra::Helper::Ssh include SpecInfra::Helper::DetectOS include SpecInfra::Helper::Properties host_properties = YAML.load_file('hosts.yml') common_properties = YAML.load_file('properties.yml') RSpec.configure do |c| if ENV['ASK_SUDO_PASSWORD'] require 'highline/import' c.sudo_password = ask("Enter sudo password: ") { |q| q.echo = false } else c.sudo_password = ENV['SUDO_PASSWORD'] end c.host = ENV['TARGET_HOST'] properties = host_properties[c.host] properties[:roles].each do |r| properties = common_properties[r].merge(host_properties[c.host]) if common_properties[r] end set_property properties options = Net::SSH::Config.for(c.host) user = options[:user] || Etc.getlogin c.ssh = Net::SSH.start(c.host, user, options) c.os = backend.check_os c.request_pty = true end
変数を定義しているファイルが複数(上記の例では、hosts.ymlとproperties.yml)に分けられていても、set_propertyメソッドは各テスト実行対象ホストに対して1回のみ実行するという点に注意しましょう。これはset_propertyメソッドを実行すると、内部で一旦変数情報が初期化された後、引数で渡した変数群をセットする仕様だからです。そのためset_propertyを複数回呼び出すと、最後に実行したset_propertyのみが有効な状態になってしまいます。
テストコード内で変数を展開
spec_helper.rbで設定した値をテストコード内で呼び出すには、propertyという名前の変数を利用します。
describe command("mysqlshow -u#{property[:db_user]}") do it { should return_stdout /zabbix/ } end
テスト実行サーバ内の情報をもとにした分岐処理
より詳細に稼働状況を確認したいケースなどでは、サーバ内部の情報をもとに処理を振り分けたいといった場合もあるかと思います。第2回の記事中では、OSの情報をもとに分岐処理する方法を紹介しました。それ以外にも、任意のコマンドの実行結果をもとに処理を分岐させることも可能です。
テストコードの記述例
テスト実行対象サーバ内にあるファイルが存在していればテストAを、なければテストBを実行する例を紹介します。サーバ内部で処理を実行するにはbackend.run_commandを実行します。この結果はSpecInfra::CommandResultオブジェクトとして、以下の値を取得できます。
- 標準出力の結果
- 標準エラー出力の結果
- 終了ステータスの結果
- シグナルの受信結果
result = backend.run_command('ls <ファイルパス>') if result[:exit_status] == 0 テストAのコード elsif テストBのコード end