Golang Test 코드 작성할 때 Setup, Teardown 적용하기

Table of Contents

테스트 코드를 작성할 때 각 테스트를 수행하기 전/후에 실행할 작업을 Setup, Teardown이라고 합니다. Golang으로 테스트 코드를 작성할 때 Setup, Teardown 동작을 수행하는 방법을 공유합니다.

TestMain 함수 사용하기

golang 자체적으로 지원하는 기능으로 테스트 파일에 TestMain(m *testing.M) 함수가 있을 경우 해당 파일의 테스트를 실행하기 전/후에 특정 작업을 수행하도록 할 수 있습니다.

package test_main_test

import (
  "fmt"
  "testing"
)

var (
  GlobalVar = 0
)

func TestFunct_1(t *testing.T) {
  t.Log("run test 1")
  if GlobalVar != 1 {
    t.Error("GlobalVar should be equal to 1")
  }
}

func TestFunc_2(t *testing.T) {
  t.Log("run test 2")
  if GlobalVar != 1 {
    t.Error("GlobalVar should be equal to 1")
  }
}

func setup() {
  fmt.Println("setup TestMain")
  GlobalVar = 1
}

func teardown() {
  fmt.Println("tear down TestMain")
  GlobalVar = 1
}

func TestMain(m *testing.M) {
  setup()
  m.Run()
  teardown()
}
setup TestMain
=== RUN   TestFunct_1
    test_main_test.go:13: run test 1
--- PASS: TestFunct_1 (0.00s)
=== RUN   TestFunc_2
    test_main_test.go:20: run test 2
--- PASS: TestFunc_2 (0.00s)
PASS
tear down TestMain
ok      go-test/test_main       0.238s

이 방식은 테스트 파일 기준으로 동작하기 때문에 파일 내부에 테스트 함수가 여러개있어도 각 테스트마다 Setup, Teardown이 실행되지 않고 한번만 실행됩니다.

Testify suite 사용하기

testify 패키지의 suite를 사용하면 Setup, Teardown 동작을 수행할 수 있습니다. Suite, Test, SubTest 실행 전/후에 각각 Setup, Teardown을 수행할 수 있어서 세밀하게 조절이 가능합니다. (SubTest는 Table Driven Test에서 각 테스트 Run을 의미합니다.)

package testify_suite_test

import (
  "testing"

  "github.com/stretchr/testify/suite"
)

type UnitTestSuite struct {
  suite.Suite
}

func (s *UnitTestSuite) Test_TableTest1() {
  type testCase struct {
    name string
  }

  testCases := []testCase{
    {
      name: "1",
    },
    {
      name: "2",
    },
  }

  for _, tc := range testCases {
    s.Run(tc.name, func() {
      s.T().Log("run test: ", tc.name)
    })
  }
}

func (s *UnitTestSuite) Test_TableTest2() {
  type testCase struct {
    name string
  }

  testCases := []testCase{
    {
      name: "3",
    },
    {
      name: "4",
    },
  }

  for _, tc := range testCases {
    s.Run(tc.name, func() {
      s.T().Log("run test: ", tc.name)
    })
  }
}

func (s *UnitTestSuite) SetupSuite() {
  s.T().Log("setup suite")
}

func (s *UnitTestSuite) TearDownSuite() {
  s.T().Log("tear down suite")
}

func (s *UnitTestSuite) SetupTest() {
  s.T().Log("setup test")
}

func (s *UnitTestSuite) TearDownTest() {
  s.T().Log("tear down test")
}

func (s *UnitTestSuite) BeforeTest(suiteName, testName string) {
  s.T().Log("before test")
}

func (s *UnitTestSuite) AfterTest(suiteName, testName string) {
  s.T().Log("after test")
}

func (s *UnitTestSuite) SetupSubTest() {
  s.T().Log("setup sub test")
}

func (s *UnitTestSuite) TearDownSubTest() {
  s.T().Log("tear down sub test")
}

func TestUnitTestSuite(t *testing.T) {
  suite.Run(t, new(UnitTestSuite))
}
=== RUN   TestUnitTestSuite
    testify_suite_test.go:56: setup suite
