Memisahkan pengujian unit dan pengujian integrasi di Go

98

Apakah ada praktik terbaik yang ditetapkan untuk memisahkan pengujian unit dan pengujian integrasi di GoLang (bersaksi)? Saya memiliki campuran tes unit (yang tidak bergantung pada sumber daya eksternal dan dengan demikian berjalan sangat cepat) dan tes integrasi (yang bergantung pada sumber daya eksternal dan dengan demikian berjalan lebih lambat). Jadi, saya ingin dapat mengontrol apakah akan menyertakan tes integrasi atau tidak ketika saya katakan go test.

Teknik yang paling lurus ke depan adalah dengan mendefinisikan sebuah flag -integrate di main:

var runIntegrationTests = flag.Bool("integration", false
    , "Run the integration tests (in addition to the unit tests)")

Dan kemudian untuk menambahkan pernyataan-if ke bagian atas dari setiap pengujian integrasi:

if !*runIntegrationTests {
    this.T().Skip("To run this test, use: go test -integration")
}

Apakah ini yang terbaik yang bisa saya lakukan? Saya mencari dokumentasi bersaksi untuk melihat apakah mungkin ada konvensi penamaan atau sesuatu yang menyelesaikan ini untuk saya, tetapi tidak menemukan apa pun. Apakah saya melewatkan sesuatu?

Craig Jones
sumber
2
Saya pikir stdlib menggunakan -short untuk menonaktifkan tes yang menghantam jaringan (dan hal-hal longrunning lainnya juga). Jika tidak, solusi Anda terlihat oke.
Volker
-short adalah opsi yang bagus, seperti halnya flag build kustom Anda, tetapi flag Anda tidak perlu berada di main. jika Anda mendefinisikan var sebagai di var integration = flag.Bool("integration", true, "Enable integration testing.")luar fungsi, variabel akan muncul dalam cakupan paket, dan flag akan bekerja dengan baik
Atifm

Jawaban:

156

@ Ainar-G menyarankan beberapa pola bagus untuk memisahkan pengujian.

Kumpulan praktik Go dari SoundCloud ini merekomendasikan penggunaan tag build ( dijelaskan di bagian "Batasan Build" dari paket build ) untuk memilih pengujian mana yang akan dijalankan:

Tulis sebuah integration_test.go, dan berikan tag build integrasi. Tentukan tanda (global) untuk hal-hal seperti alamat layanan dan string koneksi, dan gunakan dalam pengujian Anda.

// +build integration

var fooAddr = flag.String(...)

func TestToo(t *testing.T) {
    f, err := foo.Connect(*fooAddr)
    // ...
}

go test membutuhkan tag build seperti go build, sehingga Anda dapat memanggil go test -tags=integration. Ia juga mensintesis sebuah paket utama yang memanggil flag.Parse, sehingga setiap flag yang dideklarasikan dan terlihat akan diproses dan tersedia untuk pengujian Anda.

Sebagai opsi serupa, Anda juga bisa menjalankan pengujian integrasi secara default dengan menggunakan kondisi build // +build !unit, lalu menonaktifkannya sesuai permintaan dengan menjalankango test -tags=unit .

@adamc komentar:

Untuk orang lain yang mencoba menggunakan tag build, penting bahwa // +build testkomentar tersebut adalah baris pertama dalam file Anda, dan Anda menyertakan baris kosong setelah komentar, jika tidak,-tags perintah akan mengabaikan perintah tersebut.

Selain itu, tag yang digunakan dalam komentar build tidak boleh menggunakan tanda hubung, meskipun garis bawah diperbolehkan. Misalnya, // +build unit-teststidak akan berhasil, sedangkan // +build unit_testsakan.

