プログラミングのツールやサービスは便利なものが揃っているので Ruby の gem を作りながら紹介します。
- Docker なので OS X, Windows, Linux で動作します。
- gem は Ruby の外部ライブラリです。
すること、できること
- Docker の Ruby で gem をテスト駆動開発で作ります。
- CircleCI や CodeClimate のサービスで自動テストをします。
- GitHub や RubyGems にリリースした gem を使います。
GitHub の利用
- GitHub はソースコードを管理するサービスです。
- 今回は my_gems という名前の gem を作るので my_gems のリポジトリを作ります。
- リポジトリを作ると続きはターミナルでコマンドラインを使います。
-
git init
コマンドで空の README を追加をして、git config
コマンドでユーザ情報を設定します。 -
git commit
コマンドでコミットができたら、git log
コマンドでログを確認しましょう。
$ mkdir my_gems
$ cd my_gems/
$ git init && touch README.md && git add README.md
$ git config --local user.name "your-name"
$ git config --local user.email "your-name@example.com"
$ git commit -m "Initial commit" && rm README.md
$ git log --pretty=format:"%an <%ae> : %s"
your-name <your-name@example.com> : Initial commit
-
git remote add
コマンドで GitHub の URL を設定します。 -
git push
コマンドで GitHub にソースコードがアップロードされます。
$ git remote add origin https://github.com/your-name/my_gems.git
$ git push -u origin master
- README だけのリポジトリができました!
-
.netrc
ファイルにgit push
コマンドで要求されるユーザ情報を登録することができます。- ユーザ情報は暗号化することもできます。
machine github.com
login your-name@example.com
password your-password
Docker と Bundler で gem の作成
- Docker はコンテナ型の仮想環境を操作するツールです。
- Bundler は gem の依存を管理したり gem の作成をサポートするツールです。
-
docker run -v $(pwd):/my_gems ruby:2.5.1
コマンドは Ruby が使える仮想環境でコマンドを実行します。-
-v $(pwd):/my_gems
のオプションで仮想環境のボリューム(フォルダ)をホストとマウントします。-
$(pwd)
は絶対パスが設定されます。任意で変更してください。
-
-
-
bundle gem my_gems --test=rspec --mit --no-coc
コマンドで gem の雛形を作ります。-
--test=rspec
のオプションで Test Framework の RSpec を利用します。 -
--mit --no-coc
のオプションで license は MIT、Contributor Code of Conduct の出力は不要にします。
-
$ docker run -v $(pwd):/my_gems ruby:2.5.1 bundle gem my_gems --test=rspec --mit --no-coc
Creating gem 'my_gems'...
MIT License enabled in config
create my_gems.Gemfile
create my_gems.lib.my_gems.rb
create my_gems.lib.my_gems.version.rb
create my_gems.my_gems.gemspec
create my_gems.Rakefile
create my_gems.README.md
create my_gems.bin.console
create my_gems.bin.setup
create my_gems..gitignore
create my_gems..travis.yml
create my_gems..rspec
create my_gems.spec.spec_helper.rb
create my_gems.spec.my_gems_spec.rb
create my_gems.LICENSE.txt
Initializing git repo in /my_gems
Gem 'my_gems' was successfully created. For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html
-
my_gems.gemspec
ファイルの TODO の箇所を修正します。
- spec.authors = ["TODO: Write your name"]
- spec.email = ["TODO: Write your email address"]
+ spec.authors = ["your-name"]
+ spec.email = ["your-name@example.com"]
- spec.summary = %q{TODO: Write a short summary, because RubyGems requires one.}
- spec.description = %q{TODO: Write a longer description or delete this line.}
- spec.homepage = "TODO: Put your gem's website or public repo URL here."
+ spec.summary = %q{my gems of short summary.}
+ spec.description = %q{my gems of longer description.}
+ spec.homepage = "https://github.com/your-name/my_gems.git"
-
Gemfile
ファイルに開発で使う gem を追加します。
gem 'guard-rspec', require: false, group: %i[development test]
gem 'rspec', group: :test
gem 'rspec_junit_formatter', group: :test
gem 'rubocop', require: false, group: %i[development test]
gem 'simplecov', require: false, group: :test
-
git commit
コマンドでコミットします。
$ git add --all && git commit -m "Bundler New gem"
Docker Compose で Docker の構築
- Docker Compose は複数の Docker を管理するツールです。
-
touch
コマンドで空ファイルを作成します。
$ touch Dockerfile && touch docker-compose.yml
-
Dockerfile
ファイルに仮想環境で使う Ruby のバージョンを設定します。-
locale-gen
で日本語の対応をしています。
-
FROM ruby:2.5.1
RUN apt-get update -qq && apt-get install -y locales
RUN sed -i 's/#.*ja_JP\.UTF/ja_JP\.UTF/' /etc/locale.gen
RUN locale-gen && update-locale LANG=ja_JP.UTF-8
RUN mkdir /my_app
WORKDIR /my_app
COPY . /my_app
RUN bundle install
-
docker-compose.yml
ファイルに仮想環境のボリューム(フォルダ)とホストのマウントを設定します。
version: "3"
services:
app:
build: .
volumes:
- .:/my_app
environment:
- LANG=ja_JP.UTF-8
- LC_CTYPE=ja_JP.UTF-8
-
docker-compose build
コマンドで仮想環境を構築するとrake -T
コマンドでタスクを確認できます。
$ docker-compose build
$ docker-compose run app rake -T
rake build # Build my_gems-0.1.0.gem into the pkg directory
rake clean # Remove any temporary products
rake clobber # Remove any generated files
rake install # Build and install my_gems-0.1.0.gem into system gems
rake install:local # Build and install my_gems-0.1.0.gem into system gems without network access
rake release[remote] # Create tag v0.1.0 and build and push my_gems-0.1.0.gem to TODO: Set to 'http://mygemserver.com'
rake spec # Run RSpec code examples
-
git commit
コマンドでコミットします。
$ git add --all && git commit -m "Docker Compose build"
CircleCI の利用
- CircleCI は自動でテストやデプロイを実行するサービスです。
-
touch
コマンドで空ファイルを作成します。
$ mkdir .circleci/
$ touch .circleci/config.yml
-
.circleci/config.yml
ファイルにテストの実行とレポートの送信を設定します。
version: 2
jobs:
build:
docker:
- image: circleci/ruby:2.5.1-node-browsers
working_directory: ~/repo
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "Gemfile.lock" }}
- v1-dependencies-
- run:
name: Install Dependencies
command: |
bundle install --jobs=4 --retry=3 --path vendor/bundle
- run:
name: Setup Code Climate test-reporter
command: |
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
chmod +x ./cc-test-reporter
- save_cache:
paths:
- ./vendor/bundle
key: v1-dependencies-{{ checksum "Gemfile.lock" }}
- run:
name: Run Tests
command: |
mkdir /tmp/test-results
TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
./cc-test-reporter before-build
bundle exec rspec --format progress \
--format RspecJunitFormatter \
--out /tmp/test-results/rspec.xml \
--format progress \
$TEST_FILES
./cc-test-reporter after-build --coverage-input-type simplecov --exit-code $?
- store_test_results:
path: /tmp/test-results
- store_artifacts:
path: /tmp/test-results
destination: test-results
-
circleci build
コマンドでテストを実行することができます。- Bundler で作成した RSpec の雛形はテストが失敗します。
$ circleci build
Error: runner failed
Task failed
-
git commit
コマンドでコミットして、git push
コマンドでアップロードします。
$ git add --all && git commit -m "CircleCI config"
$ git push origin master
- CircleCI の
Add Projects
から my_gems でSet Up Project
をクリックします。
- CircleCI で
Start building
をクリックするとテストが実行されます。- 今回はテストに失敗したことがレポートされています。
Code Climate の利用
- Code Climate は自動でコードの品質をチェックするサービスです。
- Code Climate で
Add a repository
をクリックして my_gems を追加します。
- カバレッジは未対応なので傘のアイコンが表示されています。
-
touch
コマンドで空ファイルを作成します。
$ touch .codeclimate.yml
-
.codeclimate.yml
ファイルに Rubocop の利用を設定します。
version: "2"
plugins:
rubocop:
enabled: true
-
git commit
コマンドでコミットして、git push
コマンドでアップロードします。
$ git add --all && git commit -m "Code Climate config"
$ git push origin master
- Code Climate で品質に問題があることを指摘されています。
Rubocop の利用
- Rubocop はコーディング規約のツールです。
-
touch
コマンドで空ファイルを作成します。
$ touch .rubocop.yml
-
.rubocop.yml
ファイルに Rubocop のコーディング規約を設定します。
AllCops:
Exclude:
- "bin/*"
- "spec/spec_helper.rb"
- "Guardfile"
- "Gemfile"
- "*.gemspec"
- "Rakefile"
Style/Documentation:
Enabled: false
Metrics/BlockLength:
Exclude:
- "spec/**/*"
-
bundle exec rubocop
コマンドで規約違反を確認することができます。
$ docker-compose run app bundle exec rubocop
Inspecting 3 files
CCC
-
git commit
コマンドでコミットして、git push
コマンドでアップロードします。
$ git add --all && git commit -m "Rubocop config"
$ git push origin master
- Code Climate も同じ結果になります。
SimpleCov の利用
- SimpleCov はテストのカバレッジをサポートするツールです。
- Code Climate の
Test coverage
でTEST REPORTER ID
の値を CircleCI に設定します。
- CircleCI で Environment の
Add Variable
をクリックしてCC_TEST_REPORTER_ID
を追加します。 - Code Climate Enterprise は
CC_TEST_REPORTER_COVERAGE_ENDPOINT
も追加します。- https://[your-domain]/api/v1/test_reports
-
spec/spec_helper.rb
ファイルに SimpleCov の設定をします。
require "bundler/setup"
require "simplecov"
SimpleCov.start do
add_filter "/spec/"
end
require "my_gems"
RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure
config.example_status_persistence_file_path = ".rspec_status"
# Disable RSpec exposing methods globally on `Module` and `main`
config.disable_monkey_patching!
config.expect_with :rspec do |c|
c.syntax = :expect
end
end
-
bundle exec rake spec
コマンドでテストを実行します。 -
coverage/index.html
ファイルにカバレッジが出力されます。
$ docker-compose run app bundle exec rake spec
$ open coverage/index.html
-
git commit
コマンドでコミットして、git push
コマンドでアップロードします。
$ git commit -am "SimpleCov config"
テスト駆動開発
- テスト駆動開発 はプログラミングの開発手法で、最初にテストから記述(テストファースト)します。
- テスト駆動開発は Red Green Refactor のサイクルを繰り返します。
- まず、テストから書いて
Red
(Assert First) - すぐ、テストを成功させて
Green
(Fake It) - テストが成功の状態で
Refactor
(Refactoring)
- まず、テストから書いて
Guard RSpec の利用
- Guard はテストの実行をサポートするツールです。
-
bundle exec guard init
コマンドで Guardfile を出力します。 -
bundle exec guard
コマンドで自動テストの準備をします。
$ docker-compose run app bundle exec guard init
$ docker-compose run app bundle exec guard
[1] guard(main)>
- FizzBuzz のプログラムを作ります。
-
spec/my_gems_spec.rb
ファイルに FizzBuzz クラスの仕様を書きます。
require 'spec_helper'
RSpec.describe MyGems do
describe MyGems::FizzBuzz do
context '3で割り切れる場合' do
example 'Fizz という'
end
context '5で割り切れる場合' do
example 'Buzz という'
end
context '15で割り切れる場合' do
example 'FizzBuzz という'
end
context '3や15で割り切れない場合' do
example 'その 数字 をいう'
end
end
end
-
lib/my_gems.rb
ファイルに FizzBuzz のクラスを仮実装します。
require 'my_gems/version'
module MyGems
class FizzBuzz
def initialize(number)
@number = number
end
def say
''
end
end
end
- ファイルが保存されると自動でテストが実行されます。
MyGems
MyGems::FizzBuzz
3で割り切れる場合
Fizz という (PENDING: Not yet implemented)
5で割り切れる場合
Buzz という (PENDING: Not yet implemented)
15で割り切れる場合
FizzBuzz という (PENDING: Not yet implemented)
3や15で割り切れない場合
その 数字 をいう (PENDING: Not yet implemented)
-
git commit
コマンドでコミットして、git push
コマンドでアップロードします。
$ git add --all && git commit -m "Guard config"
$ git push origin master
- CircleCI は全てペンディングなので成功が表示されます。
- Code Climate はカバレッジが表示されます。
Pull request
- Pull request は GitHub Flow という開発フローのプロセスの一つです。
-
git checkout
コマンドで開発のブランチを作成します。 -
bundle exec guard
コマンドで Guard RSpec を実行します。
$ git checkout -b develop
$ docker-compose run app bundle exec guard
-
spec/my_gems_spec.rb
ファイルに say メソッドの仕様を記述します。
require 'spec_helper'
RSpec.describe MyGems do
describe MyGems::FizzBuzz do
describe '#say' do
let(:fizz_buzz) { described_class.new(number) }
subject { fizz_buzz.say }
context '3で割り切れる場合' do
let(:number) { 3 }
example 'Fizz という' do
expect(subject).to eq 'Fizz'
end
end
context '5で割り切れる場合' do
let(:number) { 5 }
example 'Buzz という' do
expect(subject).to eq 'Buzz'
end
end
context '15で割り切れる場合' do
let(:number) { 15 }
example 'FizzBuzz という' do
expect(subject).to eq 'FizzBuzz'
end
end
context '3や15で割り切れない場合' do
let(:number) { 1 }
example 'その 数字 をいう' do
expect(subject).to eq '1'
end
end
end
end
end
- say メソッドは
Fizz
の戻り値を期待しているが、違う値なのでエラーになります。
Failures:
1) MyGems MyGems::FizzBuzz#say 3で割り切れる場合 Fizz という
Failure/Error: expect(subject).to eq 'Fizz'
expected: "Fizz"
got: ""
-
git commit
コマンドでコミットして、git push
コマンドでアップロードします。
$ git commit -am "Test example"
$ git push origin develop
- GitHub に Pull request を登録すると CircleCI と Code Climate の状態が表示されます。
- CircleCI は失敗が表示されます。
-
lib/my_gems.rb
ファイルに何かテストが成功するように実装します。(Fake It)
require 'my_gems/version'
module MyGems
class FizzBuzz
def initialize(number)
@number = number
end
def say
'Fizz'
end
end
end
-
lib/my_gems.rb
ファイルにいくつかのテストが成功するように実装します。(Refactoring)
require 'my_gems/version'
module MyGems
class FizzBuzz
def initialize(number)
@number = number
end
def say
@number == 3 ? 'Fizz' : 'Buzz'
end
end
end
-
lib/my_gems.rb
ファイルにリファクタリングを繰り返して、全てのテストが成功するように実装します。
require 'my_gems/version'
module MyGems
class FizzBuzz
def initialize(number)
@number = number
end
def say
return 'FizzBuzz' if (@number % 15).zero?
return 'Buzz' if (@number % 5).zero?
return 'Fizz' if (@number % 3).zero?
@number.to_s
end
end
end
-
git commit
コマンドでコミットして、git push
コマンドでアップロードします。
$ git commit -am "Pass a test"
$ git push origin develop
- GitHub の Pull request は自動で CircleCI と Code Climate の状態が更新されます。
- CircleCI がテストを成功しています。
- Github で
Merge pull request
をクリックすると Code Climate のカバレッジが更新されます。
- README に CircleCI と Code Climate の状態をバッジを表示することができます。
GitHub の gem を利用
- GitHub にアップロードされた gem は別のプログラムから読み込むことができます。
-
touch
コマンドで空ファイルを作成します。
$ mkdir my_app
$ cd my_app/
$ touch app.rb && touch Gemfile && touch Gemfile.lock
$ touch Dockerfile && touch docker-compose.yml
-
app.rb
ファイルに FizzBuzz を使うコードを記述します。
require 'my_gems'
fizz_buzz = MyGems::FizzBuzz.new(3)
puts fizz_buzz.say
-
Gemfile
ファイルに GitHub のパスを設定します。
gem 'my_gems', git: "https://github.com/your-name/my_gems.git", branch: :master
-
Dockerfile
ファイルに仮想環境で使う Ruby のバージョンを設定します。-
Gemfile
ファイルを仮想環境にコピーします。
-
FROM ruby:2.5.1
RUN mkdir /my_app
WORKDIR /my_app
COPY Gemfile /my_app/Gemfile
COPY Gemfile.lock /my_app/Gemfile.lock
RUN bundle install
COPY . /my_app
-
docker-compose.yml
ファイルに仮想環境のボリューム(フォルダ)とホストのマウントを設定します。
version: "3"
services:
app:
build: .
volumes:
- .:/my_app
-
bundle exec ruby app.rb
コマンドで Fizz が出力されます。
$ docker-compose run app bundle exec ruby app.rb
Fizz
-
Gemfile.lock
ファイルのremote
は GitHub を参照しています。
GIT
remote: https://github.com/your-name/my_gems.git
revision: aa1f28a6c59e09892340d32d8cad76bc5b2154a4
branch: master
specs:
my_gems (0.1.0)
RubyGems の gem を利用
- RubyGems にアップロードされた gem は別のプログラムから読み込むことができます。
-
Gemfile
ファイルに RubyGems のパスを設定します。
source "https://rubygems.org"
gem 'my_gems'
-
my_gems.gemspec
ファイルの allowed_push_host に URL を設定します。
spec.metadata["allowed_push_host"] = "https://rubygems.org/"
-
rake release
コマンドで gem を RubyGems にアップロードします。
$ rake release
my_gems 0.1.0 built to pkg/my_gems-0.1.0.gem.
Tagged v0.1.0.
Pushed git commits and tags.
Pushed my_gems 0.1.0 to rubygems.org
-
bundle exec ruby app.rb
コマンドで Fizz が出力されます。
$ docker-compose run app bundle
$ docker-compose build
$ docker-compose run app bundle exec ruby app.rb
Fizz
-
Gemfile.lock
ファイルのremote
は RubyGems を参照しています。
GEM
remote: https://rubygems.org/
specs:
my_gems (0.1.0)
-
lib/my_gems/version.rb
ファイルの0.1.0
の値が gem のバージョンです。- gem の内容を変更してバージョンアップするにはこのファイルを修正します。
完成したコード
- GitHub に my_gems のコードを公開しています。
参考
-
プロを目指す人のための Ruby 入門
- FizzBuzz プログラムの例題が掲載されています。