ためすう

アジャイルソフトウェア開発の奥義 第4章 テスティング

2019-06-08

「アジャイルソフトウェア開発の奥義」の読書メモです。

時々、個人の見解入りです。

ユニットテストを書くという行為は、機能検証というよりも設計に近い行為である。また、ドキュメント作成に近い行為であるとも言える。

テストを最初に書くという行為は、設計上の判断をふるいにかける行為でもある。

テスティングフレームワークに頼らない場合、本章にある通り、設計を気にする必要がある。

Ruby on Rails などの場合、テスティングフレームワーク の機能がリッチだと思うので

テスト実行時に、テスト対象の処理を上書きする形にすればテストは実現できそうである。

Mock Object パターンも、テスト専用の処理をどこに、どうやって書くかの1つの選択肢。

テスティングフレームワークに頼りすぎると、基盤の移行をする際、選択肢が少なくなる可能性があるのがデメリットと考える。

根底にある考えは理解する必要があると思うが、現実に即して考えると全部真似する必要はないと思う。

Active Support の concern を使ってみる

2019-06-08

やったこと

Active Support の concern を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

調査

concern を使う主な理由

mix-in で同じことをやると記述が複雑になりますが、concern を使えば簡潔に書くことができます。

また、複雑な依存関係を考慮しなくて良くなります。

確認 (concern なし)

test.rb

module Hoge2
  def self.included(base)
    base.class_eval do
      def self.method_injected_by_hoge2
        'method_injected_by_hoge2'
      end
    end
  end
end

module Hoge1
  def self.included(base)
    base.method_injected_by_hoge2
  end
end


class Sample
  include Hoge2
  include Hoge1
end

p Sample.method_injected_by_hoge2

ここで、include Hoge2 がないと下記のエラーで落ちます。

Traceback (most recent call last):
	3: from ruby-mix-in.rb:18:in `<main>'
	2: from ruby-mix-in.rb:19:in `<class:Sample>'
	1: from ruby-mix-in.rb:19:in `include'
test.rb:13:in `included': undefined method `method_injected_by_hoge2' for Sample:Class (NoMethodError)

確認 (concern あり)

test.rb

require 'active_support/concern'

module Hoge2
  extend ActiveSupport::Concern

  included do
    def self.method_injected_by_hoge2
      'method_injected_by_hoge2'
    end
  end
end

module Hoge1
  extend ActiveSupport::Concern

  include Hoge2

  included do
    self.method_injected_by_hoge2
  end
end


class Sample
  include Hoge1
end

p Sample.method_injected_by_hoge2

Sample クラスは Hoge1 だけ include すれば良くなりました。

参考

Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated が出る件

2019-06-08

やったこと

Selenium に関する警告が出るようになったので修正します。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

調査

メッセージ

2019-06-08 14:23:23 WARN Selenium [DEPRECATION] Selenium::WebDriver::Chrome#driver_path= is deprecated. Use Selenium::WebDriver::Chrome::Service#driver_path= instead.

書いてある通りですが、

[非推奨] Selenium::WebDriver::Chrome#driver_path=

[推奨] Selenium::WebDriver::Chrome::Service#driver_path

みたいです。

修正

Today I got the same issue and the reason of that was a gem called chromedriver-helper in Gemfile, I just removed this gem and after I ran this command: bundle install

参考リンクを見ると、chromedriver-helper を削除したら、解決できそうです。

また、

If your code is using selenium, perhaps with the Watir gem, use the Webdriver gem instead of chromedriver-helper now. gem ‘webdrivers’, ‘~> 3.0’

chromedriver-helper の代わりに、webdrivers を使うと幸せになれるかもしれない。

とあるのでやってみます。

Gemfile

-  gem 'chromedriver-helper'
+  gem 'webdrivers', '~> 3.0'
$ bundle install

これで警告が出なくなりました。

参考

hashdiff に関する警告が出たので修正する

2019-06-08

