いもろぐ

思い立ったら書いていくスタイルで

書籍「実践SeleniumWebDriver」のPageObjectパターンをRSpecでテストコードにしてみた。

このブログは2014/1/1から始めましたが、丸1年経っていったい何ができるようになったんだと。「非開発者がプログラム技術を使ったQAを目指すブログ」って言ってるけどお前ホンキで目指しているのかと。

なんて思いながら新年を迎えてしまいましたが、「Qiitaは「プログラミングに関する知識を記録、共有する最適なサービス」です。」ってことなので、そっちに乗っかってみようかと思いました。あは。

ということで、前回の続きはQiitaで。

書籍「実践SeleniumWebDriver」のPageObjectパターンをRSpecでテストコードにしてみた。 - Qiita

RSpec: beforeで詰まった(itの中から見えるもの見えないもの)

ディレクトリ構成

E2Etest
 └ pages
   └ admin_login_page.rb
 └ spec
   └ test_spec.rb
# admin_login_page.rb
class AdminLoginPage
  def initialize(driver)
    @driver = driver
    @driver.get("https://xxx.wordpress.com/wp-admin/")
  end
~後略~
# test_spec.rb
require 'selenium-webdriver'
Dir[File.expand_path("../../pages/", __FILE__) << '/*.rb'].each do | file |
  require file
end

describe "ログインして書いて編集して削除するシナリオ" do
  @driver = Selenium::WebDriver.for :firefox

  it "正しいID/PWでログインできること" do
    # ログインページでログインする
    @login_page = AdminLoginPage.new(@driver)

    # loginの戻り値は AllPostsPage
    @all_posts_page = @login_page.login

    # 今回はtitleに「投稿」という文字列が含まれているかで比較
    expect(@driver.title).to include "投稿"
  end
end

を実行すると下記のようなエラーになる

:spec sakaimo$ rspec test_spec.rb 
F

Failures:

  1) ログインして書いて編集して削除するシナリオ 正しいID/PWでログインできること
     Failure/Error: @login_page = AdminLoginPage.new(@driver)
     NoMethodError:
       undefined method `get' for nil:NilClass
     # /Users/sakaimo/mydev/selenium/E2Etest/pages/admin_login_page.rb:5:in `initialize'
     # ./test_spec.rb:13:in `new'
     # ./test_spec.rb:13:in `block (2 levels) in <top (required)>'

