ฉันจะทดสอบการตั้งค่าโดยใช้แพ็คเกจการทดสอบใน Go ได้อย่างไร


111

ฉันจะประมวลผลการตั้งค่าการทดสอบโดยรวมซึ่งกำหนดขั้นตอนสำหรับการทดสอบทั้งหมดเมื่อใช้แพ็คเกจการทดสอบได้อย่างไร

ดังตัวอย่างใน Nunit มี[SetUp]แอตทริบิวต์

[TestFixture]
public class SuccessTests
{
  [SetUp] public void Init()
  { /* Load test data */ }
}

2
เริ่มต้นด้วย 1.4 คุณสามารถตั้งค่าทั่วโลกและ
Salvador Dali

คำตอบ:


159

เริ่มต้นด้วย Go 1.4 คุณสามารถใช้การตั้งค่า / การฉีกขาดได้ (ไม่จำเป็นต้องคัดลอกฟังก์ชันของคุณก่อน / หลังการทดสอบแต่ละครั้ง) เอกสารประกอบระบุไว้ที่นี่ในส่วนหลัก :

TestMain ทำงานใน goroutine หลักและสามารถทำอะไรก็ได้ที่จำเป็นต้องมีการตั้งค่าและการฉีกขาดเมื่อโทรไปที่ m.Run จากนั้นควรเรียก os.Exit ด้วยผลลัพธ์ของ m.Run

ฉันใช้เวลาพอสมควรในการคิดออกว่านี่หมายความว่าหากการทดสอบมีฟังก์ชันฟังก์ชันfunc TestMain(m *testing.M)นี้จะถูกเรียกแทนการเรียกใช้การทดสอบ และในฟังก์ชั่นนี้ฉันสามารถกำหนดวิธีการทดสอบได้ ตัวอย่างเช่นฉันสามารถใช้การตั้งค่าส่วนกลางและการฉีกขาดได้:

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

คู่ของตัวอย่างอื่น ๆสามารถพบได้ที่นี่

คุณลักษณะ TestMain ที่เพิ่มเข้ามาในกรอบการทดสอบของ Go ในรุ่นล่าสุดเป็นโซลูชันง่ายๆสำหรับกรณีการใช้งานการทดสอบต่างๆ TestMain จัดเตรียมเบ็ดส่วนกลางเพื่อดำเนินการตั้งค่าและปิดระบบควบคุมสภาพแวดล้อมการทดสอบเรียกใช้รหัสอื่นในกระบวนการย่อยหรือตรวจสอบทรัพยากรที่รั่วไหลโดยรหัสทดสอบ แพ็กเกจส่วนใหญ่ไม่จำเป็นต้องมี TestMain แต่เป็นส่วนเสริมที่น่ายินดีสำหรับช่วงเวลาเหล่านั้นเมื่อจำเป็น


17
TestMainครั้งเดียวในแพ็กเกจจึงไม่ค่อยมีประโยชน์ ฉันพบว่าการทดสอบย่อยดีกว่าสำหรับวัตถุประสงค์ที่ซับซ้อนมากขึ้น
Inanc Gumus

3
คุณควรส่งบริบทจากฟังก์ชันการตั้งค่าไปยังการทดสอบโดยไม่ใช้ตัวแปรส่วนกลางอย่างไร ตัวอย่างเช่นถ้า mySetupFunction () สร้างไดเร็กทอรีชั่วคราวเพื่อทำการทดสอบ (ด้วยชื่อที่ไม่ซ้ำกันและสุ่ม) การทดสอบจะทราบชื่อไดเร็กทอรีได้อย่างไร? ต้องมีสถานที่ตั้งบริบทนี้ ??
Lqueryvg

1
ดูเหมือนว่านี่เป็นวิธีที่เป็นทางการในการจัดการก่อนและหลังตะขอสำหรับการทดสอบโปรดดูgolang.org/pkg/testing/#hdr-Mainสำหรับเอกสารอย่างเป็นทางการ
de-jcup

4
@InancGumuslstat $GOROOT/subtests: no such file or directory
030

1
โปรดทราบว่า 'code: = m.Run ()' คือรหัสที่เรียกใช้ TestFunctions อื่น ๆ !
Alex Punnen

49

ซึ่งสามารถทำได้โดยการใส่init()ฟังก์ชันลงใน_test.goไฟล์ สิ่งนี้จะถูกเรียกใช้ก่อนinit()ฟังก์ชัน

// package_test.go
package main

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

_test.init () จะถูกเรียกก่อนฟังก์ชัน init () แพ็คเกจ


2
ฉันรู้ว่าคุณกำลังตอบคำถามของคุณเองดังนั้นสิ่งนี้อาจตรงกับกรณีการใช้งานของคุณเอง แต่นี่ไม่เทียบเท่ากับตัวอย่าง NUnit ที่คุณรวมไว้ในคำถามของคุณ
James Henstridge

@james ฉันได้แสดงความคิดอย่างหนึ่งในการตอบปัญหาและคนอื่น ๆ ก็ได้ให้ข้อมูลเชิงลึกที่ดีรวมถึงของคุณด้วย มีประโยชน์ในการได้รับอิทธิพลจากภายนอกเพื่อปรับเข้าหาคน ขอบคุณ.
miltonb

2
พอใช้. สิ่งที่คุณแสดงในคำตอบนี้ค่อนข้างใกล้เคียงกับการใช้[TestFixtureSetUp]แอตทริบิวต์ของ NUnit แทน
James Henstridge

2
ไม่รวมส่วนที่ฉีกขาด
Taras Matsyk

7
นี่ไม่ใช่วิธีแก้ปัญหาที่ดีหากไฟล์ทดสอบของคุณอยู่ในแพ็คเกจเดียวกันกับฟังก์ชันหลัก
MouseWanted

