ペアプロ二日目: かおるんさんと 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++ で使うビルドツールは sconswaf など選択肢が増えてきている。特に、 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 というファイルを作成して、そのファイルを変更していくようにした。

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」だ。

MoneyTest.cpp

#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」だ。

Money.hpp

class Dollar
{
public:

    Dollar( int amount ) : amount( 5 * 2 ) {
    }

    void times( int multiplier ) {
    }

    int amount;
};

なお、今回は、写経することが目的なのでヘッダファイルに実装も書いていったが、実際には、ソースコードに書くことになるだろう。

C++特有の部分に気を配る

テスト駆動開発入門』の Part 1 はJavaで書かれている。基本はこれをそのまま書き写せばよいのだけれど、使う言語によっては不自然になってしまうこともある。

C++を使っていると、 times や equals は operator* や operator== として書く方が自然な気がする。今回は、まず本のとおりに写経して、 ToDo リストにメンバ関数名の変更を記載。その後、コードを書き換えることにした。

ToDo (抜粋)

  hushCode()->C++いらない
  equalsをoperator==に変える
  timesをoperator*に変える

まとめ

こんな感じのことを繰り替えして Maney を作り上げていった。今回は時間の関係で途中までしか写経できなかった。また続きをやりたいと思う。

やはりこの本は写経する度に色々な発見がある。出会う度に再び TDD のリズムを思い出させてくれる、そんな本だ。

カニンガムはコードをビルドにチェックインし、上司に自信を持って報告した。(ケントベック『テスト駆動開発入門』)

僕らも github にペアで書いたコードを push した。 git-now によって記録された一歩一歩の軌跡が上への報告の代わりだ。これでまた少し、自信をもって TDD ができるかもしれない。

https://github.com/toshi-kawanishi/money-cpp-gtest

*1:きちんとやるのであれば compilation mode に ansi-color を設定するなどの方法があるが、ここでは、とりあえず、この形で色を付けている。

ペアプロ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 は使っていないが、一応追加してある。

miniblog/Gemfile

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 に設定を追記した。

miniblog/config/application.rb

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 を許さないように書き換える。

miniblog/db/migrate/20101030044650_create_posts.rb

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

その上で、バリデーションのスペックを定義した。

miniblog/db/spec/models/post_spec.rb

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

ここでは、以下のフィクスチャを使っている。

miniblog/spec/fixtures/posts.yml

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 にするためにバリデーションを有効にする。そのために、モデルを書き換えた。

miniblog/app/models/post.rb

class Post < ActiveRecord::Base
  validates_presence_of :title
  validates_presence_of :content
end

ここまでは、記事と同じ道を歩んできたことになる。

サーバを実行し、空のデータを入れてみると実際の画面はこのようになった。ちゃんと、バリデーションが効いているようだ。

$ rails s


続いて Model のロジック

続いて、本日のメインディッシュである、ロジックを記述していく。今回は、 TDD でペアプロをするということが第一の主眼なので、以下のように簡単な仕様とした。

  • ブログ本文はアルファベットで書かれ、単語は空白によって区切られる
  • 句読点を削除したりなどといった処理はしない
  • 大文字と小文字が違うと違う単語とする

また、今回は、表示の度に頻出単語の解析を毎回おこなうことにした。

部分のテストだ。まずは、 Blog.count_wards() のスペックを定義する。そのまま、仮実装から三角測量に進もうとするも、もしかしたら、一歩目が大きいかもということで、いったん、 pending し、とりあえず、 Blog.split_content() を作成することにした。

結局、モデルのコードは以下のような感じになった。

miniblog/spec/models/post_spec.rb

# -*- 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

miniblog/app/models/post.rb

# -*- 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 のスペックは、こんな感じだ。

miniblog/spec/views/posts/show.html.erb_spec.rb

# -*- 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 している。

http://github.com/tosikawa/mini-blog

まとめ

事前に、 Jxck さんと確認した、「今回の一番の目的はTDDでペアプロ」という部分は、なんとか達成できたと思う。そして、Rails 3 のテストのしやすさにも、ふれることができた。

Jxck さんご提案の KPTChangeLog 形式で書くのも良かった。その KPT の中に、「"やること"は決めておくとすんなり始められて達成感もある」ということを Jxck さんが書いてくださった。決めておいたことが出来たことと、達成感を共有できたことが何よりも良かった。

やっぱり、ペアプロって楽しいと思う。「ペアプロの楽しさコツを伝えていきたい」という Jxck さんのつぶやきが胸に響く。

あとは、直接、内容とは関係ないけれども、gitについて色々議論した。 git スゲェな、ということで合意できたと思う。

あっという間だった。いつの間にか台風は過ぎ去り、熱帯低気圧となっていたようだ。

*1:自分の Ubuntu 上で動作させようと思ったときに、webrat のコンパイルで少しつまずいた。 libxml2-dev と libxslt1-dev が必要なようだ。

ペアプロ日記を始めます

こんにちは。川西と申します。

本日よりペアプロ日記というのを付けて見ようと思います。色々な方とペアプロをして、気づいたことをまとめていければなと思います。

イベントで話を聞いたり、グループワークをするのも良いのですが、「我以外、皆師匠」というつもりで、お相手してくださった方々から、色々、勉強させていただけたらなと思います。

もし、お相手してくださる方がいらっしゃったら、是非、川西までご連絡いただけるとありがたいです。

http://twitter.com/tosikawa