やったこと

WebMock をインストールしたら、hashdiff に関する警告が出るようになったので修正します。

エラーで止まるわけではないので、ワーニングのようです。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

調査

メッセージ

The HashDiff constant used by this gem conflicts with another gem of a similar name.  As of version 1.0 the HashDiff constant will be completely removed and replaced by Hashdiff.  For more information see https://github.com/liufengyun/hashdiff/issues/45
  • 定数で似ているものが使われている
  • HashDiff -> Hashdiff に置き換えられる

修正

メッセージのリンクを辿ると、下記に解決策がありました。

Conflicts with hash_diff · Issue #45 · liufengyun/hashdiff

Gemfile

gem 'hashdiff', ['>= 1.0.0.beta1', '< 2.0.0']

$ bundle install
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/.........
Fetching gem metadata from https://rubygems.org/.
You have requested:
  hashdiff >= 1.0.0.beta1, < 2.0.0

The bundle currently has hashdiff locked at 0.4.0.
Try running `bundle update hashdiff`

If you are updating multiple gems in your Gemfile at once,
try passing them all to `bundle update`

update してみます

$ bundle update

(これで該当の警告は出なくなりましたが、新しい警告が出るようになりました。)

参考

rspec で WebMock を使う

2019-06-08

やったこと

WebMock の stub を利用して rspec でテストを書いてみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

$ gem list | grep rspec-rails
rspec-rails (3.8.2)

調査

インストール

Gemfile

group :development, :test do
  gem 'webmock'
end
$ bundle install

spec/models/task5_spec.rb

require 'rails_helper'
require 'webmock/rspec'

RSpec.describe Task, type: :model do

  before do
    WebMock.enable!
  end

  it 'test1' do
    WebMock.stub_request(:get, "http://localhost").to_return(
      body: JSON.generate({b: 3, c: 8}),
      status: 200
    )

    p Net::HTTP.get('localhost', '/')
  end
end

出力結果

$ bundle exec rspec spec/models/task5_spec.rb
"{\"b\":3,\"c\":8}"
.

Finished in 0.0048 seconds (files took 1.5 seconds to load)
1 example, 0 failures

参考

rspec で Faraday の stub を使う

2019-06-07

やったこと

Faraday の stub を利用して rspec でテストを書いてみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

$ gem list | grep rspec-rails
rspec-rails (3.8.2)

$ gem list | grep faraday
faraday (0.15.4)

調査

spec/models/task4_spec.rb

require 'rails_helper'

RSpec.describe Task, type: :model do

  it 'test1 allow success' do
    conn = Faraday.new do |conn|
      conn.adapter :test, Faraday::Adapter::Test::Stubs.new do |stub|
        stub.get '/fuga/test.json' do
          [200, {}, JSON.generate({a: 'hogehoge'})]
        end
      end
    end

    response = conn.get '/fuga/test.json'
    p response.body
  end
end

出力結果

$ bundle exec rspec spec/models/task4_spec.rb
"{\"a\":\"hogehoge\"}"
.

Finished in 0.00817 seconds (files took 2.74 seconds to load)
1 example, 0 failures

所感

こんな感じでレスポンスを差し替えることができます。

メリット

外部サービスのAPIのテストなどがやりやすくなりそうです。

デメリット

外部サービスのAPIのレスポンスが変わってもテストでは気づきにくくなりそうです。

レスポンスが変わったことについて、テストを実行しないと気付かないという体制の方が問題だと思うので、デメリットはあまり気にしなくても良いかもしれません。

参考

Ruby の reject を使う

2019-06-07

やったこと

Ruby で reject を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

調査

test.rb

ary = ["a", "", nil]
p ary
p ary.compact.reject(&:empty?)

出力結果

$ ruby test.rb
["a", "", nil]
["a"]

参考

Ruby の compact を使う

2019-06-07

やったこと

Ruby で compact を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

調査

test.rb