28

ให้ฟังก์ชันง่ายๆในการทดสอบหน่วย:

package math

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

คุณสามารถทดสอบได้ด้วยฟังก์ชันการตั้งค่าที่ส่งคืนฟังก์ชันการฉีกขาด และหลังจากเรียกการตั้งค่า () คุณสามารถโทรเลื่อนไปที่ 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)
            }
        })
    }
}

เครื่องมือทดสอบ Go จะรายงานคำสั่งการบันทึกในเชลล์คอนโซล:

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

คุณสามารถส่งผ่านพารามิเตอร์เพิ่มเติมบางอย่างไปยังการตั้งค่า / การฉีกขาดด้วยวิธีนี้


2
ตอนนี้เป็นเคล็ดลับง่ายๆ แต่ได้ผลจริง ใช้ไวยากรณ์ Go ได้อย่างยอดเยี่ยม
miltonb

1
ใช่ แต่มันเพิ่มความซ้อนกัน ( ชนิดของปิรามิดแห่งการลงโทษในจาวาสคริปต์ ) และการทดสอบจะไม่ทำงานโดยอัตโนมัติโดยชุดโปรแกรมเช่นเดียวกับการทดสอบภายนอก
Inanc Gumus

12

โดยทั่วไปแล้วการทดสอบใน go จะไม่เขียนในรูปแบบเดียวกับภาษาอื่น ๆ บ่อยครั้งที่มีฟังก์ชั่นการทดสอบค่อนข้างน้อย แต่แต่ละฟังก์ชันมีชุดกรณีทดสอบที่ขับเคลื่อนด้วยตาราง ดูบทความนี้เขียนโดยหนึ่งในทีม Go

ด้วยการทดสอบแบบใช้ตารางคุณเพียงแค่ใส่รหัสการตั้งค่าใด ๆ ก่อนลูปที่ดำเนินการกรณีทดสอบแต่ละกรณีที่ระบุในตารางและใส่รหัสล้างข้อมูลในภายหลัง

หากคุณยังคงมีรหัสการตั้งค่าที่ใช้ร่วมกันระหว่างฟังก์ชันการทดสอบคุณสามารถแยกรหัสการตั้งค่าที่ใช้ร่วมกันออกเป็นฟังก์ชันและใช้sync.Onceถ้าสิ่งสำคัญคือต้องดำเนินการเพียงครั้งเดียว (หรือตามคำตอบอื่นแนะนำให้ใช้init()แต่สิ่งนี้มีข้อเสียที่การตั้งค่า จะดำเนินการแม้ว่ากรณีทดสอบจะไม่ทำงาน (อาจเป็นเพราะคุณ จำกัด กรณีทดสอบโดยใช้go test -run <regexp>)

ฉันจะบอกว่าถ้าคุณคิดว่าคุณต้องการการตั้งค่าที่ใช้ร่วมกันระหว่างการทดสอบต่างๆที่ได้รับการดำเนินการทันทีคุณควรคิดว่าคุณต้องการจริงๆหรือไม่และถ้าการทดสอบแบบใช้ตารางจะไม่ดีกว่า


7
ยอดเยี่ยมมากเมื่อทดสอบสิ่งเล็กน้อยเช่นตัวแยกวิเคราะห์ค่าสถานะหรืออัลกอริทึมที่หมุนตัวเลข แต่มันไม่ได้ช่วยอะไรมากนักเมื่อพยายามทดสอบฟังก์ชันการทำงานที่หลากหลายซึ่งล้วนต้องใช้รหัสต้นแบบที่คล้ายกัน ฉันคิดว่าฉันสามารถกำหนดฟังก์ชั่นการทดสอบของฉันในอาร์เรย์และทำซ้ำมากกว่าสิ่งเหล่านั้น แต่มันก็ไม่ได้ขับเคลื่อนด้วยตารางมากเท่ากับลูปธรรมดาที่ควรสร้างไว้ในกรอบการทดสอบเท่านั้นเอง (ในรูปแบบของชุดทดสอบที่เหมาะสม พร้อมฟังก์ชั่นการตั้งค่า / การฉีกขาด)
iamtheddrman

9

กรอบการทดสอบ Go ไม่มีอะไรเทียบเท่ากับแอตทริบิวต์ SetUpของ NUnit (ทำเครื่องหมายฟังก์ชันที่จะเรียกก่อนการทดสอบแต่ละครั้งในชุด) มีตัวเลือกไม่กี่ตัวเลือก:

  1. เพียงเรียกSetUpใช้ฟังก์ชันของคุณจากการทดสอบแต่ละครั้งที่จำเป็น

  2. ใช้ส่วนขยายกับกรอบการทดสอบของ Go ที่ใช้กระบวนทัศน์และแนวคิด xUnit สามตัวเลือกที่ดีในใจ:

แต่ละไลบรารีเหล่านี้สนับสนุนให้คุณจัดระเบียบการทดสอบของคุณเป็นห้องชุด / ส่วนควบคล้ายกับเฟรมเวิร์ก xUnit อื่น ๆ และจะเรียกใช้วิธีการตั้งค่าในประเภทชุด / ฟิกซ์เจอร์ก่อนแต่ละTest*วิธี


0

ปลั๊กไร้ยางอายฉันสร้างhttps://github.com/houqp/gtestเพื่อช่วยแก้ปัญหานี้

นี่คือตัวอย่างสั้น ๆ :

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{})
}

คุณสามารถสร้างเป็นกลุ่มทดสอบใดก็ได้ที่คุณต้องการภายในแพ็กเกจโดยใช้ชุดการตั้งค่า / ขั้นตอนการฉีกขาดที่แตกต่างกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.