L is Bエンジニアブログ

ビジネス用メッセンジャーdirectのエンジニアによるブログ

LisBエンジニアブログ

ビジネスチャットdirectのエンジニアブログ

組み合わせテストケース生成ツール・CIT-BACHを紹介します。

QAのT.E. ( @KondavalasaR )です。

先日デザイナーの人が投稿しましたが、今回はQAが業務で使っているツールについて紹介しようと思います。 QAの仕事はテストの計画から実行まで色々ありますが、今回はテストケースの設計で使う組み合せテストのツールの一つ、CIT-BACHを紹介したいと思います。

f:id:curry-tester:20180601153407j:plain

組み合わせテストとは?

テストをする時間は有限で、且つその中で不具合を効率よく見つけるために、Pairwise法のような手法が生み出され、また、Pairwise法による組み合わせを生成するツールが生み出されました。 CIT-BACHもその一つです。

CIT-BACHとは?

CIT-BACHはよいツールなのですが、ユーザーがあまり外に出てこないのか、更新も2016年で止まってしまっていますが、この記事がCIT-BACHの普及に役立つことを祈ります。

CIT-BACHは制約(または禁則)と呼ばれる、「現実にはありえない組み合わせを排除して組み合わせを作成する仕組み」をより高速に、あるいは少ないリソースで処理できることを特色としており、この点から弊社ではCIT-BACHを採用しました。

詳しくは公式の記事をどうぞ。

Tatsuhiro Tsuchiya's Weblog : 組合せテスト用テストケース作成ツール CIT-BACH

ではさっそく作ってみましょう。 とはいえ、実際に業務で使ったものをここで出すわけにもいかないので、ここでは私の大好きなカレーの組み合わせを題材に取ろうと思います。

f:id:curry-tester:20180601153654j:plain

さて、インドやネパールの料理にはベジとノンベジというジャンルがあります。 簡単に言うと、ベジタリアンが食べるのがベジで、それ以外の人はノンベジ(肉や魚を含みます)を選びます。

その場合、カレーの具を選ぶ際に、ベジを選んだのに、羊肉や鶏肉が選べるようなことはありえないので、組み合わせを作る時には、このような組み合わせが作られないように指定する必要があり、これを表現するための仕組みが制約です。

では実際に書いてみましょう。

制約式はおおまかには2つありますが、ざっと外観を紹介します。 一つ目に、インド人とネパール人で得意な料理は異なるので、作る料理が分かれます。 ナンはインド人シェフの方が、パーパルなどはネパール人シェフの方がおいしいのです。 二つ目に、主食にご飯を選んだ場合、ターメリックとサフランライスを選べるものとします。

シェフ (インド人 ネパール人)
ジャンル (ノンベジ ベジ)
主な具 (羊肉 鶏肉 魚肉 豆)
辛さ (超辛 激辛 大辛)
主食 (ジャポニカ米 インディカ米 ナン プリ パーパル)
ご飯 (サフラン ターメリック ごはんなし)
食後のチャイ (1杯 2杯 3杯)
デザート (ラッシー マンゴーラッシー バナナラッシー)

# Vege people never accepts beef, pork, chicken, and fish
(if (== [ジャンル] ベジ)
   (and 
        (<> [主な具] 羊肉)  
        (<> [主な具] 鶏肉)
        (<> [主な具] 魚肉)))

# An Indian cheff cannot make Nepalese dishes
(if (== [シェフ] インド人) 
   (and (<> [主食] プリ)  
        (<> [主食] パーパル)))

# So do Nepalese
(if (== [シェフ] ネパール人) 
   (<> [主食] ナン))

(if (== [シェフ] ネパール人) 
   (<> [主食] ナン))

# if choosing rice, you can choose turmeric or saffron
(if (or 
      (== [主食] ジャポニカ米)
      (== [主食] インディカ米))
   (or (== [ご飯] ターメリック)  
        (== [ご飯] サフラン)))

