Bagaimana saya bisa melakukan pengaturan pengujian menggunakan paket pengujian di Go

111

Bagaimana saya dapat melakukan pemrosesan pengaturan pengujian secara keseluruhan yang menetapkan tahapan untuk semua pengujian saat menggunakan paket pengujian ?

Sebagai contoh di Nunit ada [SetUp]atribut.

[TestFixture]
public class SuccessTests
{
  [SetUp] public void Init()
  { /* Load test data */ }
}
miltonb
sumber
2
Dimulai dengan 1.4 Anda dapat memiliki penyiapan dan pembongkaran global
Salvador Dali

Jawaban:

159

Dimulai dengan Go 1.4 Anda dapat mengimplementasikan pengaturan / pembongkaran (tidak perlu menyalin fungsi Anda sebelum / setelah setiap pengujian). Dokumentasi diuraikan di sini di bagian Utama :

TestMain berjalan di goroutine utama dan dapat melakukan penyiapan dan pembongkaran apa pun yang diperlukan seputar panggilan ke m.Run. Ini kemudian harus memanggil os.Exit dengan hasil m.Run

Butuh beberapa saat bagi saya untuk mengetahui bahwa ini berarti jika pengujian berisi fungsi func TestMain(m *testing.M)maka fungsi ini akan dipanggil alih-alih menjalankan pengujian. Dan dalam fungsi ini saya dapat menentukan bagaimana tes akan berjalan. Misalnya saya dapat mengimplementasikan pengaturan global dan pembongkaran:

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

Beberapa contoh lainnya dapat ditemukan di sini .

Fitur TestMain yang ditambahkan ke framework pengujian Go di rilis terbaru adalah solusi sederhana untuk beberapa kasus penggunaan pengujian. TestMain menyediakan hook global untuk melakukan penyiapan dan penghentian, mengontrol lingkungan pengujian, menjalankan kode yang berbeda dalam proses anak, atau memeriksa sumber daya yang bocor oleh kode pengujian. Sebagian besar paket tidak memerlukan TestMain, tetapi ini adalah tambahan yang disambut baik untuk saat-saat dibutuhkan.

Salvador Dali
sumber
17
TestMainadalah satu kali dalam satu paket, jadi tidak terlalu berguna. Saya menemukan subtes lebih baik untuk tujuan yang lebih kompleks.
Inanc Gumus
3
Bagaimana Anda bisa meneruskan konteks dari fungsi setup ke tes tanpa menggunakan variabel global? Misalnya jika mySetupFunction () membuat direktori sementara untuk melakukan pengujian di (dengan nama acak yang unik), bagaimana pengujian mengetahui nama direktori tersebut? Harus ada tempat untuk menyetel konteks ini ??
Lqueryvg
1
Tampaknya ini adalah cara resmi untuk menangani hook sebelum dan sesudah untuk pengujian, lihat golang.org/pkg/testing/#hdr-Main untuk dokumentasi resmi
de-jcup
4
@InancGumuslstat $GOROOT/subtests: no such file or directory
030
1
harap dicatat bahwa 'code: = m.Run ()' adalah salah satu yang menjalankan TestFunctions!
Alex Punnen
49

Ini dapat dicapai dengan meletakkan init()fungsi di _test.gofile. Ini akan dijalankan sebelum init()fungsi.

// package_test.go
package main

func init() {
     /* load test data */
}

_Test.init () akan dipanggil sebelum fungsi package init ().

miltonb
sumber
2
Saya tahu Anda menjawab pertanyaan Anda sendiri jadi ini mungkin memenuhi kasus penggunaan Anda sendiri, tetapi ini tidak setara dengan contoh NUnit yang Anda sertakan dalam pertanyaan Anda.
James Henstridge
Ya @james, saya telah menunjukkan satu pemikiran tentang cara untuk menjawab masalah ini dan yang lain telah memberikan beberapa wawasan yang baik, termasuk milik Anda. Ini berguna untuk mendapatkan pengaruh luar untuk menyesuaikan pendekatan seseorang. Terima kasih.
miltonb
2
Cukup adil. Apa yang Anda tunjukkan dalam jawaban ini agak mendekati menggunakan [TestFixtureSetUp]atribut NUnit sebagai gantinya.
James Henstridge
2
itu tidak termasuk merobohkan bagian
Taras Matsyk
7
Ini bukan solusi yang baik jika file pengujian Anda berada dalam paket yang sama dengan fungsi utama.
MouseWanted
28

Diberikan fungsi sederhana untuk pengujian unit:

package math

func Sum(a, b int) int {
    return a + b
}

Anda dapat mengujinya dengan fungsi pengaturan yang mengembalikan fungsi pembongkaran. Dan setelah memanggil setup () Anda dapat melakukan panggilan yang ditangguhkan ke teardown ().

package math

import "testing"

func setupTestCase(t *testing.T) func(t *testing.T) {
    t.Log("setup test case")
    return func(t *testing.T) {
        t.Log("teardown test case")
    }
}

func setupSubTest(t *testing.T) func(t *testing.T) {
    t.Log("setup sub test")
    return func(t *testing.T) {
        t.Log("teardown sub test")
    }
}

func TestAddition(t *testing.T) {
    cases := []struct {
        name     string
        a        int
        b        int
        expected int
    }{
        {"add", 2, 2, 4},
        {"minus", 0, -2, -2},
        {"zero", 0, 0, 0},
    }

    teardownTestCase := setupTestCase(t)
    defer teardownTestCase(t)

    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            teardownSubTest := setupSubTest(t)
            defer teardownSubTest(t)

            result := Sum(tc.a, tc.b)
            if result != tc.expected {
                t.Fatalf("expected sum %v, but got %v", tc.expected, result)
            }
        })
    }
}

