Vim - previmでmermaidに対応しました

Shibaで図を書いてTracで共有するを見てmermaidを知りました。
参考: mermaid.jsが素晴らしいけどなかなか使ってる人見かけないので実例晒す(追記あり)

面白そうだし、上記の公式サイトにmarked対応のサンプルもあったのでprevimでも対応しました。

下記のようにコードブロックのタイプをmermaidとしてもらえればOKです。
mermaid自体の具体的な書き方は公式サイトを参考にしてください。
(私も全然分かっていません)

```mermaid
graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->E;
```

あとは普通のmarkdownと同様にプレビューされます。

Vim - 文字列連結で再代入する場合はjoinを使う方が早そう

概要

自作プラグインのコードに以下のようなTODOが残っていました。

  " TODO リストじゃなくて普通に文字列連結にする(テスト書く)
  for line in s:do_external_parse(a:lines)
    let escaped = 適当な処理
    call add(converted_lines, escaped)
  endfor
  return join(converted_lines, "\\n")

ここでは文字列の配列をぐるぐる回して処理を行い、最終的に改行区切りの文字列としています。
joinで書いたものの文字列連結の方が早いのではと思って上記のようなTODOコメントを残していました。

今日このTODOを消化しようと思い、本当に文字列連結の方が早いのか計測しました。

先に結論

  • 10回程度の連結なら大差ない
  • 3000回ともなると圧倒的にjoinが早い
    • joinの方が実装もシンプル

環境

  • Mac OS X 10.9.5
  • MacVim Custom Version 7.4 (KaoriYa 20150707)

ベンチマーク

h1mesuke/vim-benchmarkを使用します。
テストデータは1行85文字が3000行です。(3000回連結される)

let s:lines = []
for s:n in range(3000)
  call add(s:lines, "    * 注意:拡張子が`.md`の場合は`markdown`ではなく`modula2`として認識されてしまいます。その場合は以下の設定を.vimrcに記述してください")
endfor

let s:bm = benchmark#new("concat string")

function! s:bm.operator_for()
  let result = ""
  let delim  = ""
  for line in s:lines
    let result = result . delim . line
    let delim  = "\\n"
  endfor
endfunction

function! s:bm.operator_while()
  let result = ""
  let delim  = ""
  let n = 0
  let length = len(s:lines)
  while n < length
    let result = result . delim . s:lines[n]
    let delim  = "\\n"
    let n += 1
  endwhile
endfunction

function! s:bm.join()
  let tmp = []
  for line in s:lines
    call add(tmp, line)
  endfor
  let result = join(tmp, "\\n")
endfunction

call s:bm.run(3)

結果は以下の通り。

Benchmark: concat string

Trial #1
  join           : 0.008425
  operator_for   : 0.348224
  operator_while : 0.356583

Trial #2
  join           : 0.010402
  operator_for   : 0.353079
  operator_while : 0.359111

Trial #3
  join           : 0.008463
  operator_while : 0.347276
  operator_for   : 0.359592

joinが圧倒的に早かった。
Java+連結すると遅いように、Vim Scriptでも気をつけた方がいいのかな。

ちなみに10回程度の連結なら大差ない。

Benchmark: concat string

Trial #1
  join           : 0.000046
  operator_for   : 0.000063
  operator_while : 0.000076

Trial #2
  join           : 0.000051
  operator_for   : 0.000080
  operator_while : 0.000083

Trial #3
  join           : 0.000048
  operator_for   : 0.000066
  operator_while : 0.000082

おわりに

「推測するな、計測せよ」の大事さ。

capistrano3のコードを読んで仕組みを理解する

メモです。

はじめに

capistrano3の概要を知るには以下の記事がとても参考になる。

業務での使用も経て何となく分かったつもりだったが、デフォルトで色々やってくれるのが逆に少し気持ち悪かった。
内部を理解しないことには気持ち悪さは解消されそうにないのでコードを読んだ。
だいぶ理解したものの、すぐに忘れそうなのでメモしておく。

対象

  • capistrano3について何となく知っている。使ったことがある
  • capistranoの内部でどういう風に処理が走っているかは知らない
  • capistranoのバージョンは3.4.0

長くなったので、各項目のまとめだけ読むと手っ取り早いと思う。

1. cap install

入り口としてcap installが何故実行出来るのか、から。
bin/capの中を覗く。