(if (and
      (<> [主食] ジャポニカ米)
      (<> [主食] インディカ米))
   (and (<> [ご飯] ターメリック)  
        (<> [ご飯] サフラン)))

上記の内容をinput_file.txtに保存して、下記のように組み合わせをoutput_file.txtに出力させます。

java -jar cit-bach.jar -i input_file.txt -o output_file.txt

完成した組み合わせテスト

output_file.txtの中身は下記のようになります。 こちらの内容をテスターが実際に作業しやすい形に整形する作業が残っていますが、それは今回は省略します。

#SUCCESS,24924,i,input_file.txt,s,,o,,c,2,random,24924,repeat,1
シェフ,ジャンル,主な具,辛さ,主食,ご飯,食後のチャイ,デザート
ネパール人,ノンベジ,羊肉,超辛,ジャポニカ米,サフラン,1杯,ラッシー
インド人,ベジ,豆,激辛,インディカ米,サフラン,2杯,マンゴーラッシー
インド人,ノンベジ,鶏肉,大辛,ナン,ごはんなし,3杯,バナナラッシー
ネパール人,ベジ,豆,超辛,インディカ米,ターメリック,3杯,バナナラッシー
ネパール人,ノンベジ,魚肉,超辛,プリ,ごはんなし,2杯,マンゴーラッシー
インド人,ノンベジ,魚肉,激辛,ジャポニカ米,ターメリック,1杯,ラッシー
ネパール人,ベジ,豆,大辛,パーパル,ごはんなし,1杯,ラッシー
ネパール人,ノンベジ,羊肉,激辛,パーパル,ごはんなし,3杯,マンゴーラッシー
インド人,ノンベジ,羊肉,大辛,インディカ米,ターメリック,2杯,ラッシー
ネパール人,ノンベジ,鶏肉,大辛,ジャポニカ米,サフラン,2杯,マンゴーラッシー
インド人,ノンベジ,鶏肉,超辛,インディカ米,ターメリック,1杯,マンゴーラッシー
ネパール人,ノンベジ,魚肉,激辛,ジャポニカ米,サフラン,3杯,バナナラッシー
ネパール人,ノンベジ,鶏肉,激辛,プリ,ごはんなし,3杯,ラッシー
ネパール人,ベジ,豆,大辛,プリ,ごはんなし,1杯,バナナラッシー
インド人,ベジ,豆,超辛,ナン,ごはんなし,2杯,ラッシー
ネパール人,ノンベジ,羊肉,超辛,パーパル,ごはんなし,2杯,バナナラッシー
インド人,ノンベジ,魚肉,激辛,ナン,ごはんなし,1杯,マンゴーラッシー
インド人,ベジ,豆,大辛,ジャポニカ米,ターメリック,3杯,バナナラッシー
ネパール人,ノンベジ,魚肉,大辛,インディカ米,サフラン,2杯,マンゴーラッシー
ネパール人,ノンベジ,豆,大辛,ジャポニカ米,サフラン,2杯,バナナラッシー
ネパール人,ノンベジ,鶏肉,激辛,パーパル,ごはんなし,3杯,ラッシー
インド人,ノンベジ,羊肉,大辛,ナン,ごはんなし,2杯,ラッシー
ネパール人,ノンベジ,羊肉,激辛,プリ,ごはんなし,2杯,ラッシー
ネパール人,ノンベジ,魚肉,大辛,パーパル,ごはんなし,1杯,マンゴーラッシー

でも、実際は上記のようには書きません・・・

ただし、私に限っていえば、実際には直接上記のように書くということは稀です。

なぜかというと、実際のテストの現場では、組み合わせを作ったあとに「やっぱりこのパラメーターも欲しい」であったり、仕様にも書いていなかったが実際のコードには存在するような制約が増えたりと、方針が変わったり、非常に複雑なシステムな場合は、制約式自体が非常に長く人の目で全てをチェックするのが困難な組み合わせになることがあります。(自分が経験した最長のものだと制約式だけで500行を越えたことがあります)