Alat pengujian Go akan melaporkan pernyataan logging di konsol shell:

% go test -v
=== RUN   TestAddition
=== RUN   TestAddition/add
=== RUN   TestAddition/minus
=== RUN   TestAddition/zero
--- PASS: TestAddition (0.00s)
    math_test.go:6: setup test case
    --- PASS: TestAddition/add (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    --- PASS: TestAddition/minus (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    --- PASS: TestAddition/zero (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    math_test.go:8: teardown test case
PASS
ok      github.com/kare/go-unit-test-setup-teardown 0.010s
% 

Anda dapat meneruskan beberapa parameter tambahan ke penyiapan / pembongkaran dengan pendekatan ini.

Kare Nuorteva
sumber
2
Nah, itu adalah trik yang sangat sederhana namun efektif. Penggunaan sintaks Go yang hebat.
miltonb
1
Ya, tapi itu meningkatkan nestedness ( semacam piramida malapetaka di javascript ). Dan, pengujian tidak dijalankan secara otomatis oleh suite seperti di pengujian luar.
Inanc Gumus
12

Biasanya, pengujian dalam go tidak ditulis dalam gaya yang sama seperti bahasa lain. Seringkali, ada fungsi pengujian yang relatif lebih sedikit, tetapi masing-masing berisi sekumpulan kasus pengujian berdasarkan tabel. Lihat artikel ini yang ditulis oleh salah satu tim Go.

Dengan pengujian berbasis tabel, Anda cukup meletakkan kode penyiapan apa pun sebelum loop yang menjalankan kasus uji individual yang ditentukan dalam tabel, dan meletakkan kode pembersihan apa pun setelahnya.

Jika Anda masih memiliki kode penyiapan yang dibagikan di antara fungsi pengujian, Anda dapat mengekstrak kode penyiapan bersama ke dalam suatu fungsi, dan menggunakan a sync.Oncejika penting agar dijalankan tepat satu kali (atau seperti yang disarankan oleh jawaban lain, gunakan init(), tetapi ini memiliki kerugian bahwa penyiapan akan dilakukan meskipun kasus pengujian tidak berjalan (mungkin karena Anda telah membatasi kasus pengujian dengan menggunakan go test -run <regexp>.)

Saya akan mengatakan jika Anda berpikir Anda memerlukan pengaturan bersama antara pengujian berbeda yang dijalankan tepat setelah Anda harus berpikir jika Anda benar-benar membutuhkannya, dan jika pengujian berbasis tabel tidak akan lebih baik.

Paul Hankin
sumber
6
Itu bagus saat menguji hal-hal sepele seperti parser bendera, atau algoritma yang menghasilkan angka. Tetapi itu tidak terlalu membantu ketika mencoba untuk menguji berbagai bagian fungsionalitas yang semuanya membutuhkan kode boilerplate yang serupa. Saya kira saya dapat mendefinisikan fungsi pengujian saya dalam sebuah array dan mengulanginya, tapi kemudian itu tidak benar-benar digerakkan oleh tabel sebanyak loop sederhana yang seharusnya benar-benar dibangun ke dalam kerangka pengujian itu sendiri (dalam bentuk rangkaian pengujian yang tepat dengan fungsi penyetelan /
pembongkaran
9

Kerangka pengujian Go tidak memiliki apa pun yang setara dengan atribut SetUp NUnit (menandai fungsi yang akan dipanggil sebelum setiap pengujian di suite). Ada beberapa opsi:

  1. Cukup panggil SetUpfungsi Anda dari setiap pengujian jika diperlukan.

  2. Gunakan ekstensi untuk kerangka pengujian Go yang mengimplementasikan paradigma dan konsep xUnit. Tiga opsi kuat muncul di benak:

Masing-masing pustaka ini mendorong Anda untuk mengatur pengujian Anda ke dalam suite / perlengkapan yang mirip dengan kerangka kerja xUnit lainnya, dan akan memanggil metode penyiapan pada tipe suite / perlengkapan sebelum masing-masing Test*metode.

James Henstridge
sumber
0

Steker tak tahu malu, saya membuat https://github.com/houqp/gtest untuk membantu menyelesaikan masalah ini dengan tepat.

Berikut ini contoh singkatnya:

import (
  "strings"
  "testing"
  "github.com/houqp/gtest"
)

type SampleTests struct{}

// Setup and Teardown are invoked per test group run
func (s *SampleTests) Setup(t *testing.T)      {}
func (s *SampleTests) Teardown(t *testing.T)   {}
// BeforeEach and AfterEach are invoked per test run
func (s *SampleTests) BeforeEach(t *testing.T) {}
func (s *SampleTests) AfterEach(t *testing.T)  {}

func (s *SampleTests) SubTestCompare(t *testing.T) {
  if 1 != 1 {
    t.FailNow()
  }
}

func (s *SampleTests) SubTestCheckPrefix(t *testing.T) {
  if !strings.HasPrefix("abc", "ab") {
    t.FailNow()
  }
}

func TestSampleTests(t *testing.T) {
  gtest.RunSubTests(t, &SampleTests{})
}

Anda dapat membuat grup uji apa pun yang Anda inginkan dalam sebuah paket dengan masing-masing grup tersebut menggunakan rangkaian rutinitas penyiapan / pembongkaran yang berbeda.

houqp
sumber