require 'capistrano/all'
Capistrano::Application.new.run

capistrano/allを読み込みCapistrano::Applicationを実行しているのみ。

1.1. capistrano/all

require 'capistrano/version'
require 'capistrano/version_validator'
require 'capistrano/i18n'
require 'capistrano/dsl'
require 'capistrano/application'
require 'capistrano/configuration'
  • dsl
    • invokeとかonとかrun_locallyとかのDSL定義
  • application
    • 後述
  • configuration
    • setとかfetchとか設定系のDSL定義

ここは特筆することなさそう。

1.2. capistrano/application

Rake::Applicationを継承。bin/capにあったCapistrano::Application.new.runのクラス。
クラスがインスタンス化されるときの処理(initialize)が以下のようになっている。
自前のCapfileに加えて、lib/CapfileRakefileとして読み込まれる。

    def initialize
      super
      @rakefiles = %w{capfile Capfile capfile.rb Capfile.rb} << capfile
    end

    # allows the `cap install` task to load without a capfile
    def capfile
      File.expand_path(File.join(File.dirname(__FILE__),'..','Capfile'))
    end

なおrunはRakeに処理を委譲しているだけ。

    def run
      Rake.application = self
      super
    end

1.3. lib/Capfile

cap installした時に作られるCapfileではなくライブラリ側のCapfile

include Capistrano::DSL
require 'capistrano/install'

lib/capistrano/install.rbの定義は1行だけ。

load File.expand_path(File.join(File.dirname(__FILE__),'tasks/install.rake'))

このあと見て行くsetupdeployでもほとんど同じで、最終的に.rakeファイルを読み込むようになっている。

`lib/capistrano/tasks/install.rake`
desc 'Install Capistrano, cap install STAGES=staging,production'
task :install do
    # ...
end

:installcap install時の処理が書かれている。
つまり色々とファイルを自動生成してくれる。

1.4. cap installのまとめ

  • bin/capcapistrano/allを読み込み、Cap用のRakeを実行する
  • capistrano/allにより基本的なDSLが定義される
    • 中で読まれるcapistrano/applicationによりライブラリ側のCapfileが読まれる
    • ライブラリのCapfileにはinstallだけが定義されている
      • なのでcap installが実行出来る

cap installすると生成されるCapfileは不要なコメントアウト行を除くと以下の通り。

# Load DSL and set up stages
require 'capistrano/setup'

# Include default deployment tasks
require 'capistrano/deploy'

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }

