多国通貨(The Money Example) 実装を導くテスト(Make It) interface型、型アサーション
テスト駆動開発 (Kent Beck(著)、和田 卓人(翻訳)、オーム社)の第Ⅰ部(多国通貨(The Money Example))、第13章(実装を導くテスト(Make It))をJavaではなくGo言語で取り組んでみる。
コード
money_test.go
package money
import "testing"
func TestTimes(t *testing.T) {
five := NewMoneyDollar(5)
tests := []struct {
m int
d Money
}{
{2, NewMoneyDollar(10)},
{3, NewMoneyDollar(15)},
}
for _, test := range tests {
got := five.Times(test.m)
want := test.d
if got != want {
t.Errorf("%v.Times(%v) got %v, want %v", five, test.m, got, want)
}
}
}
func TestCurrency(t *testing.T) {
tests := []struct {
c string
m Money
}{
{"USD", NewMoneyDollar(1)},
{"CHF", NewMoneyFranc(1)},
}
for _, test := range tests {
got := test.m.Currency()
want := test.c
if got != want {
t.Errorf("%v.Currency() got %v, want %v",
test.m, got, want)
}
}
}
func TestEq(t *testing.T) {
tests := []struct {
mx, my Money
want bool
}{
{NewMoneyDollar(5), NewMoneyDollar(5), true},
{NewMoneyDollar(5), NewMoneyDollar(6), false},
{NewMoneyFranc(5), NewMoneyDollar(5), false},
}
for _, test := range tests {
got := test.mx.Eq(test.my)
want := test.want
if got != want {
t.Errorf("%v.Eq(%v) got %v, want %v",
test.mx, test.my, got, want)
}
}
}
func TestAdd(t *testing.T) {
five := NewMoneyDollar(5)
sum := five.Add(five)
bank := NewBank()
got := bank.reduce(sum, "USD")
want := NewMoneyDollar(10)
if got.Ne(want) {
t.Errorf("%v.Add(%v) got %v, want %v", five, five, got, want)
}
}
func TestAddReturnsSum(t *testing.T) {
five := NewMoneyDollar(5)
result := five.Add(five)
sum := result.(Sum)
tests := []Money{sum.augend, sum.addend}
for _, test := range tests {
if test.Ne(five) {
t.Errorf("%v.Ne(%v)", test, five)
}
}
}
func TestReduceSum(t *testing.T) {
sum := NewSum(NewMoneyDollar(3), NewMoneyDollar(4))
bank := NewBank()
result := bank.reduce(sum, "USD")
seven := NewMoneyDollar(7)
if result.Ne(seven) {
t.Errorf("%v.Ne(%v)", result, seven)
}
}
func TestReduceMoney(t *testing.T) {
bank := NewBank()
result := bank.reduce(NewMoneyDollar(1), "USD")
one := NewMoneyDollar(1)
if result.Ne(one) {
t.Errorf("%v.Ne(%v)", result, one)
}
}
money.go
package money
import "fmt"
// Expression ...
type Expression interface {
reduce(string) Money
}
// Money ...
type Money struct {
amount int
currency string
}
// Sum ...
type Sum struct {
augend, addend Money
}
func (mx Money) String() string {
return fmt.Sprintf("%v %v", mx.amount, mx.currency)
}
// NewMoney ...
func NewMoney(amount int, currency string) Money {
return Money{amount, currency}
}
// reduce ...
func (mx Money) reduce(to string) Money {
return mx
}
// Currency ...
func (mx Money) Currency() string {
return mx.currency
}
// Eq ...
func (mx Money) Eq(my Money) bool {
return mx.amount == my.amount && mx.currency == my.currency
}
// Ne ...
func (mx Money) Ne(my Money) bool {
return !mx.Eq(my)
}
// Times ...
func (mx Money) Times(m int) Money {
return NewMoney(mx.amount*m, mx.currency)
}
// NewMoneyDollar ...
func NewMoneyDollar(amount int) Money {
return NewMoney(amount, "USD")
}
// NewMoneyFranc ...
func NewMoneyFranc(amount int) Money {
return NewMoney(amount, "CHF")
}
// Add ...
func (mx Money) Add(my Money) Expression {
// return NewMoney(mx.amount+my.amount, mx.currency)
return NewSum(mx, my)
}
// NewSum ...
func NewSum(augend, addend Money) Sum {
return Sum{augend, addend}
}
func (s Sum) reduce(to string) Money {
amount := s.augend.amount + s.addend.amount
return NewMoney(amount, to)
}
// Bank 通貨を換算する
type Bank struct{}
// NewBank ...
func NewBank() Bank {
return Bank{}
}
func (b Bank) reduce(source Expression, to string) Money {
// return NewMoneyDollar(10)
// if m, ok := source.(Money); ok {
// return m.reduce(to)
// }
// sum := source.(Sum)
// amount := sum.augend.amount + sum.addend.amount
// return NewMoney(amount, to)
return source.reduce(to)
}
入出力結果(Terminal, Zsh)
% go test
--- FAIL: TestReduceSum (0.00s)
money_test.go:87: 10 USD.Ne(7 USD)
FAIL
exit status 1
FAIL money 0.386s
% go test
PASS
ok money 0.275s
% go test
PASS
ok money 0.303s
% go test
--- FAIL: TestReduceMoney (0.00s)
panic: interface conversion: money.Expression is money.Money, not money.Sum [recovered]
panic: interface conversion: money.Expression is money.Money, not money.Sum
goroutine 12 [running]:
testing.tRunner.func1.1(0x1124c80, 0xc0000582a0)
/opt/local/lib/go/src/testing/testing.go:1072 +0x30d
testing.tRunner.func1(0xc000001e00)
/opt/local/lib/go/src/testing/testing.go:1075 +0x41a
panic(0x1124c80, 0xc0000582a0)
/opt/local/lib/go/src/runtime/panic.go:969 +0x1b9
money.Bank.reduce(...)
/.../go/src/money/money.go:92
money.TestReduceMoney(0xc000001e00)
/.../go/src/money/money_test.go:93 +0x45
testing.tRunner(0xc000001e00, 0x1150170)
/opt/local/lib/go/src/testing/testing.go:1123 +0xef
created by testing.(*T).Run
/opt/local/lib/go/src/testing/testing.go:1168 +0x2b3
exit status 2
FAIL money 0.282s
% go test
PASS
ok money 0.221s
%