Alex
sumber
1
Saya telah menggunakan ini untuk beberapa waktu sekarang dan sejauh ini merupakan pendekatan yang paling logis dan sederhana.
Ory Band
1
jika Anda memiliki pengujian unit dalam paket yang sama, Anda perlu mengatur // + build unitpengujian unit dan menggunakan -tag unit untuk menjalankan pengujian
LeoCBS
2
@ Tyler.z.yang dapatkah Anda memberikan tautan atau detail lebih lanjut tentang penghentian tag? Saya tidak menemukan informasi seperti itu. Saya menggunakan tag dengan go1.8 untuk cara yang dijelaskan dalam jawaban dan juga untuk mengejek jenis dan fungsi dalam pengujian. Saya pikir ini adalah alternatif yang bagus untuk antarmuka.
Alexander I.Grafov
2
Bagi orang lain yang mencoba menggunakan tag build, penting bahwa // +buildkomentar pengujian adalah baris pertama dalam file Anda, dan Anda menyertakan baris kosong setelah komentar, jika tidak, -tagsperintah akan mengabaikan perintah tersebut. Selain itu, tag yang digunakan dalam komentar build tidak boleh menggunakan tanda hubung, meskipun garis bawah diperbolehkan. Misalnya, // +build unit-teststidak akan berhasil, sedangkan // +build unit_testsakan
adamc
6
Bagaimana cara menangani wildcard? go test -tags=integration ./...tidak bekerja, itu mengabaikan tag
Erika Dsouza
54

Untuk menguraikan komentar saya untuk jawaban yang sangat baik @ Ainar-G, selama setahun terakhir saya telah menggunakan kombinasi -shortdenganIntegration konvensi penamaan untuk mencapai yang terbaik dari kedua dunia.

Unit dan Integrasi menguji harmoni, dalam file yang sama

Bendera build sebelumnya memaksa saya memiliki banyak file (services_test.go ,services_integration_test.go , dll).

Sebagai gantinya, ambil contoh di bawah ini di mana dua yang pertama adalah pengujian unit dan saya memiliki pengujian integrasi di bagian akhir:

package services

import "testing"

func TestServiceFunc(t *testing.T) {
    t.Parallel()
    ...
}

func TestInvalidServiceFunc3(t *testing.T) {
    t.Parallel()
    ...
}

func TestPostgresVersionIntegration(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping integration test")
    }
    ...
}

Perhatikan tes terakhir memiliki konvensi:

  1. menggunakan Integration dalam nama tes.
  2. memeriksa apakah berjalan di bawah -shortperintah bendera.

Pada dasarnya, spesifikasi tersebut berbunyi: "tulis semua pengujian secara normal. Jika ini adalah pengujian yang berjalan lama, atau pengujian integrasi, ikuti konvensi penamaan ini dan periksa -short bersikap baik kepada rekan Anda."

Jalankan hanya tes Unit:

go test -v -short

ini memberi Anda serangkaian pesan yang bagus seperti:

=== RUN   TestPostgresVersionIntegration
--- SKIP: TestPostgresVersionIntegration (0.00s)
        service_test.go:138: skipping integration test

Jalankan Tes Integrasi saja:

go test -run Integration

Ini hanya menjalankan tes integrasi. Berguna untuk kenari penguji asap dalam produksi.

Jelas sisi negatif dari pendekatan ini adalah jika ada yang menjalankan go test, tanpa ekstensi-short flag, itu akan secara default menjalankan semua tes - tes unit dan integrasi.

Pada kenyataannya, jika proyek Anda cukup besar untuk memiliki pengujian unit dan integrasi, kemungkinan besar Anda menggunakan di Makefilemana Anda dapat memiliki arahan sederhana untuk digunakan go test -shortdi dalamnya. Atau, masukkan saja ke dalam README.mdfile Anda dan hentikan.

eduncan911
sumber
3
suka kesederhanaan
Jacob Stanley
Apakah Anda membuat paket terpisah untuk pengujian tersebut untuk hanya mengakses bagian umum dari paket? Atau semua campuran?
Dr.eel
@ Dr.eel Nah, itu OT dari jawabannya. Tapi secara pribadi, saya lebih suka keduanya: nama paket yang berbeda untuk pengujian sehingga saya dapat importmembuat paket dan mengujinya, yang akhirnya menunjukkan kepada saya seperti apa API saya bagi orang lain. Saya kemudian menindaklanjuti dengan logika tersisa yang perlu dicakup sebagai nama paket pengujian internal.
eduncan911
@ eduncan911 Terima kasih atas jawabannya! Jadi seperti yang saya pahami di sini package servicesberisi sute uji integrasi, jadi untuk menguji APIfo paket sebagai kotak hitam kita harus menamainya dengan cara lain package services_integration_testitu tidak akan memberi kita kesempatan untuk bekerja dengan struktur internal. Jadi paket untuk pengujian unit (mengakses internal) harus diberi nama package services. Begitu?
Dr.eel
Benar, ya. Berikut adalah contoh bersih bagaimana saya melakukannya: github.com/eduncan911/podcast (perhatikan cakupan kode 100%, menggunakan Contoh)
eduncan911
50

