LoginSignup
4
0

More than 5 years have passed since last update.

テスト駆動開発でお試しする Pharo Smalltalk・第7回 MoneyBagに異種通貨同士の加算を保持

Last updated at Posted at 2017-12-16

新訳版『テスト駆動開発』の第Ⅰ部の多国通貨をネタに Smalltalk を学ぶシリーズの第7回です。同書と似た仕様を目指しますが、好みを優先する等の都合で写経にはなっていませんのでどうぞあしからず。(12/24追記:なるべく忠実な写経は別に書きました→こちら

前回 は、同書の流れからは外れ、換算レートのオブジェクト化(MoneyXRate, money exchange rate)とそれを用いた換算を試しました。

今回は、第17章でちょっとだけ触れられている“MoneyBag”を、きっとこんな感じなのではないかな…と勝手な想像を巡らしながら実装してみます。


参考: 多国通貨実装のおおまかな流れ
  1. Dollar を定義
  2. Dollar をコピーして Franc を定義
  3. Dollar、Franc の重複を新しく作ったスーパークラスの Money にプルアップ
  4. 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

1027.png

コンパイル(Accept)に際し、Bank の定義を求められますが Define new calss を選んで、下のクラス定義式のまま OK します。

Object subclass: #Bank
    instanceVariableNames: ''
    classVariableNames: ''
    package: 'TDD-Money'

1029.png

MoneyText 全テストを走らせる(○印クリック)と結果はレッド(エラー)になるので、

1030.png

続けて、testBankSettingGettingXRate のみを走らせて、指摘された未定義メソッドごとに Create を押して定義してゆきます。。

1031.png

addXRate: は Bank に定義、プロトコルは adding にでもしておきましょう。メソッドは空でもよいのですが、adding 系のメソッドは add した要素をそのまま返すのが慣習なのでとりあえずそうしておきます。

Bank >> addXRate: aMoneyXRate 
    ^ aMoneyXRate

1032.png

fromToCurrency は MoneyXRate に定義します。fromCurrency と toCurrency は実装済みなので表現できなくもないですが、fromToCurrency 自体もあった方が便利ですね(と、しれっと言う)。

プロトコルは accessing です。前回の xRate 同様、インスタンス変数名にメソッド名が一致するので自動でそれを返すコードにしてくれていますので、そのまま Proceed します。

MoneyXRate >> fromToCurrency
    ^ fromToCurrency

1033.png

次に未定義を指摘される xRateOf: は Bank に、プロトコルは accessing で定義します。テストを失敗させるため、1 USD / 1 CHF を返すコードにするのをお忘れなく。

Bank >> xRateOf: anAssociation 
    ^ 1 USD / 1 CHF

1034.png

全テストを実行して失敗(イエロー)を確認できたら 1 USD / 2 CHF を返す仮実装に変えてテストを通します。

Bank >> xRateOf: anAssociation 
    ^ 1 USD / 2 CHF

1035.png

Bank>>addXRate: 、xRateOf: は、辞書をインスタンス変数 xRateDict として持たせておき、それに対して格納と問い合わせることで実現しましょう。

Bank >> addXRate: aMoneyXRate
    ^ xRateDict at: aMoneyXRate fromToCurrency put: aMoneyXRate

コンパイルに際して xRateDict の未宣言を指摘されるので Declare new instance variable します。

1036.png

忘れないうちに、Bank の右クリック → Analyze → Generate initialize method で初期化メソッドを定義し、Dictionay new をセットしておきます。

1037.png

Bank >> initialize

    super initialize.

    xRateDict := Dictionary new.

1038.png

xRateOf: は引数として与えられた「換算前の通貨 -> 換算後の通貨」を保持する key-value 値(anAssociation)をキーにして、辞書 xRateDict から該当する換算レート(MoneyXRate)を探して返します。同種通貨換算の場合(anAssociation の key と value が一致する場合)は通貨変換は不要なので 1 を返します。

Bank >> xRateOf: anAssociation
    anAssociation key == anAssociation value ifTrue: [ ^ 1 ]. 
    ^ xRateDict at: anAssociation

1040.png

テストはグリーンのままです。

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: にしてみました。

1041.png

コンパイルは通りますが、テストはレッド(エラー)です。testExchangeMoney のみ実行して exchange: to: を Bank に追加します。プロトコルは適当に exchanging とでもします。

Bank >> exchange: aMoney to: toCurrency
    ^ 1 USD

テストを失敗させるため、返値は 1 USD にします。

1043.png

失敗が確認できたら、5 USD を返す仮実装に書き換えます。

Bank >> exchange: aMoney to: toCurrency
    ^ 5 USD

1044.png

重複の排除はこのようにします。

Bank >> exchange: aMoney to: toCurrency 
    ^ aMoney * (self xRateOf: aMoney currency -> toCurrency)

1045.png

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

全テストを実行するとエラー(レッド)になるので、

1047.png

testSimpleAddition を個別に実行して Money>> + をプロトコル arithmetic に追加します。仮引数は addend に変更し、テストを失敗させるためにメソッド本体は空にしてコンパイルします(self が返ります)。

Money >> + addend

1048.png

仮実装として、7 USD が返るように Money>> + を書き直してコンパイルします。

Money >> + addend
    ^ 7 USD

1049.png

重複の排除は次のようにします。

Money >> + addend
    ^ amount + addend amount perform: currency

1050.png

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'

1051.png

全テスト実行すると失敗するので、Money>> + を次の仮実装に書き換えてコンパイルし直します。

Money >> + addend
    addend currency ~~ currency
        ifTrue: [ ^ MoneyBag new ].
    ^ amount + addend amount perform: currency

1052.png

テストは通りますが、まだ 何の情報も持たない MoneyBag が返ってきているだけです。

MoneyBag は elements として加算の要素を持ちます。set で始まるコンストラクターパラメーターメソッド(ベパp27)を private プロトコルに追加し、インスタンス生成時に使いましょう。

1053.png

1054.png

MoneyBag >> setElements: aCollectionOfMoney
    elements := aCollectionOfMoney

1055.png

コンパイル時に elements が未宣言であることを指摘されますので、Declare new instance variable でインスタンス変数に追加してもらいます。

1056.png

ゲッターも定義しておきましょう。普通にコード枠に次のコードを入力してコンパイルしてもよいですし、MoneyBag を右クリック → Refactoring → Inst Var Refactoring → Accessors → elements をクリック → Ok で自動生成するのもよしです。

1057.png

MoneyBag >> elements
    ^ elements

あと、private な setElements: を MoneyBag の外で使わずに済むように、MoneyBag class(MoneyBag のメタクラス)に withAll: というインスタンス生成用のクラスメソッドも定義しておきます。

クラスブラウザのクラスリスト枠下にある Class ボタンをクリックしてクラスサイドに切り替え、プロトコルは instance creation を設け、そこに定義します。

MoneyBag class >> withAll: aCollectionOfMoney
    ^ self new setElements: aCollectionOfMoney; yourself

1027.png

再び Class ボタンを押してインスタンスサイドに忘れずに戻しておきます。

準備が整ったので MoneyBag 生成時に必要な情報を付加するように Money>> + を書き換えます。

Money >> + addend
    addend currency ~~ currency
        ifTrue: [ ^ MoneyBag withAll: {self. addend} ].
    ^ amount + addend amount perform: currency

1028.png

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 になっているのが分かります。

1060.png

このまま Bank>>exchange: to: を次のように適切に書き換えましょう。

Bank >> exchange: aMoneyOrBag to: toCurrency 
    ^ aMoneyOrBag exchangeTo: toCurrency with: self

1061.png

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)

1062.png

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! !
4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0