また、元となるスプレッドシートを更新して、CIT-BACHも更新するとミスが発生しやすいので、TSVの形式をそのままコピペして貼るようにしました。

以上の理由から、以下の用にRubyのスクリプトの中にExcelなどからコピーしたパラメーターの元データをパースして、その下に制約の式を書き、さらにその下で生成した組み合わせを読みこんで、チェック項目をチェックさせて、人間が目視で確認する手間を減らす、というようなことを行います。

今回は紙面の都合上最後のチェックの部分が非常に簡素ですが、実際の業務で使う際にはもっと長々と書きます。

TSV形式のテキストをパースした上に、Rubyの中にLISPのようなものを書くので、もしかしたら違和感を感じる人もいるかもしれませんが、トライアンドエラーをする作業を続けた結果、この方式に落ち着きましたので、暖かく見守ってください。

いっとき、あまりに同じことを繰り返すのでフレームワークを作ろうとしたことがあったのですが、枠にはめてはみるものの、想定外のテストを作るケースが続いて、更新に時間がかかってしまい本業が進まないので効率的ではないということで今はあきらめています。

require "csv"

input_file = File.new("input_file.txt", "w")

parameters_raw = <<EOS
シェフ インド人    ネパール人         
ジャンル  ノンベジ    ベジ          
主な具 羊肉  鶏肉  魚肉  豆
辛さ    超辛  激辛  大辛      
主食    ジャポニカ米  インディカ米  ナン  プリ  パーパル
ご飯    サフラン    ターメリック  ごはんなし
食後のチャイ    1杯    2杯    3杯        
デザート  ラッシー    マンゴーラッシー    バナナラッシー       
EOS

parameters = []
parameters_raw.split(/[\n\r]/).each_with_index do |vals_raw, p_ix|
  vals = vals_raw.split(/\t/)
  parameters.push({
    :parameter => vals[0],
    :values => (1..(vals.size-1))
      .map{|ix| vals[ix] }
      .delete_if {|e| e.size.zero? }
  })
end

parameters.each do |p|
  input_file.puts "#{p[:parameter]} (#{p[:values].join(" ")})"
end

#restrictions

restrictions_raw = <<EOS

# Vege people never accepts beef, pork, chicken, and fish
(if (== [ジャンル] ベジ)
   (and 
        (<> [主な具] 羊肉)  
        (<> [主な具] 鶏肉)
        (<> [主な具] 魚肉)))

# An Indian cheff cannot make Nepalese dishes
(if (== [シェフ] インド人) 
   (and (<> [主食] プリ)  
        (<> [主食] パーパル)))

# So do Nepalese
(if (== [シェフ] ネパール人) 
   (<> [主食] ナン))

(if (== [シェフ] ネパール人) 
   (<> [主食] ナン))

# if choosing rice, you can choose turmeric or saffron
(if (or 
      (== [主食] ジャポニカ米)
      (== [主食] インディカ米))
   (or (== [ご飯] ターメリック)  
        (== [ご飯] サフラン)))

(if (and
      (<> [主食] ジャポニカ米)
      (<> [主食] インディカ米))
   (and (<> [ご飯] ターメリック)  
        (<> [ご飯] サフラン)))

EOS

input_file.puts restrictions_raw
input_file.close

system("java -jar /usr/local/bin/cit-bach.jar -i input_file.txt -o output_file.txt")

#assertion : not to repeat the same checks
CSV.foreach("output_file.txt", :encoding => "UTF-8") do |r|
  # a sample assertion for combinations
  if (r[1] == "ベジ" && r[4] == "羊肉")
    raise
  end
end

QAのメンバーを募集中です。

弊社では新しく開発チームに加わってくださるメンバーを大募集しています。

https://l-is-b.com/ja/recruit/

上記のURLからお気軽にご応募ください。 テストに情熱を持っている方の参加をお待ちしております。