setupdeployについて読み進めていく。
(lib/capistrano/tasks/*.rakeについてはコメントの通りカスタム定義なので読むところはなし)

2. capistrano/setup

include Capistrano::DSL

namespace :load do
  task :defaults do
    load 'capistrano/defaults.rb'
  end
end

stages.each do |stage|
  Rake::Task.define_task(stage) do
    set(:stage, stage.to_sym)

    invoke 'load:defaults'
    load deploy_config_path
    load stage_config_path.join("#{stage}.rb")
    load "capistrano/#{fetch(:scm)}.rb"
    I18n.locale = fetch(:locale, :en)
    configure_backend
  end
end

require 'capistrano/dotfile'

処理の流れは何となく読み取れる。

  • stage(たぶんproductionとかstagingとか)ごとの設定を読み込んでいる
  • defaultsっていうのを読み込んでいる。きっとデフォルト設定だろう
  • scmの定義を読み込んでいる
  • その他なんか設定している

もう少しコードを読んでみる。

2.1. stage(たぶんproductionとかstagingとか)ごとの設定を読み込んでいる

いくつかプロパティが出てくるが、これらはどこで定義されているか。

stages

前述のcapistrano/dslにて読み込まれるlib/capistrano/dsl/stages.rbに定義されている。

      def stages
        Dir[stage_definitions].map { |f| File.basename(f, '.rb') }
      end

      def stage_definitions
        stage_config_path.join('*.rb')
      end

stage_config_pathlib/capistrano/dsl/paths.rbに定義されている。

      def stage_config_path
        Pathname.new fetch(:stage_config_path, 'config/deploy')
      end

:stage_config_pathが設定されていればそのパス、設定されていなければconfig/deployのパスを取得している。

cap installするとconfig/deploy/{production,staging}.rbを作成してくれるので、デフォルトではこれらが取得される。
File.basenameなので結果としては['production', 'staging']という値がstagesの戻り値になっているはず。

deploy_config_path

上記のstage_config_pathと同じくlib/capistrano/dsl/paths.rbに定義されている。

      def deploy_config_path
        Pathname.new fetch(:deploy_config_path, 'config/deploy.rb')
      end

こちらもcap installで作られるconfig/deploy.rbがデフォルトである。

なおsetupのコードを再び見ると、loadの順番は次のようになっている。

    load deploy_config_path
    load stage_config_path.join("#{stage}.rb")

config/deploy.rbを読み込んでからconfig/deploy/*.rbを読み込む。
なので両者で同じ定義をしているとconfig/deploy/*.rbが勝ちそうな気がする。試していない。

2.2. defaultsっていうのを読み込んでいる

その名の通り変数のデフォルト値を定義している。

set_if_empty :scm, :git
set_if_empty :branch, :master
set_if_empty :deploy_to, -> { "/var/www/#{fetch(:application)}" }
set_if_empty :tmp_dir, "/tmp"

set_if_empty :default_env, {}
set_if_empty :keep_releases, 5

set_if_empty :format, :pretty
set_if_empty :log_level, :debug

set_if_empty :pty, false

set_if_empty :local_user, -> { Etc.getlogin }

多くの変数については、自動生成されるdeploy.rbコメントアウトされた内容として書かれている。

# Default value for :scm is :git
# set :scm, :git
...

2.3. scmの定義を読み込んでいる

前述の通り:scmdeploy.rbなどで定義するがデフォルトはgitである。
lib/capistrano/git.rbが読み込まれ、このあとのdeployで必要なコマンドが定義される。
あとでまた詳しく見るとする。

2.4. その他なんか設定している

configure_backendについて。

    def configure_backend
      backend.configure do |sshkit|
        sshkit.format           = fetch(:format)
        sshkit.output_verbosity = fetch(:log_level)
        sshkit.default_env      = fetch(:default_env)
        sshkit.backend          = fetch(:sshkit_backend, SSHKit::Backend::Netssh)
        sshkit.backend.configure do |backend|
          backend.pty                = fetch(:pty)
          backend.connection_timeout = fetch(:connection_timeout)
          backend.ssh_options        = (backend.ssh_options || {}).merge(fetch(:ssh_options,{}))
        end
      end
    end

sshkitの設定ぽい。処理の全体像とは関係が低そうなのでこれ以上は追わない。

2.5. capistrano/setupのまとめ

  • SCMやデプロイ先などアプリケーション固有の変数を初期化
  • config/deploy.rbの読み込み
  • config/deploy/*.rbの読み込み
  • SCM固有のコマンド定義を読み込み
  • SSH設定を読み込み

3. capistrano/deploy

require 'capistrano/framework'

load File.expand_path("../tasks/deploy.rake", __FILE__)

capistrano/frameworkについては以下のようになっている。

load File.expand_path("../tasks/framework.rake", __FILE__)
require 'capistrano/install'

ということで、framework.rakedeploy.rakeを読み進める必要がある。

3.1. framework.rake

冒頭の参考記事(2つ目)にもある通り、このファイルはデプロイフローの雛形を定義している。

namespace :deploy do
  desc 'Start a deployment, make sure server(s) ready.'
  task :starting do
  end

  # ...

  desc 'Rollback to previous release.'
  task :rollback do
    %w{ starting started
        reverting reverted
        publishing published
        finishing_rollback finished }.each do |task|
      invoke "deploy:#{task}"
    end
  end
end

desc 'Deploy a new release.'
task :deploy do
  set(:deploying, true)
  %w{ starting started
      updating updated
      publishing published
      finishing finished }.each do |task|
    invoke "deploy:#{task}"
  end
end
task default: :deploy
  • deploy/rollbackは共に必要なコマンドを実行するだけ
    • このあとで詳しく見る
  • deploy/rollback以外のコマンドはデフォルトでは何もしない
    • 具体的な処理はdeploy.rakeで定義される
  • namespace :deployのデフォルトは:deploy
    • なのでcap deploydeployが呼ばれる

テンプレートメソッドっぽい。

3.2. deploy.rake

:startingとか:updatingとか、framework.rakeのいくつかのコマンドが再定義されている。

3.2.1. deploy.rake -- deploy

フェーズ毎に簡単に処理を見てみる。

deploy:starting

該当部分だけ抜き出す。

  task :starting do
    invoke 'deploy:check'
    invoke 'deploy:set_previous_revision'
  end

  desc 'Check required files and directories exist'
  task :check do
    invoke "#{scm}:check"
    invoke 'deploy:check:directories'
    invoke 'deploy:check:linked_dirs'
    invoke 'deploy:check:make_linked_dirs'
    invoke 'deploy:check:linked_files'
  end

  task :set_previous_revision do
    on release_roles(:all) do
      target = release_path.join('REVISION')
      if test "[ -f #{target} ]"
        set(:previous_revision, capture(:cat, target, '2>/dev/null'))
      end
    end
  end
  • check系のタスクではファイルやディレクトリのチェックや準備を行う
    • deploy:check:directoriesなどのタスクは同一ファイルに定義されている
    • execute :mkdir, '-p', shared_path, releases_pathなどをしている
  • :set_previous_revisionではリリース前の最新リビジョンを変数に保存しておく

deploy:started

デフォルトのまま。何もしない。

deploy:updating

:updatingには依存があり、先に:new_release_pathが行われる。 まずは:new_release_pathから読む。

  task :new_release_path do
    set_release_path
  end

set_release_pathlib/capistrano/dsl/paths.rbに定義されている。

      def releases_path
        deploy_path.join('releases')
      end

      def set_release_path(timestamp=now)
        set(:release_timestamp, timestamp)
        set(:release_path, releases_path.join(timestamp))
      end
  • リリース先のパスを変数に設定している
  • releases/20150080072500みたいなパス
    • デプロイの構造は公式のStructureを参照

次にupdatingの処理の中身を読む。

  task :updating => :new_release_path do
    invoke "#{scm}:create_release"
    invoke "deploy:set_current_revision"
    invoke 'deploy:symlink:shared'
  end

  namespace :symlink do
    # ...
    desc 'Symlink files and directories from shared to release'
    task :shared do
      invoke 'deploy:symlink:linked_files'
      invoke 'deploy:symlink:linked_dirs'
    end
    # ...
  end

  desc "Place a REVISION file with the current revision SHA in the current release path"
  task :set_current_revision  do
    invoke "#{scm}:set_current_revision"
    on release_roles(:all) do
      within release_path do
        execute :echo, "\"#{fetch(:current_revision)}\" >> REVISION"
      end
    end
  end
  • :set_current_revisionは前述のREVISIONにリビジョンを書き込んでいるだけ
  • :symlink:sharedはsymlinkの必要があるディレクトリやファイルがあれば作っている
    • 上記のリンク(Structure)のsharedを参照

:create_releaseについては少し詳しく読む。
コードを全部載せると長いので概要だけ。

  # lib/capistrano/tasks/git.rake
  desc 'Upload the git wrapper script, this script guarantees that we can script git without getting an interactive prompt'
  task :wrapper do
    # ...
  end

  desc 'Clone the repo to the cache'
  task clone: :'git:wrapper' do
    # ...
  end

  desc 'Update the repo mirror to reflect the origin state'
  task update: :'git:clone' do
    # ...
  end

  desc 'Copy repo to releases'
  task create_release: :'git:update' do
    # ...
  end
  • 依存関係を考慮した実行順は wrapper -> clone -> update -> create_release
  • wrapper
    • 専用のgit-ssh.shを配置する
  • clone
    • cloneされていなければcloneする
      • git :clone, '--mirror', repo_url, repo_path
    • clone済みなら何もしない
      • デプロイ先にrepo/HEADがあるかどうか
        • 上記のリンク(Structure)のrepoを参照
  • update
    • repoの中で実行される
    • git :remote, :update
  • create_release
    • repoの中で実行される
    • :repo_tree がある場合
      • git :archive, fetch(:branch), tree, "| tar -x --strip-components #{components} -f - -C", release_path
    • ない場合
      • git :archive, fetch(:branch), '| tar -x -f - -C', release_path

deploy:updated

デフォルトのまま。何もしない。

deploy:publishing

  task :publishing do
    invoke 'deploy:symlink:release'
  end

  namespace :symlink do
    desc 'Symlink release to current'
    task :release do
      on release_roles :all do
        tmp_current_path = release_path.parent.join(current_path.basename)
        execute :ln, '-s', release_path, tmp_current_path
        execute :mv, tmp_current_path, current_path.parent
      end
    end
  end

current -> /var/www/my_app_name/releases/20150120114500/ みたいなsymlinkを作っているだけ。
デプロイ先の構造については上記のリンク(Structure)を参照。

deploy:published

デフォルトのまま。何もしない。

deploy:finishing

  task :finishing do
    invoke 'deploy:cleanup'
  end

  desc 'Clean up old releases'
  task :cleanup do
    on release_roles :all do |host|
      releases = capture(:ls, '-xtr', releases_path).split
      if releases.count >= fetch(:keep_releases)
        info t(:keeping_releases, host: host.to_s, keep_releases: fetch(:keep_releases), releases: releases.count)
        directories = (releases - releases.last(fetch(:keep_releases)))
        if directories.any?
          directories_str = directories.map do |release|
            releases_path.join(release)
          end.join(" ")
          execute :rm, '-rf', directories_str
        else
          info t(:no_old_releases, host: host.to_s, keep_releases: fetch(:keep_releases))
        end
      end
    end
  end

descの通り:keep_releasesを超えた古いリリースファイルを消す作業。
capture(:ls, '-xtr', releases_path).splitは、こんなやり方があるのかとちょっと驚いた。
ls -xtr <pat>とすることで「水平(x)」に「日付(t)」の「降順(r)」で出力させて、splitすることでそのまま配列として扱っている。

あとt(:keeping_releases ...)tって何だと思ったが、i18nのようだった。

# lib/capistrano/dsl.rb
def t(key, options={})
  I18n.t(key, options.merge(scope: :capistrano))
end

deploy:finished

  task :finished do
    invoke 'deploy:log_revision'
  end

  desc 'Log details of the deploy'
  task :log_revision do
    on release_roles(:all) do
      within releases_path do
        execute %{echo "#{revision_log_message}" >> #{revision_log}}
      end
    end
  end

リリース先のrevisions.logにメッセージを書き込む。
revisions.logについても上記のリンク(Structure)を参照。

3.2.2. deploy.rake -- rollback

deploy:starting
deploy:started

deployと同じ。

deploy:reverting

  task :reverting do
    invoke 'deploy:revert_release'
  end

  desc 'Revert to previous release timestamp'
  task :revert_release => :rollback_release_path do
    on release_roles(:all) do
      set(:revision_log_message, rollback_log_message)
    end
  end

  task :rollback_release_path do
    on release_roles(:all) do
      releases = capture(:ls, '-xt', releases_path).split
      if releases.count < 2
        error t(:cannot_rollback)
        exit 1
      end
      last_release = releases[1]
      set_release_path(last_release)
      set(:rollback_timestamp, last_release)
    end
  end
  • リリースが1つしかないならロールバック不可
  • 一つ前のリリースを:release_pathに設定

deploy:reverted

デフォルトのまま。何もしない。

deploy:publishing

処理的にはdeployと同じ。
ただし前述の通り:release_pathが直前のリリースパスになっているので、currentがそれにsymlinkされる。

deploy:published

deployと同じ。(何もしない)

deploy:finishing_rollback

  task :finishing_rollback do
    invoke 'deploy:cleanup_rollback'
  end

  desc 'Remove and archive rolled-back release.'
  task :cleanup_rollback do
    on release_roles(:all) do
      last_release = capture(:ls, '-xt', releases_path).split.first
      last_release_path = releases_path.join(last_release)
      if test "[ `readlink #{current_path}` != #{last_release_path} ]"
        execute :tar, '-czf',
          deploy_path.join("rolled-back-release-#{last_release}.tar.gz"),
        last_release_path
        execute :rm, '-rf', last_release_path
      else
        debug 'Last release is the current release, skip cleanup_rollback.'
      end
    end
  end
  • ロールバックしたリリースはいらないよね、っていう作業
  • rolled-back-release-#{last_release}.tar.gzという感じのファイル名でアーカイブ
    • デプロイ先(currentとかと同じ階層)に置かれる
  • releasesにあるリリースを消す

ちなみにロールバックに関する公式のページは中身がなかった。

deploy:finished

deployと同じ。

3.3. capistrano/deploy のまとめ

  • capistrano/frameworkはフローの雛形を提供している
  • deploy
    • デプロイ先のディレクトリなどを準備する
    • 初回のみgit cloneする
    • 毎回git remote updateが行われる
    • git archive <branch>したものがreleases/<timestamp>の中に置かれる
    • 上記リリース物とcurrentをsymlinkでくっつける
    • releases:keep_releasesを超えないように調整する
  • rollback
    • 直前のリリース物をcurrentにする
    • 直前のリリース物をアーカイブする
    • 直前のリリース物をreleasesから削除する

frameworkdeployのおかげで利用者としては基本的にはフックを定義するだけで良い。ということをちゃんと理解出来た。

基本処理は分かったとして、一応before/afterのフックについても仕組みを見ておきたい。

4. before/after

Before / After Hooks

capistrano/dsl.rbで読み込まれるlib/capistrano/dsl/task_enhancements.rbにて定義されている。

    def before(task, prerequisite, *args, &block)
      prerequisite = Rake::Task.define_task(prerequisite, *args, &block) if block_given?
      Rake::Task[task].enhance [prerequisite]
    end

    def after(task, post_task, *args, &block)
      Rake::Task.define_task(post_task, *args, &block) if block_given?
      post_task = Rake::Task[post_task]
      Rake::Task[task].enhance do
        post_task.invoke
      end
    end

capistranoというよりはRakeの構文の模様。
Rake で任意のタスクの前後に別のタスクを実行する

4.1. before/after のまとめ

  • Rakeに委譲しているだけ

おわりに

整理と備忘のために書いたけど、忘れてもまたドキュメントとコードを読んだ方が分かりやすいような気がしてきた。

「第二回 システムテスト自動化 標準ガイド 読書会」に参加してきた

前回に引き続き参加してきました。

第二回 システムテスト自動化 標準ガイド 読書会
今回は3、4章がテーマ。

3章

http://www.slideshare.net/dnoguchi/3-20150523-48477354

保守性の高いテストスクリプトを書くために、というお話。

  • テストスクリプトとプログラミングは似ている
  • 良いスクリプトと悪いスクリプト
    • 良いスクリプトについて書かれていることは、良いプログラミングと同じ事
    • 「どういうのが良いスクリプトか」の内容については、そりゃそうだよねという感想
  • スクリプトにはきちんとしたドキュメントを残そうという話
    • バージョン管理(Git)やWebDriver/Gebを使えば基本いらないように感じた
  • スクリプトの種類(上から下に向かうにつれて、よりよいスクリプト)
  • スクリプト前処理
    • メンテしやすいようにフォーマットとか定数とか気をつけましょうという話
    • 発表者の方も仰っていたけど当然だよねという話
      • 3章全体がそんな感じではある
      • この本は「基本を書いている」らしいので、そういうレベルのこともあるのかな
      • もしくは当時はこれが当然と思われてはいなかったのか
  • 会場でのディスカッションにて
    • テストスクリプトは完全にDRYにすると読みにくくなる
      • 同感。最近は自分もそう感じるようになっているのでそこまでDRYを心がけていない
    • どのスクリプト手法でもPros/Consがある
      • ただしデータ駆動についてはConsが少なかった

4章

実行結果と期待値の比較について。

  • 「何を」「どのくらいの粒度で」比較するかを検討するのが大事
    • 強く同感
  • 比較の種類
    • 動的比較、実行後比較(静的比較?)
    • 定義及び何のために分けているのかイマイチ不明
    • 質問を投げてみたが、みなさんも定義がもやっとしている様子
      • 実行後比較については刊行当時のツールの制約があるのかもしれないとのこと
  • 単純な比較と複雑な比較
    • 単純な比較: 素直に比較を取る
    • 複雑な比較: 条件付きで比較を取る
      • 例: IDはアカウントによって異なるので差分に含めない
  • テストツールとテストハーネスの関係についての図(P142)
  • 1つの欠陥につき1つのテストケースが失敗するのが理想
  • センシティブかロバスト
    • 基本的なところはセンシティブ、詳細なところはロバストがおすすめ戦略
  • 実行結果と期待値を等価的に扱うためにフィルタを用いる
    • 日付はハイフンに統一するとか
    • 例:HTML全部を比較するようなセンシティブなテストで、IDとか日付とかを固定文字列に置換する
    • 例:画面のエラーメッセージの順番が定まっていないときにsortする
  • 現場の状況(プロジェクトや製品の性質、メンバースキルなど)を理解して方法を選択する
    • これは比較に限らずって感じ

感想

前回に引き続き有意義でした。
今回は懇親会にも参加し、色々とお話が聞けたのも良かったです。

  • 動的比較と実行後比較はやっぱり分かり辛いよね
    • (本編でも議論されていましたが)テスト実行後に出来たファイルを目視確認するとかが実行後比較? -> いやそれは動的じゃない?とか
  • データ駆動とキーワード駆動について
    • 本ではキーワード駆動が最上位という感じだけど、データ駆動とキーワード駆動については一長一短なのでは
    • テスターとプログラマーが分かれているなどの大規模案件だとキーワード駆動の効果は大きそう
    • プログラマーがテストも行うような小規模だとデータ駆動が丁度良いのではないか
  • 画面テストではやはりIEは対象外にするのが平和である
  • テスト以外の話題も(むしろそっちの方が多かったかも)
    • 採用について
    • マネジメントについて
    • モチベーションについて

参加された方々のポジションも様々で、品質/プロダクティビティエンジニア/人事などなど。
プロダクティビティエンジニアという呼び方は知りませんでした。
分野的にはエンジニアリングプロダクティビティと呼ぶのだとか。噛みそう。

あとお店の食べ物が美味しかった。

私はこの日体調が悪く、前半は普通に喋れていたのですが後半は体調が悪化して口数が減ってしまいました。
前述した「ドキュメントはどの粒度で書くべきか」という話もしたかったのですが体力尽きていた。次回は体調を万全にしておきたい。

直近でのテスト関係のイベントをいくつか教えて頂きました。

「第一回 システムテスト自動化 標準ガイド 読書会」に参加してきた

第一回 システムテスト自動化 標準ガイド 読書会に参加してきました。
今回は1、2章がテーマ。

1章

http://www.slideshare.net/fujisawa_y/20150418-46648838

  • テスト管理
  • テスト設計
    • モデルベースドテスト
      • 開発に使ったモデルからテストを作るのでなく、テストのためのモデルを再設計する?
      • まだリンク先の資料を読んでいない
    • PictMasterやCEGTestなどのツールがある
  • ブラウザテスト
    • メンテコストが高いと感じているがどうだろう
      • やっぱメンテコスト高いねという話
      • xpathとかを扱っていると死にやすい
        • classやidを振っているとまだ変更に対応しやすい
    • Gebがいいよって話
    • WebDriverの本がそのうち出るらしい(別々の方から2冊)
  • 負荷テストはJMeterよりGatlingが良いらしい
  • 要件や設計のテストって、レビューとは違う?
    • シーケンス図や業務フロー図を書いてみることがテストの一つに該当する
    • CEGTest使うと漏れが見える
      • ただ手法が難しいらしい

2章

スライドはどこかに上がっているのでしょうか?

  • ソフトウェア設計自体が、テスティングの自動化自体の成功を左右する
    • 最初からテストを意識して設計すると良いよね
      • 前述のブラウザテストで例えると、idやclassを最初から振っておくとか
  • テスターとテストオートメーターが分かれている経験ある人?
    • テスターは派遣とか外注、オートメーターは自社というパターンがあるみたい
  • テスターとテストオートメーターの分業について
    • 賛成/反対に分かれて参加者で意見出し
  • 2章の話は原著当時の背景が強く影響している?
    • MSのWindowsテスト?(うろ覚え)
  • テストスクリプトが複雑になる = メンテコストが高くなる
    • あるある
  • アドホックテストの自動化は無意味。自動化の前にテスト手法/設計を見直すべき
  • テスト自動化をプロジェクトで導入するには
    • 実際に見せる。画面テストを100ケースとか
    • QAの世界では4つのコスト(予防、評価、外部失敗、内部失敗)という考えがある
    • 気合い

感想

  • 興味が湧いたツール
  • 興味が湧いた考え方
    • モデルベーステスト
    • コストモデル
  • カバレッジは補助として使う
    • 品質保証としては使っていない(一例)
    • 自分たちのテストがどこを通っているのか = 次はどこをテストをすべきかの参考にする
  • 行って良かった