ためすう
アジャイルソフトウェア開発の奥義 第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 に関する警告が出るようになったので修正します。
確認環境
$ 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) とあります。