Finished in 0.00099 seconds (files took 2.96 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./test_spec.rb:10 # ログインして書いて編集して削除するシナリオ 正しいID/PWでログインできること

admin_login_page.rb の initialize に渡される driver が nil みたい。

# test_spec.rb
require 'selenium-webdriver'
Dir[File.expand_path("../../pages/", __FILE__) << '/*.rb'].each do | file |
  require file
end

describe "ログインして書いて編集して削除するシナリオ" do
  before do #←ここ!
    @driver = Selenium::WebDriver.for :firefox
  end

  it "正しいID/PWでログインできること" do
    # ログインページでログインする
    @login_page = AdminLoginPage.new(@driver)

    # loginの戻り値は AllPostsPage
    @all_posts_page = @login_page.login

    # 今回はtitleに「投稿」という文字列が含まれているかで比較
    expect(@driver.title).to include "投稿"
  end
end

で解決しました。

  • 元のコードでは@driverはitの中からは見えない、ってことなのかな。
  • beforeは「exampleの実行前に毎回呼ばれる」からitの中でも見える、ってことなのかな。


ruby: まとめてrequireする

前回↓

書籍「実践SeleniumWebDriver」のPageObjectパターンをRubyでやってみた。 - 非開発者がプログラム技術を使ったQAを目指すブログ

の課題を解決したい

  • ruby 2.0.0p481
  • mac OS10.10.1


E2Etest
  └ pages
    └ add_new_post_page.rb
    └ admin_login_page.rb
    └ all_posts_page.rb
    └ delete_post_page.rb
    └ edit_post_page.rb
  └ test.rb

という構成( 前回と少しディレクトリ構造を変えています)で、test.rbに

#test.rb
require File.expand_path(File.dirname(__FILE__) + '/pages/add_new_post_page')
require File.expand_path(File.dirname(__FILE__) + '/pages/admin_login_page')
require File.expand_path(File.dirname(__FILE__) + '/pages/all_posts_page')
require File.expand_path(File.dirname(__FILE__) + '/pages/edit_post_page')
require File.expand_path(File.dirname(__FILE__) + '/pages/delete_post_page')

って書くのが大変だなという話。


Rubyで指定ディレクトリ以下のファイルを全てrequireする方法 - くろの雑記帳

にまさにドンピシャな内容が書かれていたので参考にさせていただきました。

Dir[File.expand_path("../pages/", __FILE__) << '/*.rb'].each do | file |
  require file
end


以下、自分メモ

File.expand_path('相対Path', __FILE__)
  • "FILE"(現在のファイル。この場合test.rb)を基準として'相対Path'の位置にあるファイルの絶対パスを文字列で返す。
  • ってことは test.rb からみて "../" が "E2Etestディレクトリ" を指すので、結果として
E2Etest/pages/*.rb

のファイルがrequireされる、と。

こちらも参考にさせてもらいました

Rubyを知ろう

RubyのFile.expand_path('相対パス', __FILE__)の意味 - maeharinの日記



書籍「実践SeleniumWebDriver」のPageObjectパターンをRubyでやってみた。

  • 「実践SeleniumWebDriver(初版)」の「9.5 WordPressのエンドトゥエンドのサンプル」がJAVAで書かれてるのでRubyに置き換えようとした。
  • だけど私はJAVARubyもよくわかんないので、会社にあった既存のテストコードを参考にしながら書いてみた。
  • 結果として本に沿ってないし「置き換え」にはなってないっぽい><
  • 正直自信ないので改善点をいただけると嬉しいです。(Rubyの言語仕様も理解不足なので...特にRubyとしての作法とか命名のお約束とか)

前置き

step1:ログインページを操作

  • まずはこんなディレクトリ構成にする。2つのフォルダが同じディレクトリにあるっていうことでスタート。
E2Etest
  └ admin_login_page.rb
  └ test.rb
  • ファイルの中身はこんなん
# admin_logion_page.rb
# ログインページのPageObject
class AdminLoginPage

  def initialize(driver)
    @driver = driver
    @driver.get("https://xxx.wordpress.com/wp-admin/")
  end

  # ページの機能
  def set_email
    email.clear
    email.send_keys("イーメール")
  end

  def set_pwd
    pwd.clear
    pwd.send_keys("パスワード")
  end

  def click_login_btn
    login_button.click
  end


  # ページの要素
  private

  def email
    return @driver.find_element(:id, "user_login")
  end

  def pwd
    return @driver.find_element(:id, "user_pass")
  end

  def login_button
    return @driver.find_element(:id, "wp-submit")
  end
end
  • 本のP.183によると

    • PageObjectにはユーザーに対するサービスを提供するのであって、ユーザーのアクションを提供するのではない
  • 多分上記のadmin_login_page.rbは「サービス」ではなく「ユーザーのアクション」が書かれてるんだと思う。

  • で、test.rbはこんな感じ。
# test.rb
require 'selenium-webdriver'
require File.expand_path(File.dirname(__FILE__) + '/admin_login_page')

driver = Selenium::WebDriver.for :firefox

# ログインページにログインする
admin_login_page = AdminLoginPage.new(driver)
admin_login_page.set_email
admin_login_page.set_pwd
admin_login_page.click_login_btn
  • コンソールから test.rb を実行してみると動くはず。
$ ruby test.rb

step2:投稿作成ページも作ってみる

  • 投稿の新規作成画面(add_new_post_page.rb)を作成する。
E2Etest
  └ add_new_post_page.rb # ←投稿の新規作成page
  └ admin_login_page.rb
  └ test.rb
# add_new_post_page.rb
# 新規投稿作成画面のPageObject
class AddNewPostPage

  def initialize(driver)
    @driver = driver
    @driver.get("https://xxx.wordpress.com/wp-admin/post-new.php")
  end

  # ページの機能
  # 本文を入力する
  def set_body(str)
    @driver.switch_to.frame("content_ifr") # WYSIWYGでの入力はframeの切り替えが必要
    body.send_keys(str)
  end

  # タイトルを入力する
  def set_title(str)
    @driver.switch_to.default_content
    title.send_keys(str)
  end

  # 公開ボタンを押す
  def click_publish_btn
    @driver.switch_to.default_content
    publish_btn.click
  end

  # ページの要素
  private
  def body
    return @driver.find_element(:id, "tinymce")
  end

  def title
    return @driver.find_element(:id, "title")
  end

  def publish_btn
    return @driver.find_element(:id, "publish")
  end
end
  • test.rb に記事投稿の処理を追加する
# test.rb
require 'selenium-webdriver'
require File.expand_path(File.dirname(__FILE__) + '/admin_login_page')
require File.expand_path(File.dirname(__FILE__) + '/add_new_post_page')

driver = Selenium::WebDriver.for :firefox

# ログインページにログインする
login_page = AdminLoginPage.new(driver)
login_page.set_email
login_page.set_pwd
login_page.click_login_btn

# 新規投稿
new_post_page = AddNewPostPage.new(driver)
new_post_page.set_title("タイトルです")
new_post_page.set_body("本文です")
new_post_page.click_publish_btn
  • これで新規投稿が可能になった
  • これだと毎回同じタイトル、本文になるので、yyyymmdd_hhmmssとかを追加してユニークな文字列になるようにするtipsはどこかに公開されているはず
  • ちなみに本だとAddNewPostPageの中にaddNewPostというメソッドがあって、PageObjectの中で「記事を書く」というアクションを一つのメソッドでまとめています。

step3:だったら同じようにpageに一連の処理をまとめておけばいいんじゃないか?

# admin_login_page.rb
class AdminLoginPage

  def initialize(driver)
    @driver = driver
    @driver.get("https://xxx.wordpress.com/wp-admin/")
  end

  # ページの機能
  def login
    email.clear
    email.send_keys("イーメール")
    pwd.clear
    pwd.send_keys("パスワード")
    login_button.click
  end

  # ページの要素
  private

  def email
    return @driver.find_element(:id, "user_login")
  end

  def pwd
    return @driver.find_element(:id, "user_pass")
  end

  def login_button
    return @driver.find_element(:id, "wp-submit")
  end
end
  • 次にadd_new_post_page.rb
# add_new_post_page.rb
class AddNewPostPage

  def initialize(driver)
    @driver = driver
    @driver.get("https://xxx.wordpress.com/wp-admin/post-new.php")
  end

  # ページの機能
  def add_new_post(titlestr, description)
    @driver.switch_to.frame("content_ifr")
    body.send_keys(description)

    @driver.switch_to.default_content
    title.send_keys(titlestr)

    publish_btn.click
  end


  # ページの要素
  private
  def body
    return @driver.find_element(:id, "tinymce")
  end

  def title
    return @driver.find_element(:id, "title")
  end

  def publish_btn
    return @driver.find_element(:id, "publish")
  end
end
  • 続いてこの2つを呼び出すtest.rbを下記のように変更
# test.rb
require 'selenium-webdriver'
require File.expand_path(File.dirname(__FILE__) + '/admin_login_page')
require File.expand_path(File.dirname(__FILE__) + '/add_new_post_page')

driver = Selenium::WebDriver.for :firefox

# ログインページにログインする
login_page = AdminLoginPage.new(driver)
login_page.login

# 新規投稿
new_post_page = AddNewPostPage.new(driver)
new_post_page.add_new_post("タイトルです", "本文です")
  • ってした後に、test.rbを実行するとさっきと同じように動くはず。
$ ruby test.rb
  • ただこれだと、loginを呼ぶと必ず「正しいID/PWの組み合わせ」でのログイン処理になるので、「間違ったID/PWの組み合わせでログイン出来ないこと」を確認するテストが必要なときは変える必要がありますね。

step4:ページをもっとふやす

  • 残りの下記の画面を追加する

    • 一覧画面(all_post_page.rb)
    • 編集画面(edit_post_page.rb)
    • 削除画面(delete_post_page.rb)
  • 本によると一覧画面( https://xxx.wordpress.com/wp-admin/edit.php )では「以下の6つのサービスを提供します」とのこと

    • 投稿の作成
    • 投稿の編集
    • 投稿の削除
    • カテゴリによる投稿のフィルタリング
    • 投稿内のテキスト検索
    • 投稿数のカウント
  • ってことでこのページは盛りだくさんだけど書いてみる。

# all_posts_page.rb
class AllPostsPage

  def initialize(driver)
    @driver = driver
    @driver.get("https://xxx.wordpress.com/wp-admin/edit.php")
  end

  # ページの機能
  # 記事の作成
  def create_new_post(title, description)
    # 新規作成画面に遷移
    add_new_post.click

    # 作成画面のPageObjectを作成+投稿
    new_post = AddNewPostPage.new(@driver)
    new_post.add_new_post(title, description)
  end

  # 投稿の編集
  def edit_post(present_title, new_title, description)
    # 指定されたタイトルの詳細画面に行く
    go_to_paticular_post_page(present_title)
    edit_post_page = EditPostPage.new(@driver)
    edit_post_page.editPost(new_title, description)
  end

  # 投稿の削除
  def delete_post(title)
    # 指定されたタイトルの詳細画面に行く
    go_to_paticular_post_page(title)
    delete_post_page = DeletePostPage.new(@driver)
    delete_post_page.delete_post
  end

  # 一覧画面での投稿のフィルタリング
  def filter_posts_by_category(category)
    # 本にも載ってないので割愛。あとで書いてみるかもしれない。
  end

  # 投稿の検索
  def search_in_posts(search_text)
    # 本にも載ってないので割愛。あとで書いてみるかもしれない。
  end

  # 投稿数の取得
  def get_all_posts_count
    return posts_container.find_elements(:tag_name, "tr").size
  end


  # ページの要素
  private

  def add_new_post
    return @driver.find_element(:link_text, "新規追加")
  end

  # 各記事タイトルの要素(という言い方でいいのかな)をすべて取得
  def posts_container
    return @driver.find_element(:id, "the-list")
  end

  # 指定した投稿の編集ページに遷移するメソッド
  def go_to_paticular_post_page(title)
    all_posts = posts_container.find_elements(:class_name, "row-title")
    all_posts.each do |ele|
      if ele.text == title
        ele.click
        break
      end
    end
  end
end
# edit_post_page.rb
class EditPostPage

  def initialize(driver)
    @driver = driver
  end

  def editPost(str_title, str_description)
    @driver.switch_to.frame(content_frame)
    body.clear
    body.send_keys(str_description)

    @driver.switch_to.default_content
    title.clear
    title.send_keys(str_title)

    publish_btn.click
  end


  private
  
  # WYSIWYGのiframe  
  def content_frame
    return @driver.find_element(:id, "content_ifr")
  end

  # 本文入力欄
  def body
    return @driver.find_element(:id, "tinymce")
  end

  # タイトル入力欄
  def title
    return @driver.find_element(:id, "title")
  end

  # 更新ボタン
  def publish_btn
    return @driver.find_element(:id, "publish")
  end
end
# delete_post_page.rb
class DeletePostPage

  def initialize(driver)
    @driver = driver
  end

  def delete_post
    move_to_trush.click
  end

  private

  def move_to_trush
    return @driver.find_element(:link_text, "ゴミ箱へ移動")
  end
end
  • ...と、ここで気づいたこと
    • 本をよく見たら admin_login_pageにあるloginメソッドって戻り値としてAllPostsPageクラスを返してるのね。。。見落としてたわ_| ̄|○
    • ってことで下記のように修正
# admin_login.rb
class AdminLoginPage

  def initialize(driver)
    @driver = driver
    @driver.get("https://xxx.wordpress.com/wp-admin/")
  end

  # ページの機能
  def login
    email.clear
    email.send_keys("イーメール")
    pwd.clear
    pwd.send_keys("パスワード")
    login_button.click

    # ログイン後にはAllPostPageを返す
    return AllPostsPage.new(@driver)
  end


  # ページの要素
  private

  def email
    return @driver.find_element(:id, "user_login")
  end

  def pwd
    return @driver.find_element(:id, "user_pass")
  end

  def login_button
    return @driver.find_element(:id, "wp-submit")
  end
end
  • そしてtest.rbをこうしてみる
# test.rb
require 'selenium-webdriver'
require File.expand_path(File.dirname(__FILE__) + '/admin_login_page')
require File.expand_path(File.dirname(__FILE__) + '/add_new_post_page')
require File.expand_path(File.dirname(__FILE__) + '/all_posts_page')
require File.expand_path(File.dirname(__FILE__) + '/edit_post_page')
require File.expand_path(File.dirname(__FILE__) + '/delete_post_page')


driver = Selenium::WebDriver.for :firefox

# ログインページにログインする
login_page = AdminLoginPage.new(driver)

# loginの戻り値は AllPostsPage
all_posts_page = login_page.login


# 記事の投稿
all_posts_page.create_new_post("タイトル1", "本文1")

# 記事の編集
driver.get("https://xxx.wordpress.com/wp-admin/edit.php")
all_posts_page.edit_post("タイトル1", "タイトル2", "本文2")

# 記事数の取得
driver.get("https://xxx.wordpress.com/wp-admin/edit.php")
puts "記事数 = #{all_posts_page.get_all_posts_count}"

# 記事の削除
driver.get("https://xxx.wordpress.com/wp-admin/edit.php")
all_posts_page.delete_post("タイトル2")
  • ここまでのディレクトリの中
E2Etest
  └ add_new_post_page.rb
  └ admin_login_page.rb
  └ all_posts_page.rb
  └ delete_post_page.rb
  └ edit_post_page.rb
  └ test.rb

課題

  • 課題1:pageの数だけrequieが増えるのか!? → 調べればすぐできそう
  • 課題2:PageObjectすべてにURLをフルパスで書くと変更したとき大変 → 調べればすぐできそう
  • 課題3:画面遷移をするときに、遷移先のpageオブジェクトをnewするとき、しないときってどう使い分けるのかわかってない → これは答えがよくわかんない

  • これらは「操作の自動化」であって「テストの自動化」ではないので、今後はRSpecをつかってテストの自動化をしてみたいと思います。

  • 冒頭にも書きましたが、いい書き方なのか不安なのでアドバイスお願いします!

コードがあたり前の世界

世界を変えよう。アップルによるキッズ向けコーディングワークショップ : ギズモード・ジャパン

の記事を読んで思ったこと。

世界を変える手段はコードだけじゃないだろうけど、もはやプログラミングスキルは義務教育レベルに当たり前スキルになってくるとしたときに、自分の子供に教えてあげられる(可能であればレベルの高いものを優しく)ようになってたいし、一緒にプログラミングを楽しめるようになりたい。

例えば今、年齢高い人でPC使えない人って、どこかであきらめたんだと思います。私には無理だから。あるいは(それまでの)仕事をやる上で必要ないから。でも現実的にそゆ人は組織の生産性を下げてると思います。

これは”使う”話ですが、今後は”作る”のが当たり前な世界になるんじゃなかろうか。例えば自分に期待されるアウトプットがメールやエクセルじゃなくてコードになる。営業も経理もデザインすらコードになる、みたいな世界。

chromedriverの時に --ignore-certificate-error を消したい

require "selenium-webdriver"

caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {"args" => [ "--test-type" ]})
driver = Selenium::WebDriver.for :chrome, desired_capabilities: caps
driver.get("https://www.google.co.jp/")


Before

f:id:sakaimo:20141211211429p:plain



After

f:id:sakaimo:20141211211444p:plain

めでたしめでたし。(それによりどんなことが起こるのかよくわかってないけど)

ヒンシツ大学 Evening Talk #01 ”高橋寿一氏とアジャイルとテストを語ろう に参加してきた

  • プログラムの話じゃないですが、表題の勉強会に参加してきました。
  • 初めてSHIFT社にお邪魔しましたが、キレイなスペースで素敵空間でした。
  • 写真NGとのことなので残念。

ヒンシツ大学 » 先着50名様!【無料開催】高橋寿一氏とアジャイルとテストを語ろう!

自分の感想

  • 探索的テスト、、、楽しそうだけど「上手にやる」のは難しそうだな。

  • 探索的テストのスキルの伝達方法

    • (探索的に関わらず)なんでその人は見つけるのか。本人も説明できない。
    • 出来る人を育てる、増やす、に関しては時間が掛かりそうだし、何をもって「できる」とするのかも定義は難しい。
    • 重要度の高いの不具合を多く見つけた人、とかかな。
    • ある期間やってみて圧倒的に差がつくようであれば多分その人はできる人。
  • 探索的テストの前に、開発者レベルでの「最低限の品質が担保できていること」が前提

    • 今は網羅的なテストケースつくって実施してる
    • いきなり探索的テストやったらどうなる?
    • すべてを置き換えるわけではないが、テスト観点だけを渡して後はテスター任せ。
    • どうしてもパターンを網羅しなければならない時はデシジョンテーブルなどを使って全パターンを指定する
    • をやったら、
      • テストケースを作る工数は不要になる
      • テスト実施に早く入れる→はやく不具合を指摘できる→修正時間を多く確保できる
      • ケースにかかる時間分を実施に投入できるので
        • 他のQAメンバーが参加する
        • クラウドソーシングする
        • などを試せそう。

以下、勉強会時のメモ

マイクロソフトの25年位前

  • MSでは開発者:開発40%、テスト40%、UIとか20%だった
  • そこから、よりスピードが求められた
  • 上流工程のテストに移行+探索的テストに移行

旧来のテスト

  • 網羅的=全機能ちゃんとチェック。かつ、機能の組合せ
    • →網羅度が品質基準になる、を厳密にやる
  • お金がかかる=品質に関わる投資はどんなプロジェクトでも50%を超える
  • 時間がかかる=最初のリリースから数ヶ月のテストしてませんか?
    • →お金がある大企業ではお金がかかるより時間がかかるのがいや

軽いソフトウェア、重いソフトウェアで同じテストでいいのか?

  • よくないよね。

クラウドアジャイルの新しいテストとは?

  • 早い
  • 安い
  • そこそこの品質

ビジネスで勝たないと意味が無い。

  • 旧来のやり方でメリットがあるのかどうか。

提案する新しいテストのスタイル

  • TDD+探索的テスト
  • TDD:
    • 開発者による網羅的テスト
    • QAではなく開発スピードを上げるための手法
    • 仕様変更に耐えながら「最低限の」品質を担保する
  • 探索的テスト
    • ケント・ベックは基本的にシステムテストや受け入れテストについて定義していない
    • 早く開発できるようになったら、重いテストは受け入れられない
    • そういうときに探索的テストは最適
    • デイリーリリースにもマッチする
  • 探索的テストvsテストケースベーステスト
    • →探索的テストは有効、なグラフ (開発品質によるよね?)
  • 探索的テストは「スキルがあるテスターが」やらないと効果は低い

探索的テストとは?

  • ソフトウェアテストの「スタイル」
  • 個人に自由意志を持たせるとともに責任をより明確んする
  • 一個人のテスト活動
  • ↑メモが追いつかなかったので
  • http://sssslide.com/www.slideshare.net/goyoki/ss-34292539 のP10から引用
    • Cem Kanerによる探索的テストの定義
    • ソフトウェアテストのスタイル
    • テスター一人一人の自由意志と責務に基づく
    • 各々の価値の合わせて洗練されていく
    • テスト関係の学習、テスト設計、テスト実施、テスト結果の説明を扱う
    • プロジェクトを通して並行実施される補完的な活動

やってることは

  • ソフトウェアを理解する
  • ケース設計
  • ケースの\実施
  • の3つを同時実行する → つまりこれらを説明、書き出すことができない人には探索的テストをやらせても効果は低い

同時実行なので一つ一つのテストケースは書かない

  • 書いても見ない(実施者が自分だから)
  • 何のために書くのか?
  • そんな暇があったらテストしようぜ → テストケースを書く時間は全体の半分くらい?

過去はテストケース数に対していくつ消化したか、で品質を判断していた

  • 1000ケースのうち、999ケースやったらから出荷していいのか?
  • ケースは品質を代表する指標なのか?
  • それよりは、スキルのあるテスターが「品質を担保するために」ユーザーケースを考えてテストする
  • 仕様に変更があるたびにケースを修正する時間があるなら、テスターがそれも含めてテストする
  • ソースコード量は増えている。コピペ。
  • テスト実施者の生産性はそんなに上がらない

従来のテストケースベースのテストをネガティブに言うと

  • ケースは時間がかかるしめんどうだから書かない、だったらテストする時間にしよう
  • だいたいどこにバグが有るかななわからない 触ってみる
  • 回帰テスト:スキルのあるテスターが変更箇所を考えてテストすることでバグが見つかる

要求仕様(ドキュメント)は間違いと言うより記述されていないものがバグになる

  • エスカレータの例
    • 靴を履いてください 
    • 犬を抱いてください ←犬を買ってこないとならない

テストケースがあるとテストケースしかやらない

  • テスターはそれだけやることが自分の仕事、になる。

探索的テストの本

  • 探索的テストのPass/Fail基準
    • アドホックでもいいかげんでもランダムでもない
    • ちゃんとストラテジックに行うもの
    • マネージャが部下のテストアクティビティを理解する(何をやったか、ではなく)
  • どのコンポーネントに対して、どの品質特性を担保するのか、を定義する
    • 機能性|○○したら合格|○○したら不合格|
    • 安定性|       |ユーザーが目的を達せられない
    • 対象ソフトウェアはWindowsOSを不安定にさせない、とか
  • ↑この辺りは↓この本に詳しく載ってそう

探索的テストのプロセス

  • 製品の目的を明確にする
  • 重要機能リストアップ
  • 機能を明確にする(役割分担
  • 弱いエリアを叩く ←わかりきってることはテストしない(誰かが気づく)
    • 理論的には継続開発では7%のファイルからすべてのバグが見つかる (2010年くらいの情報)
  • テスト実行およびバグを記録する
  • 上記を継続して実行する
  • ↑この辺りも↓この本に詳しく載ってそう

JamesWhittakerのアイディア

  • ソフトウェア
    • └入力処理
    • └計算
    • └出力処理
    • └データ保存
  • ソフトウェアは上記の4つしかしない。
  • ケースを考えるときにはこれを考える

タスクシートを書く

  • タスクの記述:なにをやろうとしているのか(何を担保するため?
  • 探索アプローチガイド:どのように進めていくか。どんな値を入力するか、どんな操作をするか、どういう考えですすめるのか
  • 結果:どういう品質のものがリリース可能か
  • 終了基準:どのように終わらせるか。どうなったら終わりと判断するのか。

探索的テストとは「スタイル」。メソドロジーじゃない。

  • 結果がきちんと報告できる必要がある
    • この値は境界値。この値はディシジョンテーブルでやりました。
    • 正常ステート、異常ステートを確認しました
    • 入力データは正常、異常を確認しました
    • こんなバグがでて、修正され、回帰テストを行い、問題なかったので出荷OKです。とか。

スキルのあるテスター

  • ねらった品質を担保するために適切な手法が使えること

探索的テストの欠点

  • 素人がやるのはダメ

デベロッパーとテスター

  • が並んで、デベロッパーが実装の仕方、仕様を話しながら、隣でテスターがテストする
  • こういうときにエラーハンドリングするから、やってみて
  • とかは効果を発揮した。

注意点

  • 100%探索的テスト頼っちゃだめ
  • トレーニングを十分に受けたテスターがやる
  • コードを書く人のできる人できない人差は26倍。
  • テストの場合は10倍くらいと思ってる
  • 非機能テストについては探索的テストの効果はビミョウ

マイクロソフトの実験

  • 未経験者 10%のバグを発見
  • 教育を受けた人 65%のバグを発見

MS時代

  • ファーストビルドが動くようになるまではみんなで遊ぼうよ
  • (テストが無駄になるから開発者自身で使ってみる)
  • ファーストビルドが動くまでは開発者が担保する

  • 現状はドキュメントも揃ってない、仕様変更も起こる、最新が何なのかわからない、、、

  • これが「あたりまえ」の時代になってきている。それでもリリースしなければならない状況。
  • その状況でも耐えうるテストが必要

質疑応答

  • テストエンジニアの地位
  • MSではコンピュータサイエンスを学んできた人じゃないとテストエンジニアになれない
  • PM、開発者の10%~20%低いくらいの給与
  • ポジションが明確に存在するので転職しやすい
  • 日本はプロジェクトマネジメントエンジニア、テストエンジニアの地位が不明確
  • ちゃんとスキルをもって労働市場にでれば、企業はほしい

現場で探索的テストを活用している例

  • ある会社の例

    • 1スプリント2週間
    • 1週目に開発
    • 2週目の初日に探索的テストを全員で実施
  • 1日時間をとるだけでも、継続開発を続けていった将来から見た時に検証時間は劇的に下がる

「チャーター(ガイドライン)」が重要になる

  • チャーターについては↓これのP26以降に例
  • プライマリファンクション(これがないと意味が無い、という機能)
  • コントリビューションファンクション(あったりいいな)
  • を説明して、2時間くらいで切って実施

  • 画一的なチャーターを導入してもだめ。

  • 自分のチームにあったものを作って、他のチームのチャーターを見ながら良くしていく

  • ケースを書いて消化するときにバグに悩まされる、ではなくて、

  • 今あるソフトウェアの状況を把握し、それをもとにディスカッションしながら進める

  • セッションベースドテスト

  • 探索的テストの効果を説明できること

  • リスクは存在する
  • 成果が出なかったチームは無い

優秀なテスターの指標

  • コミュニケーションスキル
    • 開発者と仲がいい方がバグを見つける
  • バグを見つけるスキル
    • (高橋さんが)長年人間観察しているが答えが出ていない
  • できる人にやったあとに聞くと、
    • 前回の製品よりはいいですよ、とかコメントして、実際に市場評価もそんな感じになっている

意味のある探索的テストにするためには

  • 前提知識をみにつける
    • ドメイン知識
    • テスト対象の理解(システムの目的や使われ方、ユーザー層など)
    • ドキュメントレビューへの参加
    • 過去の類似プロジェクトの不具合状況
  • 知見を共有する
    • 類似プロジェクトのバグの傾向、リスク
  • 手順に書かれていないテストの必要性
  • 書かれたことを消化するだけでやった気になってしまう
  • 開発プロセス全体の中の1つのプロセスとして探索的テストを位置づける

品質で儲かるエリアの製品、スピードで儲かるエリアの製品がある

  • エビデンスは残らないので、医療のような厳しい品質を求めるものには向かない