goのテストはクラス設計がとても大事っぽい

まだこうした方が良いんじゃないか?ぐらいなので、
ベストプラクティスは他にありそうです…

rubyで関数の差し替え

Rubyだと以下のように、
すでに存在するクラスに対して関数を再定義したり、
継承して関数の差し替えをすることが簡単にできます。


class A
  def exec
    f()
    a()
  end

  def f
    print "f "
  end

  def a
    print "a\n"
  end
end

a = A.new
a.exec # f a

class A
  def f
    print "newf "
  end
end

a.exec #newf a

class B < A
  def f
    print "newf "
  end
end

b = B.new
b.exec #newf a

そのため、一部の関数をモックやスタブにし、正しく呼び出されているかを検証したり、
その機能以外のところがちゃんと動いているかを検証するのが簡単にできます。

goで関数の差し替え

埋め込みによる差し替え(できない)

golangでは既存の構造体に関数を再定義することはできません。
また、以下のrubyコードのように埋め込んだ後に関数を再定義しても、
継承前の関数を読んでいる部分を差し替えることはできません。

そのため、rubyの2番目の例のような以下のコードでは、
b.ExecはAの関数fを呼び出してしまうため、動きを差し替えられません。

package main

import (
	"fmt"
)

type A struct {
}

func (a *A) Exec() {
	a.f()
	a.a()
}

func (a *A) f() {
	fmt.Print("f ")
}

func (a *A) a() {
	fmt.Print("a\n")
}

type B struct {
	*A
}

func (a *B) f() {
	fmt.Print("newf ")
}

func main() {
	a := &A{}
	a.Exec() // f a

	b := &B{
		A: a,
	}
	b.Exec() // f a (not "newf a")
}

インターフェースを利用する

golangにはインターフェースが合っていれば何でも代入できるため、
インターフェースを呼び出すように実装し、わたす構造体を変えることで、
関数の差し替えをすることができるようになります。

以下の例では計算用のCalcというインタフェースを定義し、
実際に計算を行うSumAddと、固定の値を返したり渡された値を見えるようにしたSumTestを用意しています。
SmuTestを利用することで、呼び出し側のAのロジックが正しいかを検証できるようになっています。

package main

import (
	"fmt"
)

type A struct {
  calc Calc
  list []int
}

func (a *A) F(ratio float64) float64 {
  s := a.calc.sum(a.list)
  return float64(s) * ratio
}

type Calc interface {
  sum(l []int) int
}

type SumAdd struct{
}

func (s *SumAdd) sum(l []int) int {
  sum := 0
  for _, i := range l {
    sum += i
  }
  return sum
}

type SumTest struct {
  Nums []int
}

func (s *SumTest) sum(l []int) int {
  s.Nums = l
  return 15
}

func main() {
  s := &SumAdd{}
  l := []int{1,2,3,4,5}

  a := &A{
    calc: s,
    list: l,
  }
  fmt.Println(a.F(3.0))

  t := &SumTest{}
  testA := &A{
    calc: t,
    list: l,
  }

  ret := testA.F(3.0)
  fmt.Println(ret == 45)
  fmt.Println(len(t.Nums) == 5)
}

まとめ

golangではインタフェースを利用して挙動を変えることで、部分的にテストができます。
ただし、設計時にどこをインターフェースにするかを考えて作らないと、上手く差し替えができません。

つまり、rubyは適当に作ったテストしにくい設計でも無理矢理テストできますが、
golangはテストしやすい設計を考えてから作る必要があるようです。

BPStudy#92に参加した

参加記録 BPStudy#92 お金の話でした。
経営や簿記は全く本業ではないのですが、自分が生きてる世界を構成する重要な要素なので、
知っておいた方が良いと思いました。

mongodbについて調べていた

一定時間後にデータが消える設定

容量節約のため、一定期間後のデータは消えるようにしたいなーと思っていたところ、
時間の入っているキーに対してexpireAfterSecondsのついたTTL(time to live)インデックスを作ることで、
指定時刻経過後に消されるようにできるみたいです。
Expire Data from Collections by Setting TTL
また、このインデックスはそのまま時刻付きのインデックスとしても利用できるそうです。

指定した期間のデータをdumpする

mongodumpでは-qオプションでクエリを指定できるため、
そこで時刻を指定することで指定した期間のデータをdumpできます。

ただし、どうやらISODateはコマンドラインからは上手く動かないらしく、
Dateオブジェクトを利用してミリ秒で指定する必要があるようです。

mongodump --host localhost --db testdb -q \"{time : { \\$gte :  new Date(1430000000000), \\$lt :  new Date(1440000000000) } }\" -o dump/2015-05-04
このエントリーをはてなブックマークに追加