ペアプロ二日目: かおるんさんと googletest でペアプロ
はじめに
ある金曜日、上司がWyCashという債券ポートフォリオ管理システムの見込み客であるピーターを紹介しようとワード・カニンガムのところにやって来た。ピーターは「これらの機能にとても感銘しました。しかし、御社は米ドル建て債券しか取り扱わないことに気付きました。新しい債券ファンドを始めようとしているのですが、戦略上、異なる通貨の債券を取り扱う必要があります」と言った。上司はカニンガムに向かって、「どうだい、できるかね」と言った。(ケントベック『テスト駆動開発入門』)
僕らも、集合場所にやってきた。できるかどうかの質問には明確に答えられない。ただ、やってみようと思う。集合場所は分倍河原。そこには今回のお相手のかおるんさんが待っている。今日も新たにペアプロに挑戦だ。
今回のお題
今回は事前に twitter の Direct Message で話して、ケントベックの『テスト駆動開発入門』のコードを書き写そうということになった。写経って呼ばれているヤツだ。
『テスト駆動開発入門』は TDD のバイブル的位置付けの本だ。この本を写経したことのない人がやっていることは TDD ではないと言ってもいいくらい、 TDD を実践するにあたっては大事な本だ。自分も折々、色々な言語でこの本を写経していて、この本を大切にしている。
TDDの伝道師こと t-wada さんも言っている。
『テスト駆動開発入門』をペアプロで写経。「どうだい、できるかね」と、サンプルコードが語るとおり、TDDを学んでいく。これが今回のお題だ。
今回のお相手
今回のお相手は、かおるんダイアリーが有名な、かおるんさんだ。TracやSCRUMなどのトピックで色々活動されている方だ。また、TDD Boot Camp 名古屋以降、東のイケメンということで名が通っている。
実はかおるんさんとは、以前も密会と称して、居酒屋でペアプロをしたことがある。今思えば、これがペアプロ日記の原点となっているのかもしれない。そんな想いもあり、第二回は、かおるんさんにお願いすることにした。
今回の道具
C++ & googletest
今回は googletest を使って C++ でプログラミングをした。
googletest は の C++ 用のユニットテスティングフレームワークで、 Google 謹製だけあって GoogleTest は素敵な感じに仕上がっている。また、最近はマニュアルの日本語訳も登場していて、一層、導入しやすい環境になってきている。
make
C++ で使うビルドツールは scons 、 waf など選択肢が増えてきている。特に、 make では工夫しないとできないヘッダファイルの依存関係の解決などをやってくれたりして、後発のツールに乗り換える人も多いようだ。特に、 TDD のリズムを作る場合は、関連するヘッダファイルが書き換わったことをビルドツールが感知してくれることは重要になる。
ただ今回は、これらのツールを使うのではなく、ヘッダファイルの依存関係を解決できるように Makefile を作成して、 make を使用することにした。
CXX = g++ CXXFLAGS = -O2 LDFLAGS = -lpthread -lgmock_main -lgtest -lgmock SRCS_ALL = ${wildcard *.cpp} SRCS_TEST = ${wildcard *Test.cpp} SRCS = ${filter-out ${SRCS_TEST}, ${SRCS_ALL}} OBJS_ALL = ${SRCS_ALL:.cpp=.o} OBJS = ${SRCS:.cpp=.o} BINS_TEST = ${SRCS_TEST:.cpp=.bin} TESTS = ${SRCS_TEST:.cpp=} all : test test : ${TESTS} .SUFFIXES : .cpp .make .cpp.make : ${CXX} ${CXXFLAGS} -MM -MG $< > $@ echo " ${CXX} ${CXXFLAGS} -c $< -o $*.o" >> $@ ${OBJS_ALL} : %.o: %.make make -f $< $@ rm -f $< ${BINS_TEST} : %.bin : %.o ${OBJS} ${CXX} ${OBJS} $< ${CXXFLAGS} ${LDFLAGS} -o $@ ${TESTS} : % : %.bin ./$< --gtest_output=xml:$@.report.xml clean : rm -f *.a rm -f *.o rm -f *.make rm -f *Test.bin rm -f *Test.report.xml
emacs
エディタは emacs を使った。 googletest が emacs 形式のエラーメッセージを出力してくれるのも心地よい。以下のような設定を .emacs に追加した。
(global-set-key "\M-m" 'compile) (global-set-key [M-down] 'next-error) (global-set-key [M-up] '(lambda () (interactive) (next-error -1))) (defface gtest-face-green '((t (:foreground "green"))) nil) (defface gtest-face-red '((t (:foreground "red"))) nil) (defvar gtest-face-green 'gtest-face-green) (defvar gtest-face-red 'gtest-face-red) (defadvice font-lock-mode (before my-font-lock-mode ()) (font-lock-add-keywords major-mode '(("[[]==========[]]" 0 gtest-face-green append) ("[[]----------[]]" 0 gtest-face-green append) ("[[] RUN []]" 0 gtest-face-green append) ("[[] OK []]" 0 gtest-face-green append) ("[[] PASSED []]" 0 gtest-face-green append) ("[[] FAILED []]" 0 gtest-face-red append) ))) (ad-enable-advice 'font-lock-mode 'before 'my-font-lock-mode) (ad-activate 'font-lock-mode)
ここでは
- Meta-m で make が走る
- Meta-↑ / Meta-↓ でエラー箇所にジャンプする
- テストの結果に色をつける*1
というような設定をしている。実際にテストを実行させるとこんな感じだ。
git-now
git-now は sinsoku_listy さんが考案した、テンポラリ用のコミットを作成するためのコマンドだ。自分も実装をお手伝いして Gist に登録した。使い方は sinosku_listy さんのブログで紹介されている。
実際の作業
まずは ToDo リスト
『テスト駆動開発入門』では、何をしているかを明確にするために ToDo リストを作成している。今回の作業でもそれにしたがって、 ToDo リストを作成した。今回は ToDo というファイルを作成して、そのファイルを変更していくようにした。
レートが2:1の場合、 $5 +10 CHF = $10 $5*2=$10
一歩進むごとに git-now
TDDの学習の時には一歩一歩何をやったかを記録していきたい。そこで役に立つのが git-now だ。一歩進むごとに git-now 。コマンドラインで「 git now 」と入力するたびに、変更が記録されていく。「ここで git-now 」。これが僕等の心地よいリズムとなった。
テストを書く
TDDではまずテストコードを書く。『テスト駆動開発入門』にあるとおり、 testMultiplication から書き始めた。もちろん、ここでもテストを書いて「ここで git-now」だ。
#include <gtest/gtest.h> class MoneyTest : public ::testing::Test { }; TEST_F( MoneyTest, testMultiplication ) { Dollar five; five.times( 2 ); EXPECT_EQ( 10, five.amount ); }
プロダクトコードを書く
テストコードに対応するプロダクトコードを書いていく。 testMultipication に対応して、 times メソッドを作成した。もちろん、ここでもコードを書いて「ここで git-now」だ。
class Dollar { public: Dollar( int amount ) : amount( 5 * 2 ) { } void times( int multiplier ) { } int amount; };
なお、今回は、写経することが目的なのでヘッダファイルに実装も書いていったが、実際には、ソースコードに書くことになるだろう。
まとめ
こんな感じのことを繰り替えして Maney を作り上げていった。今回は時間の関係で途中までしか写経できなかった。また続きをやりたいと思う。
やはりこの本は写経する度に色々な発見がある。出会う度に再び TDD のリズムを思い出させてくれる、そんな本だ。
僕らも github にペアで書いたコードを push した。 git-now によって記録された一歩一歩の軌跡が上への報告の代わりだ。これでまた少し、自信をもって TDD ができるかもしれない。
ペアプロ1日目: Jack さんと rails 3 と rspec と
はじめに
10月30日土曜日、その日は朝から雨風が強かった。日本付近を台風が通りすぎており、週末は大変そうだという話題が、数日前から twitter のタイムライン上に流れていた。
そんな中、ぼくらは秋葉原の喫茶店に集合し、4人掛けの席のとなりに座って何やら話を始めた。喫茶店で、男どうしが同じ側に座って、一台のパソコンの画面を眺めている。ちょっと不思議な光景かもしれない。けれども、これがぼくらのペアプロのスタイルだ。
今回のお相手
今回お相手してくださったのは、Jxck さん。86世代よりちょっと年上の方だ。Jxck さんとの出会いは、今年の XP 祭り。ランチをご一緒させていただいたのと、懇親会で立ちながら語りあったのを覚えている。
XP 祭りでの懇親会で、すごく印象に残ったのが、彼の情熱だ。まず、ペアプロの楽しさを伝えたいという事がビンビンと伝わってきた。さらに、その楽しさというのは、その道の権威が前で偉そうに話す事で伝わるのではなくて、現場の人たちが草の根的に伝わっていくものだ、ということを熱く語ってくれた。その後の twitter での Jxck さんの一言も素晴らしい。
ペアプロ日記をつけるなら、初回のお相手は彼にお願いしなければならない。そう思わずにいられないアツい人だ。もう既に、イベントで知り合った方々と、個人的に何度かペアプロをしているらしい。頼もしい限りだ。
今回のお題
Rails 3 で簡単なブログを作成することにした。まずはチュートリアルにあるようなアプリをつくること、ただ、それだけだと TDD をするのにもの足りないので、何かロジックを入れようということを、事前に Skype で話し合って決めた。
具体的には、まずは、編集ー確認ー表示といった簡単な構成で簡単なブログを作成する。そして、ポストを解析して頻出単語を表示するといったロジックを追加してみる、といった感じにまとまった。
出来上がったスペックは、以下のような感じだ。
ここでは、事前に話し合ったロジックの追加の他に、バリデーションのスペックも追加してある。
使った道具
事前に、rvm で、Jxck さんの Mac Book に、ruby 1.9.2、rails 3 を入れておいてもらった。
その上に Rails 3 のバンドルとして、
を追加した。
また、ソースコードのバージョン管理には、 git を使った。
Jxck さんの Mac Book 上の emacs で作業した。あとは、Jxck さんが用意してくださった、ホワイトボードが大変に役に立った。
実際の作業
アプリの作成、前準備
まずは、アプリを作成する。今回は rspec を使用するので、 "-T" を指定して、テストを作成しないようにした。
$ rails new miniblog -T
ここで、 Gemfile を書き換えた。結局、今回は autotest は使っていないが、一応追加してある。
source 'http://rubygems.org' gem 'rails', '3.0.1' gem 'sqlite3-ruby', :require => 'sqlite3' group :development, :test do gem 'rspec','>=2.0.0' gem 'rspec-rails','>=2.0.0' gem 'autotest' gem 'webrat' end
バンドルのインストール。
$ bundle install
その後、 rspec のヘルパを追加する。
$ rails g rspec:install
続いて、以下のように config に設定を追記した。
require File.expand_path('../boot', __FILE__) require 'rails/all' # If you have a Gemfile, require the gems listed there, including any gems # you've limited to :test, :development, or :production. Bundler.require(:default, Rails.env) if defined?(Bundler) module Miniblog class Application < Rails::Application # (省略) # Configure rspec config.generators do |g| g.test_framework :rspec, :fixture => true end end end
この辺りは、 ukstudio さんや Masatomo Nakano さんのブログを参考にしたように記憶している。
これで、準備完了だ。
Scaffold
scaffold 前にコミットを作成しておいた。そうすることで、やり直しがきく。実際に、今回も scaffold を一回やり直すことになり、バージョン管理の恩恵にあづかることができた。
$ rails g scaffold post title:string content:text
scaffold を作った後に、dbをマイグレーションする。
$ rake db:migrate
この時点で、既にブログの投稿はできるようになっている。
はじめの一歩
いよいよ、このペアでの初めてのコーディング。
はじめの一歩として、まずは、ペアの歩調を合わせるために、何かの資料をもとにテストを書いてみようということになった。そこで、登場したのが、かくたにさん、もろはしさんの 「スはスペックのス」。るびまに載ったこの記事は、日本語で rails + rspec の事について調べるのであれば一度は行き着くものだろう。この記事には、モデルのテストの例としてバリデーションのテストが書かれている。まずは、この記事と同じ事をやるのを、はじめの一歩としよう、ということになった。
まずは、マイグレーションファイルを、タイトル、本文ともに null を許さないように書き換える。
class CreatePosts < ActiveRecord::Migration def self.up create_table :posts do |t| t.string :title, :null => false t.text :content, :null => false t.timestamps end end def self.down drop_table :posts end end
そして、データベースをマイグレートする。
$ rake db:migrate
その上で、バリデーションのスペックを定義した。
describe Post, "モデル" do describe "バリデーション" do fixtures :posts before(:each) do end describe "タイトルも本文も入っている時" do it "成功する" do post = posts(:full) post.should be_valid end end describe "タイトルも本文も入っていない時" do it "失敗する" do post = posts(:empty) post.should_not be_valid end end describe "タイトルだけ入っている時" do it "失敗する" do post = posts(:title_only) post.should_not be_valid end end describe "本文だけ入っている時" do it "失敗する" do post = posts(:content_only) post.should_not be_valid end end end end
ここでは、以下のフィクスチャを使っている。
full: title: "test" content: "This is a test post." empty: title: content: title_only: title: "test" content: content_only: title: content: "This is a test post."
このスペックを実行すると Red になるので、これを Green にするためにバリデーションを有効にする。そのために、モデルを書き換えた。
class Post < ActiveRecord::Base validates_presence_of :title validates_presence_of :content end
ここまでは、記事と同じ道を歩んできたことになる。
サーバを実行し、空のデータを入れてみると実際の画面はこのようになった。ちゃんと、バリデーションが効いているようだ。
$ rails s
続いて Model のロジック
続いて、本日のメインディッシュである、ロジックを記述していく。今回は、 TDD でペアプロをするということが第一の主眼なので、以下のように簡単な仕様とした。
- ブログ本文はアルファベットで書かれ、単語は空白によって区切られる
- 句読点を削除したりなどといった処理はしない
- 大文字と小文字が違うと違う単語とする
また、今回は、表示の度に頻出単語の解析を毎回おこなうことにした。
部分のテストだ。まずは、 Blog.count_wards() のスペックを定義する。そのまま、仮実装から三角測量に進もうとするも、もしかしたら、一歩目が大きいかもということで、いったん、 pending し、とりあえず、 Blog.split_content() を作成することにした。
結局、モデルのコードは以下のような感じになった。
# -*- coding: utf-8 -*- require 'spec_helper' describe Post, "モデル" do describe "バリデーション" do fixtures :posts before(:each) do end describe "タイトルも本文も入っている時" do it "成功する" do post = posts(:full) post.should be_valid end end describe "タイトルも本文も入っていない時" do it "失敗する" do post = posts(:empty) post.should_not be_valid end end describe "タイトルだけ入っている時" do it "失敗する" do post = posts(:title_only) post.should_not be_valid end end describe "本文だけ入っている時" do it "失敗する" do post = posts(:content_only) post.should_not be_valid end end end describe Post, ".count_words()" do fixtures :posts before(:each) do end describe "本文に hoge が20回含まれる時" do it "1単語ぶんの配列を返す" do post = posts(:hoge_20) actual = post.count_words() expected = Array( "hoge" => 20 ) actual.should == expected end end describe "本文に hoge が15回, fuga が5回含まれる時" do it "2単語ぶんの配列を返す" do post = posts(:hoge_fuga) actual = post.count_words() expected = Array( "hoge" => 15, "fuga" => 5 ) actual.should == expected end end describe "本文に rails が4回, ruby が3回, on が2回, nutshell が1回含まれる時" do it "先頭3単語ぶんの配列を返す" do post = posts(:short_content) actual = post.count_words() expected = Array( "rails" => 4, "ruby" => 3, "on" => 2 ) actual.should == expected end end end describe Post, ".split_content()" do fixtures :posts before(:each) do end it "本文に含まれる単語をソートして返す" do post = posts(:short_content) actual = post.split_content() expected = ["nutshell", "on", "on", "rails", "rails", "rails", "rails", "ruby", "ruby", "ruby"] actual.should == expected end end end
# -*- coding: utf-8 -*- class Post < ActiveRecord::Base validates_presence_of :title validates_presence_of :content def count_words words = split_content word_counts = {} words.map do |w| if word_counts[w] word_counts[w] += 1 else word_counts[w] = 1 end end results = word_counts.sort do |e1, e2| e1[1] <=> e2[1] end results.reverse[0,3] end def split_content content.split.sort end end
最後に View のテスト
View のスペックは、こんな感じだ。
# -*- coding: utf-8 -*- require 'spec_helper' describe "posts/show.html.erb" do before(:each) do @post = assign(:post, stub_model(Post, :title => "short_content", :content => "on rails ruby ruby rails rails ruby rails on nutshell" )) end it "頻出単語を表示する" do render rendered.should have_selector('ul') rendered.should have_selector('li') rendered.should have_selector('span') rendered.should contain("ruby") rendered.should contain("rails") rendered.should contain("on") rendered.should contain("(4)") rendered.should contain("(3)") rendered.should contain("(2)") end end
とりあえず完成!
ここまでで、事前に打ち合わせした機能はひととり完成した。 rails 公式サイトのトップページの英文テキストを投稿してみたところ、以下のように表示された。やはり、 "rails" が再頻出単語のようだ。
なお、今回のコードは Jxck さんが github 上に push してくださった。自分はそれを fork している。
まとめ
事前に、 Jxck さんと確認した、「今回の一番の目的はTDDでペアプロ」という部分は、なんとか達成できたと思う。そして、Rails 3 のテストのしやすさにも、ふれることができた。
Jxck さんご提案の KPT を ChangeLog 形式で書くのも良かった。その KPT の中に、「"やること"は決めておくとすんなり始められて達成感もある」ということを Jxck さんが書いてくださった。決めておいたことが出来たことと、達成感を共有できたことが何よりも良かった。
やっぱり、ペアプロって楽しいと思う。「ペアプロの楽しさコツを伝えていきたい」という Jxck さんのつぶやきが胸に響く。
あとは、直接、内容とは関係ないけれども、gitについて色々議論した。 git スゲェな、ということで合意できたと思う。
あっという間だった。いつの間にか台風は過ぎ去り、熱帯低気圧となっていたようだ。