例えばコメント投稿のテスト。

# test/integration/post_comment_to_comic_test.rb:
require 'test_helper'

class PostCommentToComicTest < ActionDispatch::IntegrationTest
  test 'Post a comment' do
    visit comic_path comics(:comic)
    within('#new_comment') do
      fill_in 'comment[body]', with: 'コメントのテスト'
    end
    click_button '規約に同意してコメントする'
    assert has_selector? '.content-comment__body-text',
      text: 'コメントのテスト'
  end
end

よくあるこういうのを実行したら下記のエラーが。

% ruby -Itest test/integration/post_comment_to_comic_test.rb -n PostCommentToComicTest#test_Post_a_comment
Run options: -n PostCommentToComicTest#test_Post_a_comment --seed 29141

# Running:

E

Finished in 5.497046s, 0.1819 runs/s, 0.0000 assertions/s.

  1) Error:
PostCommentToComicTest#test_Post_a_comment:
Capybara::Webkit::ClickFailed: Failed to click element /html/body/div[@id='content']/div[@id='comments']/div/div[1]/form[@id='new_comment']/div[@id='comment_action']/input because of overlapping element /html/body/div[@id='content']/nav/div/ul/li[2]/a/span at position 831, 1025; 
A screenshot of the page at the time of the failure has been written to /var/folders/nb/5jp6psyd7h19my68chb0svvh0000gn/T/click_failed_W60479.png
    test/integration/post_comment_to_comic_test.rb:23:in `block in '

1 runs, 0 assertions, 0 failures, 1 errors, 0 skips

クリックできなかったElementのxpathを表示してくれる。そして何故クリックできなかったのか、overlapしているElementのpositionとxpathも表示してくれる。更にscreenshotここに撮っておいたからってどこまで至れり尽くせりだよ。

この場合、要はこんな感じになっててコメントボタンがクリックできない。

こんな方法でいいのかどうかわかりませんが、テスト前にexecute_scriptでoverlapしてるメニューをhideして対処。

# test/integration/post_comment_to_comic_test.rb:
require 'test_helper'

class PostCommentToComicTest < ActionDispatch::IntegrationTest
  test 'Post a comment' do
    visit comic_path comics(:comic)
    execute_script("$('nav.other-pages-nav').hide()") # It's workaround.
    within('#new_comment') do
      fill_in 'comment[body]', with: 'コメントのテスト'
    end
    click_button '規約に同意してコメントする'
    assert has_selector? '.content-comment__body-text',
      text: 'コメントのテスト'
  end
end

更に背景と同色の文字問題とかもサポートしてくれたら手動QA不要とはいわないけどもっと自動テスト化できますね。

ajaxで投稿してその結果を調べるみたいなテストでテストが通ったり通らなかったりする。投稿が反映される前に見に行くことがあるから。

feature "Posting a comment", js: true do
  scenario "as signed user" do
    comment_id = Comment.last.id + 1
    within("#new_comment") do
      fill_in 'comment[body]', with: 'コメントのテスト'
    end
    click_button '規約に同意してコメントする'
    sleep 1 # PLZ WAIT!! FIXME!!
    find("#comment_#{comment_id}").should have_content('コメントのテスト')
  end
end

CapybaraのREADMEにも書いてあるけどデフォルトは2秒待つようだけど5に変えたら行けた。(sleep 1は取りましたw)

# spec/spec_helper.rb:
RSpec.configure do |config| 
  Capybara.default_wait_time = 5
end
  • konacha: mochaをrailsで良い感じにするやつ
  • mocha: jsをrspecっぽくテストできるやつ(like a jasmine)
  • chai: mochaのassertionライブラリ
$ brew install qt
# Gemfile:
group :development, :test do
  gem 'capybara-webkit'
  gem 'konacha'
end
# config/initializers/konacha.rb:
Konacha.configure do |config|
  config.spec_dir = 'spec/javascripts'
  config.driver = :webkit
  config.stylesheets = %w(application)
end if defined?(Konacha)
# app/assets/javascripts/foo.js.coffee:
class Foo
  bar: ->
    'bar'
# spec/javascripts/foo_spec.js.coffee:
#= require foo
  
describe 'Foo', ->
  it '#bar', ->
    (new Foo).bar().should.equal('bar')
% rake konacha:run
F

  Failed: Foo #bar
    ReferenceError: Can't find variable: Foo

Finished in 0.00 seconds
1 examples, 1 failed, 0 pending

そりゃそうだ。だってcoffeeだと

(function() {
  var Foo;
  Foo = (function() {
    function Foo() {}
    Foo.prototype.bar = function() {
      return 'bar';
    };
    return Foo;
  })();
}).call(this);

こうやってグローバグ汚さないように囲われてるんだから。だからといって元々デフォルトOFFだった--bareオプションは今はデフォルトONになってるのでわざわざ外すもの気持ち悪い。

だったら

class @Foo

って書けばいいじゃん。って言ってるんだけどマジで?みんなどうやってるの?

sprockets-commonjsが標準で入ってファイル名にmoduleが必要じゃなきゃいいんだけどなあ。

  1. capybaraを2.xに上げる
  2. capybara-webkitが動かなくなる
  3. poltergeistに移行する
  4. konacha(mocha)でpoltergeist(phantomjs)が動かない
  5. capybara-webkitにcapybara2.xで動くバージョンが出る(0.14.0)
  6. capybara-webkit 0.14.0に上げる
  7. libqt4-devのバージョンが古くて(4.6)debian squeeze上でコンパイルできない
  8. debian wheezyに上げて、libqt4-devのバージョンを上げる(4.8)
  9. jenkins復活

長かった。

seleniumはguard時とかにウィンドウ出てきてウザい。capybara-webkitはcapybara2系に対応してない。(reposのheadでは対応してるので次bump upされたら対応されるっぽい)

現時点ではpoltergeist一択っぽい。

ただ、Macでwebfontを読むとphantomjsがcrushするのでpatch当てる。

$ brew install phantomjs
# Gemfile:
group :test do
  gem 'poltergeist'
  gem 'rack-contrib'
end
# spec/spec_helper.rb:
require 'capybara/poltergeist'

RSpec.configure do |config|
  Capybara.javascript_driver = :poltergeist
end
# config/initializers/poltergeist.rb:
if Rails.env.test?
  require 'rack/contrib/simple_endpoint'
  Rails.application.config.middleware.insert_after Rack::Runtime, Rack::SimpleEndpoint, /\.ttf$/ do |req, res|
    ua = req.env['HTTP_USER_AGENT']                                            
    if ua =~ /Intel Mac OS X.*PhantomJS/                                       
      res.status = 403
      "Denying #{req.fullpath} to #{ua}"                                       
    else
      :pass
    end
  end
end

これは酷い。

この作業、@mreinschとペアプロでやってたんですがpoltergeistってドイツ語だそうです。ちなみに@hrysdがhidden fieldを「ハイデンフィールド」とドイツ語っぽく呼んでたのでドイツ語で何て言うのか聞いたら、「verstecktes felder」だそうです。全く違いました。どんな言語でも「ハイデンフィールド」は間違いだそうです。

こんなこと書いてたけど普通にできた。(simpletestは必要)

% cake testsuite app all

知らんかった・・・。

ついでに、phpunitを使ったseleniumのテストも一緒にできるshell作った。

app/vendors/shells/tasks/integrations.php:

<?php
class IntegrationsTask extends Shell
{
    public function startup() {}

    public function execute()
    {
        $this->out(`phpunit app/tests/integrations`);
    }
}

app/vendors/shells/test.php:

class TestShell extends Shell
{
    public $tasks = array('Cases', 'Integrations');

    public function startup() {}

    public function main()
    {
        $arg = isset($this->args[0]) ? $this->args[0] : '';
        switch ($arg) {
        case 'cases':
            $this->Cases->execute();
            break;
        case 'integrations':
            $this->Integrations->execute();
            break;
        default:
            $this->Cases->execute();
            $this->Integrations->execute();
        }
    }
}
% cake test

CakePHP1.3でtestを書くにはsimpletestが必要。(デフォルトで付いてない)CakePHP1.3はちょっと古いのでsimpletestの最新では動かないので1.0系を使う。

まずFixtureを書く。Fixtureはクラスで書く。(これyamlとかでできないかなあ)

<?php
class PostFixture extends CakeTestFixture {
    var $name = 'Post';
    var $table = 'posts';

    var $fields = array(
        'id' => array(
            'type'    => 'integer',
            'null'    => false,
            'key'     => 'primary'
        ),
        'name' => array(
            'type'    => 'integer',
            'null'    => false
        )
    );

    var $records = array(
        array('id' => 1, 'name' => 'komagata'),
        array('id' => 2, 'name' => 'machida'),
        array('id' => 3, 'name' => 'yoshida')
    );
}
?>

modelのテストケースを書く。

<?php
class TestPost extends Post {
    var $cacheSources = false;
    var $useDbConfig = 'test';
}

class PostTestCase extends CakeTestCase {
    var $Post = null;
    var $fixtures = array('app.post');

    function startTest() {
        $this->Post =& ClassRegistry::init('Post');
    }

    function endTest() {
        unset($this->Post);
        ClassRegistry::flush();
    }

    function testPostInstance() {
        $this->assertTrue(is_a($this->Post, 'Post'));
    }

    function testFetchKomagata() {
        $this->Post->recursive = -1;
        $result = $this->Post->fetchKomagata();
        $expected =  array(
            'Post' => array('id' => 1, 'name' => 'komagata')
        );
        $this->assertEqual($result[0], $expected);
    }
}
?>

こんな感じ。/test.phpから結果が見れる。

Selenium RCをインストール

PHPUnitをインストール

PHPUnit_Seleniumをインストール。

% pear install phpunit/PHPUnit_Selenium

testを書く(ファイル名はクラスと同じCamelCase)

test/integrations/LoginTest.php

<?php
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';

class LoginTest extends PHPUnit_Extensions_SeleniumTestCase
{
    protected function setUp()
    {
        $this->setBrowser('*firefox');
        $this->setBrowserUrl('http://foo/');
    }

    public function testTitle()
    {
        $this->open('/sign_in');
        $this->assertTitle('Sign In');
        $this->type('account', 'foo');
        $this->type('password', 'password');
        $this->submitAndWait('user_form');
        $this->assertTitle('Dashboard');
    }
}
?>

実行。対象ディレクトリ以下のなんとかTest.phpを全部実行する。

$ phpunit test/integrations

This week in open source — giant robots smashing into other giant robots

We have officially stopped maintaining the following open source products: limerick_rake, trout, shoulda-context, and jester.

shoulda-contextが更新終了。怖話では全部コレでテスト書いてるのに。なんてこった!U2plusは混在してたのをRSpecに統一したところで調度良かったが・・・。

This week in open source — giant robots smashing into other giant robots

Hey! shoulda-context has a maintainer! His name is Travis Jeffery (travisjeffery) and he’s got commit rights and everything! Thank you, Travis.

なんて思ってたらメンテナが見つかって復活。もう何が何やら・・・。

GoogleのWebアプリの脆弱性スキャナ、skipfishを使ってみました。

$ brew install skipfish
$ skipfish -o log -W ~/homebrew/Cellar/skipfish/2.03b/libexec/dictionaries/minimal.wl http://hamcutlet.fjord.jp

homebrewで簡単にインストールできます。結果はhtmlファイルで出るので-oで結果の格納先ディレクトリを指定します。(作っておく)

-Wで辞書を指定します。まずは簡単にと思って一番手軽そうなminimal.wlを選んでHam Cutletのサイトに対して実行してみました。

・・・。

これが滅茶苦茶時間かかる。下記のScan timeを見てください。超簡単なサイトにminimal.wlでやっただけで4時間掛かりました・・・。

skipfish version 2.03b by 

  - hamcutlet.fjord.jp -

Scan statistics:

      Scan time : 4:06:32.814
  HTTP requests : 544785 (36.8/s), 177569 kB in, 211488 kB out (26.3 kB/s)  
    Compression : 185 kB in, 529 kB out (48.1% gain)    
    HTTP faults : 9 net errors, 0 proto errors, 841 retried, 0 drops
 TCP handshakes : 5489 total (99.3 req/conn)  
     TCP faults : 0 failures, 8 timeouts, 2 purged
 External links : 28 skipped
   Reqs pending : 0           

Database statistics:

         Pivots : 29 total, 29 done (100.00%)    
    In progress : 0 pending, 0 init, 0 attacks, 0 dict    
  Missing nodes : 18 spotted
     Node types : 1 serv, 26 dir, 1 file, 0 pinfo, 0 unkn, 1 par, 0 val
   Issues found : 82 info, 10 warn, 1 low, 85 medium, 0 high impact
      Dict size : 2182 words (11 new), 35 extensions, 196 candidates
        
[+] Wordlist '/Users/komagata/homebrew/Cellar/skipfish/2.03b/libexec/dictionaries/minimal.wl' updated (11 new words added).
[+] Copying static resources...
[+] Sorting and annotating crawl nodes: 29
[+] Looking for duplicate entries: 29
[+] Counting unique nodes: 17
[+] Saving pivot data for third-party tools...
[+] Writing scan description...
[+] Writing crawl tree: 29
[+] Generating summary views...
[+] Report saved to 'log/index.html' [0x1366e0ff].
[+] This was a great day for science!

結果は下記のように分り易いHTMLで出ます。(Chromeではjsが動かなかった)

Skipfish - scan results browser

コレだけを持ってお客さんに「弊社のサイトは安全です」と言えるかというとうーむ・・・。しかし、結果を見てみると、なるほどという点もかなりあるので怪しいところを修正する目安になりました。

でもまあ、兎に角実行時間が長い・・・。実用的なサイトに対してフルに実行したら1日じゃ終わらなそうです。