Saya melihat tiga solusi yang mungkin. Yang pertama adalah menggunakan mode pendek untuk pengujian unit. Jadi, Anda akan menggunakan go test -shortdengan unit test dan sama tetapi tanpa-short tanda untuk menjalankan pengujian integrasi Anda juga. Pustaka standar menggunakan mode pendek untuk melewati pengujian yang berjalan lama, atau membuatnya berjalan lebih cepat dengan menyediakan data yang lebih sederhana.

Yang kedua adalah menggunakan konvensi dan memanggil pengujian Anda, TestUnitFooatau TestIntegrationFoolalu gunakan tanda -runpengujian untuk menunjukkan pengujian mana yang akan dijalankan. Jadi, Anda akan menggunakannya go test -run 'Unit'untuk pengujian unit dango test -run 'Integration' untuk pengujian integrasi.

Opsi ketiga adalah menggunakan variabel lingkungan, dan mendapatkannya dalam penyiapan pengujian Anda os.Getenv. Kemudian Anda akan menggunakan simple go testuntuk pengujian unit danFOO_TEST_INTEGRATION=true go test untuk pengujian integrasi.

Saya pribadi lebih suka -shortsolusinya karena lebih sederhana dan digunakan di perpustakaan standar, jadi sepertinya ini cara de facto untuk memisahkan / menyederhanakan pengujian yang berjalan lama. Tapi solusi -rundan os.Getenvmenawarkan lebih banyak fleksibilitas (lebih banyak kehati-hatian juga diperlukan, karena regexps terlibat dengan -run).

Ainar-G
sumber
1
perhatikan bahwa pelari pengujian komunitas (mis. Tester-Go) yang umum untuk IDE (Atom, Sublime, dll) memiliki opsi bawaan untuk dijalankan dengan -shortflag, bersama dengan -coveragedan lainnya. oleh karena itu, saya menggunakan kombinasi dari kedua Integrasi dalam nama pengujian, bersama dengan if testing.Short()pemeriksaan dalam pengujian tersebut. itu memungkinkan saya untuk mendapatkan yang terbaik dari kedua dunia: dijalankan dengan -shortdalam IDE, dan secara eksplisit hanya menjalankan pengujian integrasi dengango test -run "Integration"
eduncan911
5

Saya mencoba mencari solusi untuk hal yang sama baru-baru ini. Ini adalah kriteria saya:

  • Solusinya harus universal
  • Tidak ada paket terpisah untuk pengujian integrasi
  • Pemisahan harus selesai (saya harus dapat menjalankan tes integrasi saja )
  • Tidak ada konvensi penamaan khusus untuk pengujian integrasi
  • Ini harus bekerja dengan baik tanpa perkakas tambahan

Solusi yang disebutkan di atas (bendera khusus, tag build khusus, variabel lingkungan) tidak benar-benar memenuhi semua kriteria di atas, jadi setelah sedikit menggali dan bermain, saya menemukan solusi ini:

package main

import (
    "flag"
    "regexp"
    "testing"
)

func TestIntegration(t *testing.T) {
    if m := flag.Lookup("test.run").Value.String(); m == "" || !regexp.MustCompile(m).MatchString(t.Name()) {
        t.Skip("skipping as execution was not requested explicitly using go test -run")
    }

    t.Parallel()

    t.Run("HelloWorld", testHelloWorld)
    t.Run("SayHello", testSayHello)
}

Penerapannya mudah dan minimal. Meskipun memerlukan konvensi sederhana untuk pengujian, tetapi tidak terlalu rentan terhadap kesalahan. Perbaikan lebih lanjut dapat mengekspor kode ke fungsi pembantu.

Pemakaian

Jalankan pengujian integrasi hanya di semua paket dalam sebuah proyek:

go test -v ./... -run ^TestIntegration$

Jalankan semua tes ( reguler dan integrasi):

go test -v ./... -run .\*

Jalankan tes biasa saja :

go test -v ./...

Solusi ini bekerja dengan baik tanpa perkakas, tetapi Makefile atau beberapa alias dapat membuatnya lebih mudah untuk pengguna. Ini juga dapat dengan mudah diintegrasikan ke dalam IDE apa pun yang mendukung pengujian go.

Contoh lengkapnya dapat ditemukan di sini: https://github.com/sagikazarmark/modern-go-application

mark.sagikazar
sumber