ary = ["a", "", nil, 4]
p ary
p ary.compact

出力結果

$ ruby test.rb
["a", "", nil, 4]
["a", "", 4]

compact は自身から nil を取り除いた配列を生成して返します。

参考

Rails5 で Faraday を使ってみる

2019-06-07

やったこと

Rails5 で Faraday を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

調査

インストール

Gemfile

gem 'faraday'
$ bundle install

使ってみる

app/controllers/tasks_controller.rb

class TasksController < ApplicationController

  def index
    render :json => {c:3, d:4}
  end

  def new
    uri = 'http://127.0.0.1:3000/'
    conn = Faraday::Connection.new(:url => uri) do |builder|
      builder.use Faraday::Adapter::NetHttp
      builder.use Faraday::Response::Logger
    end
    res = conn.get "/", {a:1, b:2}
    logger.debug(res.body)
  end
end

URL にアクセス

$ curl http://localhost:3000/tasks/new
$ less log/development.log
Started GET "/tasks/new" for ::1 at 2019-06-07 00:37:53 +0900
Processing by TasksController#new as */*
Started GET "/?a=1&b=2" for 127.0.0.1 at 2019-06-07 00:37:53 +0900
Processing by TasksController#index as */*
  Parameters: {"a"=>"1", "b"=>"2"}
Completed 200 OK in 1ms (Views: 0.3ms | ActiveRecord: 0.0ms)


{"c":3,"d":4}
  Rendering tasks/new.html.erb within layouts/tasks
  Rendered tasks/new.html.erb within layouts/tasks (0.3ms)
Completed 200 OK in 45ms (Views: 33.4ms | ActiveRecord: 0.0ms)

参考

Rails5 で rubocop を使ってみる

2019-06-06

やったこと

今回、 rubocop を使ってみます。

rubocop は、static code analyzer です。(引用そのまま)

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

調査

インストールその1 (失敗)

Gemfile

gem 'rubocop', '~> 0.71.0', require: false

$ bundle install

なんかエラーが出て、gem 追加しろと言われています。

Post-install message from rubocop:
Rails cops will be removed from RuboCop 0.72. Use the `rubocop-rails` gem instead.

Put this in your `Gemfile`.


```rb
gem 'rubocop-rails'
```

And then execute:

```sh
$ bundle install
```

Put this into your `.rubocop.yml`.

```yaml
require: rubocop-rails
```

More information: https://github.com/rubocop-hq/rubocop-rails

インストールその2 (成功)

Gemfile

gem 'rubocop', '~> 0.71.0', require: false
gem 'rubocop-rails'
$ bundle install

使ってみる

ヘルプ

$ bundle exec rubocop -h

手元のファイルで実行する

$ bundle exec rubocop app/controllers/tasks_controller.rb
Inspecting 1 file
C

Offenses:

app/controllers/tasks_controller.rb:1:1: C: Style/Documentation: Missing top-level class documentation comment.
class TasksController < ApplicationController
^^^^^
app/controllers/tasks_controller.rb:1:1: C: Style/FrozenStringLiteralComment: Missing magic comment # frozen_string_literal: true.
class TasksController < ApplicationController
^
app/controllers/tasks_controller.rb:14:5: C: Style/RedundantBegin: Redundant begin block detected.
    begin
    ^^^^^
app/controllers/tasks_controller.rb:17:5: C: Style/RescueStandardError: Avoid rescuing without specifying an error class.
    rescue => e
    ^^^^^^
app/controllers/tasks_controller.rb:28:3: C: Style/EmptyMethod: Put empty method definitions on a single line.
  def new ...
  ^^^^^^^
app/controllers/tasks_controller.rb:47:12: C: Style/HashSyntax: Use the new Ruby 1.9 hash syntax.
    render :json => tmp.to_jso

C は、Convention です。

ここには載っていませんが、

W (Warning)、E (Error)、F (Fatal) とあります。

参考