LoginSignup
8
7

More than 5 years have passed since last update.

os.Exit()はDefer functionの実行を待たない

Posted at

testingパッケージを利用してテストを記述している場合、初期化や終了後の後処理を記述するにはTestMain関数を利用する。

package test

import (
  "log"
  "os"
  "testing"
)

func TestMain(m *testing.M) {
  setup()
  code := m.Run()
  teardown()
  os.Exit(code)
}

func setup() {
  // 何らかの初期化処理...
  log.Println("setupped here.")
}

func teardown() {
  // 何らかの終了処理...
  log.Println("cleaned up here.")
}

func TestCase(t *testing.T) {
  t.Errorf("the test always fails")
}

この時、いつもの癖でteadown()deferで呼ぶコードを書いてしまうとする。こんなかんじだ。

func TestMain(m *testing.M) {
  setup()
  defer teardown()
  code := m.Run()
  os.Exit(code)
}

しかし、この書き方では問題が起こる。

deferを使ってしまうとこうなる
2015/06/13 00:06:04 setupped here.
=== RUN TestCase
--- FAIL: TestCase (0.00s)
    test_test.go:29: the test always fails
FAIL
exit status 1   <--- teardown()が実行されていない!!
FAIL    github.com/harukasan/test/test  0.008s

os.Exit(code)は全てのdeferを破棄して終了する。そのため、deferが正常に実行されずに、後処理が行われないのである。

で、どうするかなんだけど、まず考えるのはdeferの中でos.Exit(code int)を呼ぶ方法である。うまいこと終了コードを渡さないといけないので、あまり綺麗じゃないけどポインタ渡しにした。

func TestMain(m *testing.M) {
  setup()
  var code int
  defer func(int *code) {
    teardown()
    os.Exit(*code)
  }(&code)

  code = m.Run()
}

他の方法としては、codeをグローバル変数にする方法がある。

var code = 0

func TestMain(m *testing.M) {
  setup()
  var code int
  defer func() {
    teardown()
    os.Exit(code)
  }()

  code = m.Run()
}

どっちもどっちっぽいし、そもそもdeferを使うのをやめた方が綺麗なんじゃないかと思う。

8
7
1

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
8
7