=== RUN   TestUnitTestSuite/Test_TableTest1
    testify_suite_test.go:64: setup test
    testify_suite_test.go:72: before test
=== RUN   TestUnitTestSuite/Test_TableTest1/1
    testify_suite_test.go:80: setup sub test
    testify_suite_test.go:29: run test:  1
    testify_suite_test.go:84: tear down sub test
=== RUN   TestUnitTestSuite/Test_TableTest1/2
    testify_suite_test.go:80: setup sub test
    testify_suite_test.go:29: run test:  2
    testify_suite_test.go:84: tear down sub test
=== NAME  TestUnitTestSuite/Test_TableTest1
    testify_suite_test.go:76: after test
    testify_suite_test.go:68: tear down test
=== RUN   TestUnitTestSuite/Test_TableTest2
    testify_suite_test.go:64: setup test
    testify_suite_test.go:72: before test
=== RUN   TestUnitTestSuite/Test_TableTest2/3
    testify_suite_test.go:80: setup sub test
    testify_suite_test.go:50: run test:  3
    testify_suite_test.go:84: tear down sub test
=== RUN   TestUnitTestSuite/Test_TableTest2/4
    testify_suite_test.go:80: setup sub test
    testify_suite_test.go:50: run test:  4
    testify_suite_test.go:84: tear down sub test
=== NAME  TestUnitTestSuite/Test_TableTest2
    testify_suite_test.go:76: after test
    testify_suite_test.go:68: tear down test
=== NAME  TestUnitTestSuite
    testify_suite_test.go:60: tear down suite
--- PASS: TestUnitTestSuite (0.00s)
    --- PASS: TestUnitTestSuite/Test_TableTest1 (0.00s)
        --- PASS: TestUnitTestSuite/Test_TableTest1/1 (0.00s)
        --- PASS: TestUnitTestSuite/Test_TableTest1/2 (0.00s)
    --- PASS: TestUnitTestSuite/Test_TableTest2 (0.00s)
        --- PASS: TestUnitTestSuite/Test_TableTest2/3 (0.00s)
        --- PASS: TestUnitTestSuite/Test_TableTest2/4 (0.00s)
PASS

수동으로 Setup, Teardown호출하기

테스트를 수행할 때 직접 Setup, Teardown함수를 호출하는 방법입니다. Table Driven Test 방식에서도 적용할 수 있는 방법입니다. Setup함수에서 필요한 초기화를 수행하고 테스트에 필요한 값을 반환하는 형태로 제공하고, Teardown함수를 t.Cleanup을 통해 호출해서 자원을 해제할 수 있습니다.

package manual_test

import "testing"

func setup(t *testing.T) func() {
  t.Log("setup")

  return func() {
    t.Log("teardown")
  }
}

func TestManualSetupTeardown(t *testing.T) {
  type testCase struct {
    name string
  }

  testCases := []testCase{
    {
      name: "1",
    },
    {
      name: "2",
    },
  }

  for _, tc := range testCases {
    t.Run(tc.name, func(t *testing.T) {
      teardown := setup(t)
      t.Cleanup(teardown)

      t.Logf("run test: %s", tc.name)
    })
  }
}
=== RUN   TestManualSetupTeardown
=== RUN   TestManualSetupTeardown/1
    /Users/dalpaeng2/go-test/manual/manual_test.go:6: setup
    /Users/dalpaeng2/go-test/manual/manual_test.go:32: run test: 1
    /Users/dalpaeng2/go-test/manual/manual_test.go:9: teardown
--- PASS: TestManualSetupTeardown/1 (0.00s)
=== RUN   TestManualSetupTeardown/2
    /Users/dalpaeng2/go-test/manual/manual_test.go:6: setup
    /Users/dalpaeng2/go-test/manual/manual_test.go:32: run test: 2
    /Users/dalpaeng2/go-test/manual/manual_test.go:9: teardown
--- PASS: TestManualSetupTeardown/2 (0.00s)
--- PASS: TestManualSetupTeardown (0.00s)
PASS