新訳版『テスト駆動開発』の第Ⅰ部の多国通貨をネタに Smalltalk を学ぶシリーズの第7回です。同書と似た仕様を目指しますが、好みを優先する等の都合で写経にはなっていませんのでどうぞあしからず。(12/24追記:なるべく忠実な写経は別に書きました→こちら )
前回 は、同書の流れからは外れ、換算レートのオブジェクト化(MoneyXRate, money exchange rate)とそれを用いた換算を試しました。
今回は、第17章でちょっとだけ触れられている“MoneyBag”を、きっとこんな感じなのではないかな…と勝手な想像を巡らしながら実装してみます。
#####*参考: 多国通貨実装のおおまかな流れ* 1. Dollar を定義 1. Dollar をコピーして Franc を定義 1. Dollar、Franc の重複を新しく作ったスーパークラスの Money にプルアップ 1. **Sum(Expression)、Bank を定義して reduce を実現** ← 今ここ
同書を読んでいない方向けに補足:Sum(当シリーズでは MoneyBag)は換算前の異種通貨同士の加算(式, expression)の保持を、Bank は異種通貨間の換算レートの管理、および、Sum の換算を伴う簡約(reduce)それぞれ担当します。
Bank に通貨換算レートの登録と問い合わせの機能を持たせる
TODO リスト
- 5 USD + 10 CHF = 10 USD (レートが 2 : 1 の場合)
- 10 CHF * (1 USD / 2 CHF) = 5 USD
- 2 USD / 2 = 1 USD
- 2 USD / 1 USD = 2
- 1 USD / 2 CHF は MoneyXRate を返す
- Bank>>addXRate: 、xRateOf:
前回、通貨と換算レートで換算は可能になりましたが、換算レートは本来 Bank に管理される情報なので、そこに蓄積・問い合わせが可能になるようにします。
テストを書きます。
MoneyTest >> testBankSettingGettingXRate
| xRate bank |
xRate := 1 USD / 2 CHF.
bank := Bank new.
bank addXRate: xRate.
self assert: (bank xRateOf: xRate fromToCurrency) equals: xRate
コンパイル(Accept)に際し、Bank の定義を求められますが Define new calss を選んで、下のクラス定義式のまま OK します。
Object subclass: #Bank
instanceVariableNames: ''
classVariableNames: ''
package: 'TDD-Money'
MoneyText 全テストを走らせる(○印クリック)と結果はレッド(エラー)になるので、
続けて、testBankSettingGettingXRate のみを走らせて、指摘された未定義メソッドごとに Create を押して定義してゆきます。。
addXRate: は Bank に定義、プロトコルは adding にでもしておきましょう。メソッドは空でもよいのですが、adding 系のメソッドは add した要素をそのまま返すのが慣習なのでとりあえずそうしておきます。
Bank >> addXRate: aMoneyXRate
^ aMoneyXRate
fromToCurrency は MoneyXRate に定義します。fromCurrency と toCurrency は実装済みなので表現できなくもないですが、fromToCurrency 自体もあった方が便利ですね(と、しれっと言う)。
プロトコルは accessing です。前回の xRate 同様、インスタンス変数名にメソッド名が一致するので自動でそれを返すコードにしてくれていますので、そのまま Proceed します。
MoneyXRate >> fromToCurrency
^ fromToCurrency
次に未定義を指摘される xRateOf: は Bank に、プロトコルは accessing で定義します。テストを失敗させるため、1 USD / 1 CHF を返すコードにするのをお忘れなく。
Bank >> xRateOf: anAssociation
^ 1 USD / 1 CHF
全テストを実行して失敗(イエロー)を確認できたら 1 USD / 2 CHF を返す仮実装に変えてテストを通します。
Bank >> xRateOf: anAssociation
^ 1 USD / 2 CHF
Bank>>addXRate: 、xRateOf: は、辞書をインスタンス変数 xRateDict として持たせておき、それに対して格納と問い合わせることで実現しましょう。
Bank >> addXRate: aMoneyXRate
^ xRateDict at: aMoneyXRate fromToCurrency put: aMoneyXRate
コンパイルに際して xRateDict の未宣言を指摘されるので Declare new instance variable します。
忘れないうちに、Bank の右クリック → Analyze → Generate initialize method で初期化メソッドを定義し、Dictionay new をセットしておきます。
Bank >> initialize
super initialize.
xRateDict := Dictionary new.
xRateOf: は引数として与えられた「換算前の通貨 -> 換算後の通貨」を保持する key-value 値(anAssociation)をキーにして、辞書 xRateDict から該当する換算レート(MoneyXRate)を探して返します。同種通貨換算の場合(anAssociation の key と value が一致する場合)は通貨変換は不要なので 1 を返します。
Bank >> xRateOf: anAssociation
anAssociation key == anAssociation value ifTrue: [ ^ 1 ].
^ xRateDict at: anAssociation
テストはグリーンのままです。
TODO リスト
- 5 USD + 10 CHF = 10 USD (レートが 2 : 1 の場合)
- 10 CHF * (1 USD / 2 CHF) = 5 USD
- 2 USD / 2 = 1 USD
- 2 USD / 1 USD = 2
- 1 USD / 2 CHF は MoneyXRate を返す
- Bank>>addXRate: 、xRateOf:
通貨変換を Bank に任せる
TODO リスト
- 5 USD + 10 CHF = 10 USD (レートが 2 : 1 の場合)
- 10 CHF * (1 USD / 2 CHF) = 5 USD
- 2 USD / 2 = 1 USD
- 2 USD / 1 USD = 2
- 1 USD / 2 CHF は MoneyXRate を返す
- Bank>>addXRate: 、xRateOf:
- Bank>>exchange: to:
testExchangeMoney を書き換えます。
MoneyTest >> testExchangeMoney
| bank |
bank := Bank new.
bank addXRate: 1 USD / 2 CHF.
self assert: (bank exchange: 10 CHF to: #USD) equals: 5 USD
reduce というのはピンと来なかったので(知らないだけで、通貨換算をそう表現する言い回しがあるのかもしれませんが…)ここではテストのメソッド名は testReduceMoney には変えずに前回のとおり testExchangeMoney のままで、Bank のメソッド名も exchange: to: にしてみました。
コンパイルは通りますが、テストはレッド(エラー)です。testExchangeMoney のみ実行して exchange: to: を Bank に追加します。プロトコルは適当に exchanging とでもします。
Bank >> exchange: aMoney to: toCurrency
^ 1 USD
テストを失敗させるため、返値は 1 USD にします。
失敗が確認できたら、5 USD を返す仮実装に書き換えます。
Bank >> exchange: aMoney to: toCurrency
^ 5 USD
重複の排除はこのようにします。
Bank >> exchange: aMoney to: toCurrency
^ aMoney * (self xRateOf: aMoney currency -> toCurrency)
TODO リスト
- 5 USD + 10 CHF = 10 USD (レートが 2 : 1 の場合)
- 10 CHF * (1 USD / 2 CHF) = 5 USD
- 2 USD / 2 = 1 USD
- 2 USD / 1 USD = 2
- 1 USD / 2 CHF は MoneyXRate を返す
- Bank>>addXRate: 、xRateOf:
- Bank>>exchange: to:
同種通貨同士の加算
TODO リスト
- 5 USD + 10 CHF = 10 USD (レートが 2 : 1 の場合)
- 10 CHF * (1 USD / 2 CHF) = 5 USD
- 2 USD / 2 = 1 USD
- 2 USD / 1 USD = 2
- 1 USD / 2 CHF は MoneyXRate を返す
- Bank>>addXRate: 、xRateOf:
- Bank>>exchange: to:
- 3 USD + 4 USD = 7 USD
次に異種通貨同士の加算の準備として、同種通貨の加算を実装します。まずはテストです。
MoneyTest >> testSimpleAddition
self assert: 3 USD + 4 USD equals: 7 USD
全テストを実行するとエラー(レッド)になるので、
testSimpleAddition を個別に実行して Money>> + をプロトコル arithmetic に追加します。仮引数は addend に変更し、テストを失敗させるためにメソッド本体は空にしてコンパイルします(self が返ります)。
Money >> + addend
仮実装として、7 USD が返るように Money>> + を書き直してコンパイルします。
Money >> + addend
^ 7 USD
重複の排除は次のようにします。
Money >> + addend
^ amount + addend amount perform: currency
TODO リスト
- 5 USD + 10 CHF = 10 USD (レートが 2 : 1 の場合)
- 10 CHF * (1 USD / 2 CHF) = 5 USD
- 2 USD / 2 = 1 USD
- 2 USD / 1 USD = 2
- 1 USD / 2 CHF は MoneyXRate を返す
- Bank>>addXRate: 、xRateOf:
- Bank>>exchange: to:
- 3 USD + 4 USD = 7 USD
異種通貨の加算は MoneyBag を返す
TODO リスト
- 5 USD + 10 CHF = 10 USD (レートが 2 : 1 の場合)
- 10 CHF * (1 USD / 2 CHF) = 5 USD
- 2 USD / 2 = 1 USD
- 2 USD / 1 USD = 2
- 1 USD / 2 CHF は MoneyXRate を返す
- Bank>>addXRate: 、xRateOf:
- Bank>>exchange: to:
- 3 USD + 4 USD = 7 USD
- 5 USD + 10 CHF は MoneyBag を返す
では次に、異種通貨の加算の場合です。いよいよ MoneyBag の登場です。まずテストを書きます。
MoneyTest >> testPlusDifferentCurrencyReturnsMoneyBag
| sum |
sum := 5 USD + 10 CHF.
self assert: sum class equals: MoneyBag
コンパイルに際して、MoneyBag の定義を要求されますので Declear new class を選んでそのまま OK します。
Object subclass: #MoneyBag
instanceVariableNames: ''
classVariableNames: ''
category: 'TDD-Money'
全テスト実行すると失敗するので、Money>> + を次の仮実装に書き換えてコンパイルし直します。
Money >> + addend
addend currency ~~ currency
ifTrue: [ ^ MoneyBag new ].
^ amount + addend amount perform: currency
テストは通りますが、まだ 何の情報も持たない MoneyBag が返ってきているだけです。
MoneyBag は elements として加算の要素を持ちます。set で始まるコンストラクターパラメーターメソッド(ベパp27)を private プロトコルに追加し、インスタンス生成時に使いましょう。
MoneyBag >> setElements: aCollectionOfMoney
elements := aCollectionOfMoney
コンパイル時に elements が未宣言であることを指摘されますので、Declare new instance variable でインスタンス変数に追加してもらいます。
ゲッターも定義しておきましょう。普通にコード枠に次のコードを入力してコンパイルしてもよいですし、MoneyBag を右クリック → Refactoring → Inst Var Refactoring → Accessors → elements をクリック → Ok で自動生成するのもよしです。
MoneyBag >> elements
^ elements
あと、private な setElements: を MoneyBag の外で使わずに済むように、MoneyBag class(MoneyBag のメタクラス)に withAll: というインスタンス生成用のクラスメソッドも定義しておきます。
クラスブラウザのクラスリスト枠下にある Class ボタンをクリックしてクラスサイドに切り替え、プロトコルは instance creation を設け、そこに定義します。
MoneyBag class >> withAll: aCollectionOfMoney
^ self new setElements: aCollectionOfMoney; yourself
再び Class ボタンを押してインスタンスサイドに忘れずに戻しておきます。
準備が整ったので MoneyBag 生成時に必要な情報を付加するように Money>> + を書き換えます。
Money >> + addend
addend currency ~~ currency
ifTrue: [ ^ MoneyBag withAll: {self. addend} ].
^ amount + addend amount perform: currency
TODO リスト
- 5 USD + 10 CHF = 10 USD (レートが 2 : 1 の場合)
- 10 CHF * (1 USD / 2 CHF) = 5 USD
- 2 USD / 2 = 1 USD
- 2 USD / 1 USD = 2
- 1 USD / 2 CHF は MoneyXRate を返す
- Bank>>addXRate: 、xRateOf:
- Bank>>exchange: to:
- 3 USD + 4 USD = 7 USD
- 5 USD + 10 CHF は MoneyBag を返す
異種通貨の加算を Bank に簡約・換算させる
当初の目的である、異種通貨の和を指定した通貨で換算しつつ簡約する処理を実装します。
TODO リスト
- 5 USD + 10 CHF = 10 USD (レートが 2 : 1 の場合)
- 10 CHF * (1 USD / 2 CHF) = 5 USD
- 2 USD / 2 = 1 USD
- 2 USD / 1 USD = 2
- 1 USD / 2 CHF は MoneyXRate を返す
- Bank>>addXRate: 、xRateOf:
- Bank>>exchange: to:
- 3 USD + 4 USD = 7 USD
- 5 USD + 10 CHF は MoneyBag を返す
テストを書きます。
MoneyTest >> testExchangeMoneyDifferentCurrency
| bank result |
bank := Bank new.
bank addXRate: 1 USD / 2 CHF.
result := bank exchange: 5 USD + 10 CHF to: #USD.
self assert: result equals: 10 USD
エラー(レッド)になります。
testExchangeMoneyDifferentCurrency のみ走らせて出てきたノーティファイアーで(Create ではなく) Dubug をクリックして開くデバッガーを見ると、Money を想定した仮引数 aMoney に MoneyBag が渡り、それに currency の問い合わせが行って MessageNotUnderstood になっているのが分かります。
このまま Bank>>exchange: to: を次のように適切に書き換えましょう。
Bank >> exchange: aMoneyOrBag to: toCurrency
^ aMoneyOrBag exchangeTo: toCurrency with: self
Proceed すると、先ほどの定義に含まれた exchangeTo:with: が未定義との指摘(MessageNotUnderstood: MoneyBag>>exchangeTo:with:)を受けるので(今度は Debug ボタンではなく) Create ボタンをクリックします。プロトコルは exchanging でよいでしょう。
MoneyBag >> exchangeTo: toCurrency with: aBank
^ elements
inject: (0 perform: toCurrency)
into: [ :acc :aMoney | acc + (aMoney exchangeTo: toCurrency with: aBank) ]
Proceed するとさらに Money にも同名メソッドの定義を促されますので同様に Create します。プロトコルは同じく exchanging 。
Money >> exchangeTo: toCurrency with: aBank
^ self * (aBank xRateOf: currency -> toCurrency)
TODO リスト
- 5 USD + 10 CHF = 10 USD (レートが 2 : 1 の場合)
- 10 CHF * (1 USD / 2 CHF) = 5 USD
- 2 USD / 2 = 1 USD
- 2 USD / 1 USD = 2
- 1 USD / 2 CHF は MoneyXRate を返す
- Bank>>addXRate: 、xRateOf:
- Bank>>exchange: to:
- 3 USD + 4 USD = 7 USD
- 5 USD + 10 CHF は MoneyBag を返す
#ここまでのまとめと補足
- 5 USD + 5 USD(同種通貨の和)は常に Money を返す
- 未定義メソッドのコールがあるメソッドのコンパイル時、細かく警告を出してその都度候補を提示してくる Squeak と違い、何も言わずにコンパイルを通す Pharo は、開発の軽快なテンポを保つのに役立ち、TDD とも相性がよい。しかしタイプミスのチェックくらいはやって欲しくもあり、双方を両立できないのがもどかしい。
- Bank>>exchange: to: から exchangeTo:with: の流れはダブルディスパッチ。
- inject:into: は、たとえば #(1 2 3) inject: 0 into: [ :sum :x | sum + x ] "=> 6 " のように動作する、他言語では reduce とか fold(fold left)に相当するメソッドで、第一引数に初期値を第二引数に二変数の無名関数(Pharo は不可だが、Squeak ように二項メッセージセレクターを許す処理系もある → #(1 2 3) inject: 0 into: #+ )を与えて使う。
- MoneyBag は、実は Bag になっていない。(次回、直す予定)
#この時点のコード
ファイルアウト形式のソースです。(この形式で Smalltalk のソースを読むことはあまり推奨されません。為念)
Pharo では、いったんこのコードをテキストファイルに保存してから、そのファイルを Pharo 内から Tools → File Browser で開き、Filein ボタンをクリックすると読み込めます。
Squeak でも読み込みや実行が可能なコードにもなっています。Pharo 同様に File List で開くか、デスクトップクリック → Workspace で開いたワークスペースなどにコピペし、alt/cmd + shift + g などで file it in するとクラスブラウザから閲覧したり、デスクトップクリック → Test Runner でテストも試せます(ただし使い勝手は Pharo に比べるとかなり落ちます^^;)。
!Number methodsFor: '*TDD-Money' stamp: 'sumim 12/7/2017 18:16'!
USD
^ Money amount: self currency: #USD! !
!Number methodsFor: '*TDD-Money' stamp: 'sumim 12/7/2017 18:16'!
CHF
^ Money amount: self currency: #CHF! !
!Object methodsFor: '*TDD-Money' stamp: 'sumim 12/14/2017 18:03'!
isMoney
^ false! !
Object subclass: #Bank
instanceVariableNames: 'xRateDict'
classVariableNames: ''
poolDictionaries: ''
category: 'TDD-Money'!
!Bank methodsFor: 'accessing' stamp: 'sumim 12/16/2017 08:39'!
addXRate: aMoneyXRate
^ xRateDict at: aMoneyXRate fromToCurrency put: aMoneyXRate! !
!Bank methodsFor: 'accessing' stamp: 'sumim 12/16/2017 08:42'!
xRateOf: anAssociation
anAssociation key == anAssociation value ifTrue: [ ^ 1 ].
^ xRateDict at: anAssociation! !
!Bank methodsFor: 'exchanging' stamp: 'sumim 12/16/2017 09:05'!
exchange: aMoneyOrBag to: toCurrency
^ aMoneyOrBag exchangeTo: toCurrency with: self! !
!Bank methodsFor: 'initialization' stamp: 'sumim 12/16/2017 08:42'!
initialize
super initialize.
xRateDict := Dictionary new.! !
Object subclass: #Money
instanceVariableNames: 'amount currency'
classVariableNames: ''
poolDictionaries: ''
category: 'TDD-Money'!
!Money methodsFor: 'accessing' stamp: 'sumim 12/7/2017 17:32'!
currency
^ currency! !
!Money methodsFor: 'accessing' stamp: 'sumim 12/5/2017 17:28'!
amount
^ amount! !
!Money methodsFor: 'arithmetic' stamp: 'sumim 12/14/2017 18:06'!
* multiplier
multiplier isNumber
ifTrue: [ ^ amount * multiplier perform: currency ].
self assert: multiplier fromCurrency == currency.
^ amount * multiplier xRate perform: multiplier toCurrency! !
!Money methodsFor: 'arithmetic' stamp: 'sumim 12/14/2017 18:05'!
/ divisor
divisor isNumber
ifTrue: [ ^ amount / divisor perform: currency ].
divisor isMoney
ifFalse: [ ^ self error: 'unsupported divisor' ].
^ divisor currency == currency
ifTrue: [ amount / divisor amount ]
ifFalse: [ MoneyXRate new
setFromToCurrency: divisor currency -> currency
xRate: amount / divisor amount ]! !
!Money methodsFor: 'arithmetic' stamp: 'sumim 12/16/2017 08:59'!
+ addend
addend currency ~~ currency
ifTrue: [ ^ MoneyBag withAll: {self. addend} ].
^ amount + addend amount perform: currency! !
!Money methodsFor: 'comparing' stamp: 'sumim 12/7/2017 17:50'!
= other
^ other currency == currency and: [ other amount = amount ]! !
!Money methodsFor: 'comparing' stamp: 'sumim 12/14/2017 18:08'!
hash
^ currency hash bitXor: amount hash! !
!Money methodsFor: 'exchanging' stamp: 'sumim 12/16/2017 09:08'!
exchangeTo: toCurrency with: aBank
^ self * (aBank xRateOf: currency -> toCurrency)! !
!Money methodsFor: 'printing' stamp: 'sumim 12/7/2017 18:08'!
printOn: aStream
aStream print: amount; space; nextPutAll: currency! !
!Money methodsFor: 'private' stamp: 'sumim 12/7/2017 17:41'!
setAmount: aNumber currency: aSymbol
amount := aNumber.
currency := aSymbol! !
!Money methodsFor: 'testing' stamp: 'sumim 12/14/2017 18:04'!
isMoney
^ true! !
!Money class methodsFor: 'instance creation' stamp: 'sumim 12/7/2017 18:15'!
amount: aNumber currency: aSymbol
^ self new setAmount: aNumber currency: aSymbol; yourself! !
Object subclass: #MoneyBag
instanceVariableNames: 'elements'
classVariableNames: ''
poolDictionaries: ''
category: 'TDD-Money'!
!MoneyBag methodsFor: 'accessing' stamp: 'sumim 12/16/2017 08:58'!
elements
^ elements! !
!MoneyBag methodsFor: 'exchanging' stamp: 'sumim 12/16/2017 09:07'!
exchangeTo: toCurrency with: aBank
^ elements
inject: (0 perform: toCurrency)
into: [ :acc :aMoney | acc + (aMoney exchangeTo: toCurrency with: aBank) ]! !
!MoneyBag methodsFor: 'private' stamp: 'sumim 12/16/2017 08:56'!
setElements: aCollectionOfMoney
elements := aCollectionOfMoney! !
!MoneyBag class methodsFor: 'instance creation' stamp: 'sumim 12/17/2017 16:47:06'!
withAll: aCollectionOfMoney
^ self new setElements: aCollectionOfMoney; yourself! !
Object subclass: #MoneyXRate
instanceVariableNames: 'fromToCurrency xRate'
classVariableNames: ''
poolDictionaries: ''
category: 'TDD-Money'!
!MoneyXRate methodsFor: 'accessing' stamp: 'sumim 12/14/2017 18:06'!
xRate
^ xRate! !
!MoneyXRate methodsFor: 'accessing' stamp: 'sumim 12/16/2017 08:36'!
fromToCurrency
^ fromToCurrency! !
!MoneyXRate methodsFor: 'accessing' stamp: 'sumim 12/14/2017 18:07'!
toCurrency
^ fromToCurrency value! !
!MoneyXRate methodsFor: 'accessing' stamp: 'sumim 12/14/2017 18:06'!
fromCurrency
^ fromToCurrency key! !
!MoneyXRate methodsFor: 'comparing' stamp: 'sumim 12/14/2017 18:08'!
= other
^ other class == self class
and: [ other fromToCurrency = fromToCurrency and: [ other xRate = xRate ] ]! !
!MoneyXRate methodsFor: 'comparing' stamp: 'sumim 12/14/2017 18:08'!
hash
^ fromToCurrency hash bitXor: xRate hash! !
!MoneyXRate methodsFor: 'printing' stamp: 'sumim 12/14/2017 18:08'!
printOn: aStream
aStream
print: xRate;
space;
nextPutAll: self toCurrency;
nextPutAll: ' / ';
print: 1;
space;
nextPutAll: self fromCurrency! !
!MoneyXRate methodsFor: 'private' stamp: 'sumim 12/14/2017 18:05'!
setFromToCurrency: anAssociation xRate: aNumber
fromToCurrency := anAssociation.
xRate := aNumber! !
TestCase subclass: #MoneyTest
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'TDD-Money'!
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/16/2017 08:28'!
testBankSettingGettingXRate
| xRate bank |
xRate := 1 USD / 2 CHF.
bank := Bank new.
bank addXRate: xRate.
self assert: (bank xRateOf: xRate fromToCurrency) equals: xRate! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/7/2017 17:28'!
testCurrency
self assert: 5 USD currency equals: #USD.
self assert: 5 CHF currency equals: #CHF! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/14/2017 18:04'!
testDivideDifferentCurrencyReturnsMoneyXRate
| aMoneyXRate |
aMoneyXRate := 1 USD / 2 CHF.
self assert: aMoneyXRate class equals: MoneyXRate! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/14/2017 18:00'!
testDividedByNum
self assert: 2 USD / 2 equals: 1 USD! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/14/2017 18:02'!
testDividedBySameCurrency
self assert: 2 USD / 1 USD equals: 2! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/5/2017 12:55'!
testEquality
self assert: 5 USD = 5 USD.
self assert: 5 USD ~= 6 USD.
self assert: 5 CHF = 5 CHF.
self assert: 5 CHF ~= 6 CHF.
self assert: 5 USD ~= 5 CHF! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/16/2017 08:44'!
testExchangeMoney
| bank |
bank := Bank new.
bank addXRate: 1 USD / 2 CHF.
self assert: (bank exchange: 10 CHF to: #USD) equals: 5 USD! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/16/2017 09:00'!
testExchangeMoneyDifferentCurrency
| bank result |
bank := Bank new.
bank addXRate: 1 USD / 2 CHF.
result := bank exchange: 5 USD + 10 CHF to: #USD.
self assert: result equals: 10 USD! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/7/2017 17:30'!
testFiveCHFReturnsAMoney
self assert: 5 CHF class equals: Money! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/7/2017 17:30'!
testFiveUSDReturnsAMoney
self assert: 5 USD class equals: Money! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/14/2017 18:03'!
testIsMoney
self deny: 1 isMoney.
self assert: 1 USD isMoney! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/5/2017 12:56'!
testMultiplication
self assert: 5 USD * 2 equals: 10 USD.
self assert: 5 CHF * 2 equals: 10 CHF! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/16/2017 08:51'!
testPlusDifferentCurrencyReturnsMoneyBag
| sum |
sum := 5 USD + 10 CHF.
self assert: sum class equals: MoneyBag! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/7/2017 17:28'!
testPrintString
self assert: 5 USD printString equals: '5 USD'.
self assert: 5 CHF printString equals: '5 CHF'! !
!MoneyTest methodsFor: 'tests' stamp: 'sumim 12/16/2017 08:47'!
testSimpleAddition
self assert: 3 USD + 4 USD equals: 7 USD! !