多国通貨(The Money Example) 将来の読み手を考えたテスト(Abstraction, Finally)
テスト駆動開発 (Kent Beck(著)、和田 卓人(翻訳)、オーム社)の第Ⅰ部(多国通貨(The Money Example))、第16章(将来の読み手を考えたテスト(Abstraction, Finally))をJavaではなくGo言語で取り組んでみる。
コード
money_test.go
package money
import "testing"
func TestTimes(t *testing.T) {
five := NewDollar(5)
tests := []struct {
m int
d Money
}{
{2, NewDollar(10)},
{3, NewDollar(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 TestEq(t *testing.T) {
tests := []struct {
mx, my Money
want bool
}{
{NewDollar(5), NewDollar(5), true},
{NewDollar(5), NewDollar(6), false},
{NewFranc(5), NewDollar(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 TestCurrency(t *testing.T) {
tests := []struct {
c string
m Money
}{
{"USD", NewDollar(1)},
{"CHF", NewFranc(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 TestAdd(t *testing.T) {
five := NewDollar(5)
sum := five.Add(five)
bank := NewBank()
got := bank.reduce(sum, "USD")
want := NewDollar(10)
if got.Ne(want) {
t.Errorf("%v.Add(%v) got %v, want %v", five, five, got, want)
}
}
func TestMixedAdd(t *testing.T) {
fiveBucks := NewDollar(5)
tenFrancs := NewFranc(10)
ten := fiveBucks.Add(tenFrancs)
bank := NewBank()
bank.addRate("CHF", "USD", 2)
got := bank.reduce(ten, "USD")
want := NewDollar(10)
if got.Ne(want) {
t.Errorf("%v.reduce(%v, USD) got %v, want %v", bank, ten, got, want)
}
}
func TestAddReturnsSum(t *testing.T) {
five := NewDollar(5)
result := five.Add(five)
sum := result.(Sum)
tests := []Expression{sum.augend, sum.addend}
for _, test := range tests {
if test.Ne(five) {
t.Errorf("%v.Ne(%v)", test, five)
}
}
}
func TestSumTime(t *testing.T) {
fiveBucks := NewDollar(5)
tenFrancs := NewFranc(10)
bank := NewBank()
bank.addRate("CHF", "USD", 2)
sum := NewSum(fiveBucks, tenFrancs).Times(2)
got := bank.reduce(sum, "USD")
want := NewDollar(20)
if got.Ne(want) {
t.Errorf("%v.reduce(%v, USD) got %v, want %v", bank, sum, got, want)
}
}
money.go
package money
import "fmt"
// Expression ...
type Expression interface {
Times(amount int) Expression
reduce(Bank, string) Money
Ne(Expression) bool
}
// Money ...
type Money struct {
amount int
currency string
}
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(bank Bank, to string) Money {
rate := bank.rate(mx.currency, to)
return NewMoney(mx.amount/rate, to)
}
// 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(e Expression) bool {
if my, ok := e.(Money); ok {
return !mx.Eq(my)
}
return false
}
// Times ...
func (mx Money) Times(m int) Expression {
return NewMoney(mx.amount*m, mx.currency)
}
// NewDollar ...
func NewDollar(amount int) Money {
return NewMoney(amount, "USD")
}
// NewFranc ...
func NewFranc(amount int) Money {
return NewMoney(amount, "CHF")
}
// Add ...
func (mx Money) Add(my Money) Expression {
return NewSum(mx, my)
}
sum_test.go
package money
import "testing"
func TestAddMoney(t *testing.T) {
fiveBucks := NewDollar(5)
tenFrancs := NewFranc(10)
bank := NewBank()
bank.addRate("CHF", "USD", 2)
sum := NewSum(fiveBucks, tenFrancs).Add(fiveBucks)
got := bank.reduce(sum, "USD")
want := NewDollar(15)
if got.Ne(want) {
t.Errorf("%v.reduce(%v) got %v, want %v", bank, sum, got, want)
}
}
// func TestAddSaneCyrrebct(t *testing.T) {
// sum := NewDollar(1).Add(NewDollar(1))
// if _, ok := sum.(Money); !ok {
// t.Errorf("%v is not instance of Money", sum)
// }
// }
sum.go
package money
// Sum ...
type Sum struct {
augend, addend Expression
}
// NewSum ...
func NewSum(augend, addend Expression) Sum {
return Sum{augend, addend}
}
// Ne ...
func (sx Sum) Ne(sy Expression) bool {
return false
}
func (sx Sum) reduce(bank Bank, to string) Money {
amount :=
sx.augend.reduce(bank, to).amount + sx.addend.reduce(bank, to).amount
return NewMoney(amount, to)
}
// Add ...
func (sx Sum) Add(addend Expression) Expression {
return NewSum(sx, addend)
}
// Times ...
func (sx Sum) Times(m int) Expression {
return NewSum(sx.augend.Times(m), sx.addend.Times(m))
}
bank_test.go
package money
import "testing"
func TestBankReduceSum(t *testing.T) {
sum := NewSum(NewDollar(3), NewDollar(4))
bank := NewBank()
result := bank.reduce(sum, "USD")
seven := NewDollar(7)
if result.Ne(seven) {
t.Errorf("%v.Ne(%v)", result, seven)
}
}
func TestBankReduceMoney(t *testing.T) {
bank := NewBank()
result := bank.reduce(NewDollar(1), "USD")
one := NewDollar(1)
if result.Ne(one) {
t.Errorf("%v.Ne(%v)", result, one)
}
}
func TestBankReduceMoneyDifferentCurrency(t *testing.T) {
bank := NewBank()
bank.addRate("CHF", "USD", 2)
twoFranc := NewFranc(2)
got := bank.reduce(twoFranc, "USD")
want := NewDollar(1)
if got.Ne(want) {
t.Errorf("%v.reduce(%v, \"USD\") got %v, want %v",
bank, twoFranc, got, want)
}
}
func TestBankIdentityRate(t *testing.T) {
bank := NewBank()
got := bank.rate("USD", "USD")
want := 1
if got != want {
t.Errorf("%v.rate(USD, USD) want %v, got %v", bank, want, got)
}
}
bank.go
package money
// Bank 通貨を換算する
type Bank struct {
rates map[pair]int
}
// NewBank ...
func NewBank() Bank {
return Bank{rates: map[pair]int{}}
}
func (b Bank) reduce(source Expression, to string) Money {
return source.reduce(b, to)
}
func (b Bank) addRate(from, to string, rate int) {
b.rates[newPair(from, to)] = rate
}
func (b Bank) rate(from, to string) int {
if from == to {
return 1
}
return b.rates[newPair(from, to)]
}
pair_test.go
package money
pair.go
package money
type pair struct {
from, to string
}
func newPair(from, to string) pair {
return pair{from, to}
}
func (px pair) eq(py pair) bool {
return px.from == py.from && px.to == py.to
}
func (px pair) hashCode() int {
return 0
}
入出力結果(Terminal, Zsh)
% go test
PASS
ok money 0.359s
% go test
bank_test.go:1:1: expected 'package', found 'EOF'
% go test
PASS
ok money 0.283s
% go test
PASS
ok money 0.277s
% go test
PASS
ok money 0.291s
% go test
--- FAIL: TestAddSaneCyrrebct (0.00s)
sum_test.go:20: {{1 USD} {1 USD}} is not instance of Money
FAIL
exit status 1
FAIL money 0.278s
% go test -v
=== RUN TestBankReduceSum
--- PASS: TestBankReduceSum (0.00s)
=== RUN TestBankReduceMoney
--- PASS: TestBankReduceMoney (0.00s)
=== RUN TestBankReduceMoneyDifferentCurrency
--- PASS: TestBankReduceMoneyDifferentCurrency (0.00s)
=== RUN TestBankIdentityRate
--- PASS: TestBankIdentityRate (0.00s)
=== RUN TestTimes
--- PASS: TestTimes (0.00s)
=== RUN TestEq
--- PASS: TestEq (0.00s)
=== RUN TestCurrency
--- PASS: TestCurrency (0.00s)
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
=== RUN TestMixedAdd
--- PASS: TestMixedAdd (0.00s)
=== RUN TestAddReturnsSum
--- PASS: TestAddReturnsSum (0.00s)
=== RUN TestSumTime
--- PASS: TestSumTime (0.00s)
=== RUN TestAddMoney
--- PASS: TestAddMoney (0.00s)
PASS
ok money 0.292s
%