Index: [Article Count Order] [Thread]

Date:  Sat, 3 Jun 2000 11:56:00 +0900
From:  Yutaka Kamite <y-kamite@....jp>
Subject:  [XP-jp:00453] XP Installed 26 Test-first, by Intention  紹介
To:  extremeprogramming-jp@....jp (extremeprogramming-jp ML)
Message-Id:  <3937DD041BC.E60AY-KAMITE@....jp>
Posted:  Sat, 03 Jun 2000 01:12:52 +0900
X-Mail-Count: 00453

上手です。
XP Installed26 Test-first, by Intetion 紹介 です。
概要(とSmalltalkのコード)です。ちょと長いですが・・
Smalltalkコードの説明と、Javaコード化、よろしくお願いします。

誤りが沢山ありそうなので、指摘いただけば、すぐ修正します。
私の書いたSmalltalkに関するコメントは後で全部消します。

この例では、おそらく VisualWorksが動いていて、ワークスペース、(オブジェ
クトの)インスペクタ、デバッガのウィンドウが動的に動いていると思います。
40分位で終わったという時間感覚と、実際の動きを示すのが目的と感じました。
ですから、 Javaのコードは、動作を Java*表記*で表わすとこんな感じ、という
程度のスタンスで良いかもしれませんね。

---------------------------------------------
(#p189)
Chetと私(Ron)はテスト−ファーストの短いデモをしたいと思った。我々は顧客
のところであった実際の一つの問題を取り上げようと決めた。

この例には見るべき2つの重要なことがある。最初に、動かないテストを持って
初めてコードを書くこと。これを テスト−ファースト プログラミングという。

次に、どのようにこれを処理するかはほとんど考えていない、考えるのは何をす
べきか、ということです。我々はこれを 意向によるプログラミングと呼びます。
誰かがメソッドを書いていて、あなたはそれにメッセージを送るだけ、であるか
のようにコードを書きます。


タスクはこれです。
Sum オブジェクトの2つのコレクション(集まり)がある。一つのSumは name
(a String),amount(a number)を持つ。アウトプットは新しい Sumsの一つのコレ
クションであること。それぞれのコレクションに同じ名前のものが現れたら、合
計する。Sumが一つのコレクションにしかあらわれなかったら(#つまり新規)そ
のamountを持つ。出力の順番は最初のコレクションの順番で始め、次からはその
最初の中にないものが続く。例としては

(#p190)

first  second  結果

A1     A10     A11

C2     B3      C2
               B3


コードはゼロからつくることとする。
我々は新しいマシンを使う。そこで単純な Sum オブジェクトを定義することから
始める。
Smalltalk(#日常のちょっとした会話)を話さない人のためにちょっとコメント
をつける。

...Sum
10:25:00

この2行は、コードを追加しているクラスとそれをやった時間を示す。この目的
は、どれくらい時間がかかったかを示すためだ。

クラス定義です。

...Sum
10:25:00
Object subclass:#Sum
instance variables:'name amount'

これは、Sum という新しいクラスを定義し、インスタンス変数は name, amount 
です。Smalltalkでは、変数の型を定義する必要は無い。

では コンストラクタメソッドをつくろう。
メソッド定義は、1タブ空けたメソッド名で始まり、
Smalltalkのメソッド名は、1つ以上のキーワードを含む。

(#p191)
これ(メソッド名)は name:amount: だ。

メソッド自身(中身)は次の行からインデントされて始まる。

^self new
     setName:aString
     amount:aNumber

これは このクラス(Sum)の新しいインスタンスをつくり(self new)、それに
     setName:aString
     amount:aNumber
のメッセージを送ることです。

#注 "setName:" が引数がある場合のメソッド名。aString は引数のオブジェクト
#オブジェクトには a an をつけるようです
#コンストラクタは解りません
http://www.kk.iij4u.or.jp/~y-kamite/SmalltalkNotationForJavaProgrammer.html
#に簡単なまとめがあります

これは我々がコンストラクタ・パラメータ・メソッドと呼ぶものです。
キャレット(^)は”回答”、”returns"を意味します。

我々は各クラス定義を同じ様に始めます。コンストラクタとコンストラクタ・パ
ラメータ・メソッドです。皆さんもいつもそうすると、うまくいきます。

これがメソッドの全部です。

...Sum Class
10:25:00
name:aString amount:aNumber
    ^self new
         setName:aString
         amount:aNumber

#注:self はJavaの this
#name:引数 amount:引数 の筈?

次のステップはいつも、今メッセージを送ったコンストラクタ・パラメータ・メ
ソッドを定義することです。Smalltalkでは(普通)未定義のメソッドを使っても問
題ありません。

...Sum Class
10:25:15
setName:aString amount:aNumber
     name:=aString
     amount:=aNumber

#注 := は代入

(#p192)
このメソッドは二つのパラメータを対応するインスタンス変数に割当てます。
インスタンスは初期化されました。どうせ必要なのでアクセサもつくりましょう。
#以下??教えて下さい
厳密に言うと、これは誤りでしたが、このクラスをfrom memoryつくりました

...Sum
10:25:30
name
    ^name

このメソッドは name という名前で、name というインスタンス変数を返します。
(インスタンス変数)name のアクセサです。
#以下??教えて下さい
厳密に言うと、このクラスをfrom memory as a "given" としてつくったのでなけ
れば、これをいれるべきでない。

amount に対してもアクセサをつくる。

...Sum
10:25:40
amount
    ^amount

ウオームアップは十分だ。我々のオブジェクト、Summarizerと呼ぶ、を書こう。
最初に SummarizerTest と名付けるテストクラスを作ることから始めよう。

ペアプログラミングをやっている時の会話です。

Chet 最初に何をやろう
Ron 空のを作ろう。空のcollectionを返すやつだ。

Chetはテストを書く。彼はemptySummarizerと言う名前のメソッドがあることを仮
定している。これによりタスクに集中できた。

(#p193)

...SummarizeTest
10:32:08
testEmpty
    |summarizer|
    summarizer:=self emptySummarizer.

これは我々が"意向によるプログラミング”と呼ぶものだ。空のSummarizerをどう
やって作るか考え、たぶん次に書く代わりに、Chetは空のSummarizerを得るとい
う彼の意向を表現している。これによりコードはクリアになるが、より重要なの
は、今やっているメソッドのコーディングの間に、別の細かい事を考えることに
ギアをチェンジする必要がないので、動きがスムーズに保たれることだ。

さて、Chetは最初の should: を書く前に何をすべきか話あう。

Chet answerはどうやってもらう
Ron  Summarizer に "summarize" を送ろう
Chet "summary"はどうだろう
Ron  いいんじゃない

Summarizerのプロトコルのキー要素を定義していることに注意して下さい。送ら
れるメソッドは実際に実行されます。これをテストで実行することは大事です。
実際にオブジェクトを使うので、有用で明確なインターフェースを定義するチャ
ンスが多くなります。

...SummarizeTest
10:32:08
testEmpty
    |summarizer|
    summarizer:=self emptySummarizer.
    self should:[summarizer summary isEmpty]

上のコードは 一つのemptySummarizer(オブジェクト)をつくり、summaryが
empty(空)であるかどうかテストする。自然に我々はテストを走らす。動かない、
emptySummarizerが定義されていない。我々はわかっていた、しかしテストを走ら
す機会がある時はいつもテストするのが好きだ−それは持つと良い習慣だ。

(#p194)
Chet 取引(deals)をどうやって作る?
Ron  クラスを定義して、インスタンス変数を2つ、first と second
Chet いやな名前だね
Ron  同感、でも他にいいのを思いつかない。

..Summary
(#点が2つなのは誤植? ホソカワさん、質問すると喜ばれるかも)
10:32:45
Object subclass:#Summarizer
instance variables:'first second'


我々はすぐにテストに戻る。
#以下不明。コンストラクタをつくろうと言っているのか?

Ron  一つのemptySummarizer(オブジェクト)にはSumの2つのcollection
がいるね
Chet first:second:
Ron  with:with: はどうだい
Chet OK

with:with: という名前は引数を2つ持つ場合Smalltalkでは良い歴史を持ってい
ます。一方、この名前はあまり自己説明的(communicative)ではないので、Ronは
示唆を間違えたかもしれません。

Chetはメソッドを書き、2つの空白の配列(注 #() )を入れる。

..SummarizerTest
10:33:06
emptySummarizer
     ^Summarizer
         with:#()
         with:#()

もう一度テストを走らす、もちろんブレークする。Summarizerは with:with:を理
解しないから。
対応するコンストラクタ・パラメータ・メソッドと一緒に書く。いつもこうやっ
ているので、楽なことだ。

(#p195)
...Summarizer class
10:35:03
with:firstCollection with:secondCollection
      ^self new
           setFirstCollection:firstCollection
           secondCollection:secondCollection

...Summarizer class
10:35:54
setFirstCollection:firstCollection secondCollection:secondCollection
      first:=firstCollection
      second:=secondCollection

テストをまた走らす。今度はSummarizerがsummaryを理解しないのでブレークする。
Chetにはメソッドをどうするかのアイデアが無いので、halt(停止)をいれたも
のをつくり、デバッガにはいるためにテストを走らす。

...Summarizer
10:37:18
summary
    self halt

デバッガでちょっとばかりチェックする。
Chet OK、2つの入力コレクションは空だ。
Ron  何を答える。次に何をするかわからない。
Chet  これでうまくいくさ。

...Summarizer
10:38:11
summary
    ^first,second

Chetは二つの入力コレクションを連結して答えた。これはたぶん動くであろう最
も単純なことだ。
#以下意味不明
空のコレクションを書いてそれを返す方がより単純だが、Chetは変数に手をかけ
た。

(#p196)
我々の最初のテストは走った。オブジェクトは明らかに間違っているのに、それ
をフィックスするテストが無い。そこで、別のテストを書く。

Ron  私の書いた例をテストしよう。(テーブルの上にある)
Chet OK
Ron  これがこれだけ重要か彼らに言うのを覚えておこう。
Chet これは君が僕に教えてくれた一番重要なことの一つだ。
10秒前に誰かが私の必要とするメソッド、abcSummarizerメソッドのように、を
書いたらすぐそれを想定する(assume)コードを書くんだ。

ここでまた我々は意向について話し合う。我々は欲しいものについてはなす、ど
うやってそれをやるかではなく。欲しい物が判れば、実現するのは簡単だ。

...SummarizerTest
10:39:34
testABC
     "self unsafeRun:#testABC"
     |summarizer|
     summarizer:=self abcSummarizer.
     self should:[summarize summary size=3]

これが、意向によるプログラミングだ。Chetはテストオブジェクトをセットアッ
プするメソッドabcSummarizerを想定している。
次に、これに送られたsummaryが3要素のコレクションを確実に答えることをチ
ェックする単純なテストを書く。それはテストとしては十分ではないが、ブレー
クするには十分で、それが我々の必要としているものだ。メソッドをコピーした
ことを覚えておいて下さい−要素はまだいれない。

(#p197) 
..SummarizerTest
10:40:57
abcSummarizer
     ^Summarizer
         with:#()
         with:#()

Summarizerオブジェクトを作って、Chetはテストを強化する準備ができた。私は
まだ準備が出来ていない、しかし彼は出来ている、そこで彼が全ての値をチェッ
クするてめのテストをつくるのを見守る。

Ron もう選択はないね。コレクションを今構築しよう。
Chet まだやることがある。

..SummarizerTest
10:43:06
testABC
     "self unsafRun:#testABC"
     |summarizer summary|
     summarizer:=self abcSummarizer.
     summary:=summarizer summary.
     self should:[summary size=3].
     self should:[summary first name='a'].
     self should:[summary first amount=11].
     self should:[(summary at:2) name='c'].
     self should:[(summary at:2) amount=2].
     self should:[summary last name='b'].
     self should:[summary last amount=3].

...SummarizerTest
10:44:29
abcSummarrizer
     ^Summarizer
         with:self acCollection
         with:self abCollection

(#p198) 

Chetは考えるのに必要な時間をちょっと使い彼の意向をメソッドの中に宣言した。
つまり、aとc aとbを持つコレクションを一つづつ持つようにした。そてマジック
elf(self?:これも誤植かも>>ホソカワさん)がそれを創ったことを想定する。
さて、何が必要かは、名前が解っているので、はっきりしている。メソッドをタ
イプする。

 ...SummarizerTest
10:45:45
acCollection
     ^OrderdCollection
         with:(Sum
              name:'a'
              amount:1)
         with:(Sum
              name:'c'
              amount:2)

10:45:49
abCollectio
     ^OrderdCollection
         with:(Sum
              name:'a'
              amount:10)
         with:(Sum
              name:'b'
              amount:3)

よし、テストは書けた。走らそう。おっと。オリジナルsummaryメソッドは与えら
れた4つの要素を連結する、3つじゃない、だからテストはブレークする。でも驚
かない。

Chet OK、summaryを実際にやろう。どうやる?
Ron  firstコレクションにいって、その要素を全部 summaryコレクションに移そ
う。それからsecond コレクションで、要素がsummaryの中にあればそれを追加し
て、そうでなければ中にいれよう。(#意味不明)

(#p199) 

#ここ意味不明
Chet 2つのメソッドはだいたい似てる。それぞれのコレクションにあたって、
得た要素がsummaryの中にあればそれを追加し、そうでなければ新規につくるとい
うのはどうだい。
Ron  いいね。最初にfirst をプロセスして、次にsecondをプロセスしよう。

...Summarizer
10:50:29
summary
    self
         processFirst;
         processSecond

Chet 回答はどこで入力する?
Ron  新しいインスタンス変数をつくろう、summarizer
Chet OK、コンストラクタ・パラメータ・メソッドで初期化していい?
Ron  OK。

Chetはクラス定義にインスタンス変数を追加し、メソッドに空のOrderedCollectionを
追加する。

...Summarizer
10:51:01
setFirstCollection:firstCollection secondCollection:secondCollection
      first:=firstCollection
      second:=secondCollection
      summary:=OrderedCollection new

Ron  二つの違うプロセスメソッドを持つ必要はないね。process:メソッドを2回
使おう。

...Summarizer
10:51:55
summary
    self process:First.
    self process:Second

Ron  OK。プロセスを書こう。

(#p200) 

Chetはcollection にループを書く。ちょっと考えてから、processItem:の意向を
宣言することを思い出して、続ける。

...Summarizer
10:52:32
process:aCollection
     aCollection do:[:each|self processItem:each]

もう終了は近い。我々はテストを走らし、processItem:をまだ定義していないこ
とに気がついた。Chetはブランクメソッドをつくり、それから我々は話し合う。

...Summarizer
10:52:42
processItem:aSum

Ron  OK。ゴム舗装道路だ(#??)。どうする。
Chet えーと、summary の中で matchig Sum を見つけて・・
Ron  そして入力 Sum をそれに足し込む! そうしよう!

そのとおりコーディングして、テストを走らす。もちろん matchingSum は定義さ
れていない。

Ron  我々は summary を当たって、matching があるか見ないと・・
#以下のやりとりの意味不明
Chet 検出!
Ron  そう、検出・・
Chet そして ifAbsent:
Ron  inNone:
Chet どっちかわからない
Ron  新しい Sum をつくって summary に入力しよう

...Summarizer
10:54:22
processItem:aSum
     (self matchingSum:aSum)add:aSum

(#p201)
Chetはコードを書き上げる。これはかなり標準的なSmalltalkのイディオム(慣用
句)なので、彼は全てを一行に書く。コードの意味は、summary の中で同じ名前
の Sum を探して、もしあればそれに、なければ新しくその名前のものをつくって、
それに格納する。いずれにせよ、新しい Sum(の値が)が得られる。

...Summarizer
10:56:40
matchingSum:aSum
     ^summary
         detect:[:each|each name = aSum name]
         ifNone:[summary add:(Sum
              name:aSum name
              amount:0)]

テストを走らす。Sum が追加の方法を理解しなかったので、走らない。手早くそ
れをつくる。

...Sum
10:58:40
add:aSum
     amount:=amount + aSum amount

またテストを走らす。動かない。Sum のコレクションではなく、Summarizer が返
って来る。結果を回答するのを忘れているようだ。

Ron  Jeffriesのタイプ1エラーだ。コレクションを回答していない。パートナー
はどこ?
Chet ドライブ中。あなたがやって。

...Summarizer
10:59:31
summary
    self process:first.
    self process:second.
    ^summary

(#p202)

テストを走らす。それは動いた。短く祝福して、ベルがあったら鳴らすのにと思
う。それから、もう動いているので、クリーンアップのためにコードのレビュー
を始める。

Chet process はいい名前じゃないね。
Ron  summarizeとも呼べるね・・
Chet OK。

...Summarizer
11:04:45
summary
    self summarize:first.
    self summarize:second.
    ^summary

Chetはテストを走らす。ブレークする、summarize: メソッドが無い。 process:
メソッドをリネームする。


...Summarizer
11:04:53
summarize:aCollection
     aCollection do:[:each|self summarizeItem:each]

Chetはテストを走らし、それは動く。しか彼は新しいメソッドが気に入らない。

Chet おっと。processItem: を summarizeItem: に変えよう。
Ron いいね。

...Summarizer
11:05:30
summarize:aCollection
     aCollection do:[:each|self processItem:each]

彼は、summarize: が summarizeItem: を送るように変更する。

...Summarizer
11:05:30
summarize:aCollection
     aCollection do:[:each|self summarizeItem:each]

テストを走らす。Chetは summarizeItem: が定義されていないことを”発見”し、
processItem: を summarizeItem: にリネームする。

(#p203)
...Summarizer
11:05:42
summarizeItem:aSum
    (self matchingSum:aSum)add:aSum

もう一度テストを走らす。この時点で我々は自分達のやったことを見る、。問題なし。
昼食に行こう。

(以上)