Làm cách nào để thực hiện thiết lập thử nghiệm bằng gói thử nghiệm trong Go


111

Làm cách nào để thực hiện xử lý thiết lập thử nghiệm tổng thể tạo tiền đề cho tất cả các thử nghiệm khi sử dụng gói thử nghiệm ?

Như một ví dụ trong Nunit có một [SetUp]thuộc tính.

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

2
Bắt đầu với 1.4, bạn có thể thiết lập toàn cầu và chia nhỏ
Salvador Dali

Câu trả lời:


159

Bắt đầu với Go 1.4, bạn có thể thực hiện thiết lập / xé nhỏ (không cần sao chép các chức năng của bạn trước / sau mỗi lần kiểm tra). Tài liệu được nêu ở đây trong phần Chính :

TestMain chạy trong chương trình chính và có thể thực hiện bất cứ điều gì thiết lập và gỡ bỏ là cần thiết xung quanh cuộc gọi đến m.Run. Sau đó, nó sẽ gọi os.Exit với kết quả là m.Run

Tôi đã mất một thời gian để tìm ra rằng điều này có nghĩa là nếu một bài kiểm tra chứa một hàm func TestMain(m *testing.M)thì hàm này sẽ được gọi thay vì chạy bài kiểm tra. Và trong chức năng này, tôi có thể xác định cách các bài kiểm tra sẽ chạy. Ví dụ: tôi có thể triển khai thiết lập toàn cầu và chia nhỏ:

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

Một vài ví dụ khác có thể được tìm thấy ở đây .

Tính năng TestMain được thêm vào khung thử nghiệm của Go trong bản phát hành mới nhất là một giải pháp đơn giản cho một số trường hợp sử dụng thử nghiệm. TestMain cung cấp một hook toàn cầu để thực hiện thiết lập và tắt máy, kiểm soát môi trường thử nghiệm, chạy mã khác trong quy trình con hoặc kiểm tra các tài nguyên bị rò rỉ bởi mã thử nghiệm. Hầu hết các gói sẽ không cần TestMain, nhưng nó là một bổ sung đáng hoan nghênh cho những lúc cần thiết.


17
TestMainlà một lần trong một gói, vì vậy nó không hữu ích. Tôi thấy phép thử phụ là tốt hơn cho mục đích phức tạp hơn.
Inanc Gumus

3
Làm thế nào bạn có thể chuyển ngữ cảnh từ hàm thiết lập đến các bài kiểm tra mà không sử dụng các biến toàn cục? Ví dụ: nếu mySetupFunction () tạo một thư mục tạm thời để thực hiện kiểm tra trong (với một tên ngẫu nhiên, duy nhất), thì làm cách nào để kiểm tra biết được tên của thư mục? Phải có một nơi để đặt bối cảnh này ??
Lqueryvg

1
Dường như đây là cách chính thức để xử lý trước và sau khi móc cho các bài kiểm tra, xem golang.org/pkg/testing/#hdr-Main cho tài liệu chính thức
de-jcup

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

1
xin lưu ý rằng 'code: = m.Run ()' là mã chạy các Chức năng Kiểm tra khác!
Alex Punnen

49

Điều này có thể đạt được bằng cách đưa một init()hàm vào _test.gotệp. Điều này sẽ được chạy trước init()hàm.

// package_test.go
package main

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

_Test.init () sẽ được gọi trước hàm gói init ().


2
Tôi biết bạn đang trả lời câu hỏi của chính mình nên điều này có thể đáp ứng trường hợp sử dụng của riêng bạn, nhưng điều này không tương đương với ví dụ NUnit mà bạn đã đưa vào câu hỏi của mình.
James Henstridge

Chà @james, tôi đã chỉ ra một cách để trả lời vấn đề và những người khác đã cung cấp một số thông tin chi tiết tốt, bao gồm cả của bạn. Nó hữu ích để có được những ảnh hưởng từ bên ngoài để điều chỉnh cách tiếp cận của chúng. Cảm ơn.
miltonb

2
Đủ công bằng. Những gì bạn đã hiển thị trong câu trả lời này hơi gần với việc sử dụng [TestFixtureSetUp]thuộc tính NUnit thay thế.
James Henstridge

2
nó không bao gồm nước mắt xuống phần
Taras Matsyk

7
Đây không phải là một giải pháp tốt nếu tệp thử nghiệm của bạn nằm trong cùng một gói với chức năng chính.
Chuột mong muốn

28

Đưa ra một hàm đơn giản để kiểm tra đơn vị:

package math

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

Bạn có thể kiểm tra nó bằng một hàm thiết lập trả về hàm teardown. Và sau khi gọi setup (), bạn có thể thực hiện cuộc gọi hoãn lại tới 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)
            }
        })
    }
}

Công cụ kiểm tra Go sẽ báo cáo các câu lệnh ghi nhật ký trong bảng điều khiển 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
% 

Bạn có thể chuyển một số tham số bổ sung để thiết lập / xé nhỏ với cách tiếp cận này.


2
Bây giờ đó là một thủ thuật thực sự đơn giản nhưng hiệu quả. Sử dụng tuyệt vời cú pháp Go.
miltonb

1
Vâng, nhưng nó làm tăng tính lồng ghép ( kiểu kim tự tháp của sự diệt vong trong javascript ). Và, các bài kiểm tra không chạy tự động bởi bộ như trong các bài kiểm tra bên ngoài.
Inanc Gumus

12

Thông thường, các bài kiểm tra đang hoạt động không được viết theo cùng một phong cách như các ngôn ngữ khác. Thông thường, có tương đối ít chức năng kiểm tra hơn, nhưng mỗi hàm chứa một tập hợp các trường hợp kiểm tra hướng bảng. Xem bài viết này được viết bởi một trong những đội cờ vây.

Với kiểm tra hướng bảng, bạn chỉ cần đặt bất kỳ mã thiết lập nào trước vòng lặp thực thi các trường hợp thử nghiệm riêng lẻ được chỉ định trong bảng và đặt bất kỳ mã dọn dẹp nào sau đó.

Nếu bạn vẫn có mã thiết lập được chia sẻ giữa các chức năng thử nghiệm, bạn có thể trích xuất mã thiết lập được chia sẻ vào một hàm và sử dụng sync.Oncenếu điều quan trọng là nó được thực thi chính xác một lần (hoặc như một câu trả lời khác gợi ý, hãy sử dụng init(), nhưng điều này có nhược điểm là thiết lập sẽ được thực hiện ngay cả khi các trường hợp thử nghiệm không chạy (có lẽ vì bạn đã giới hạn các trường hợp thử nghiệm bằng cách sử dụng go test -run <regexp>.)

Tôi sẽ nói nếu bạn nghĩ rằng bạn cần thiết lập được chia sẻ giữa các bài kiểm tra khác nhau được thực thi chính xác khi bạn nên suy nghĩ nếu bạn thực sự cần nó và nếu một bài kiểm tra hướng bảng sẽ không tốt hơn.


6
Điều đó thật tuyệt khi thử nghiệm những thứ tầm thường như trình phân tích cú pháp cờ hoặc thuật toán khuấy động trên các số. Nhưng nó không thực sự hữu ích khi cố gắng kiểm tra các phần chức năng đa dạng mà tất cả đều yêu cầu mã bảng soạn sẵn tương tự. Tôi cho rằng tôi có thể xác định các hàm kiểm tra của mình trong một mảng và lặp lại các hàm đó, nhưng sau đó nó không thực sự hướng theo bảng quá nhiều như một vòng lặp đơn giản mà chỉ thực sự nên được xây dựng vào chính khung kiểm tra (dưới dạng một bộ kiểm thử thích hợp với các chức năng thiết lập / teardown)
iamtheddrman

9

Khung kiểm tra Go không có bất kỳ thứ gì tương đương với thuộc tính SetUp của NUnit (đánh dấu một hàm được gọi trước mỗi bài kiểm tra trong bộ). Tuy nhiên, có một số tùy chọn:

  1. Đơn giản chỉ cần gọi SetUphàm của bạn từ mỗi bài kiểm tra khi nó cần thiết.

  2. Sử dụng phần mở rộng cho khung thử nghiệm của Go để triển khai các mô hình và khái niệm xUnit. Ba lựa chọn mạnh mẽ xuất hiện trong tâm trí:

Mỗi thư viện này khuyến khích bạn tổ chức các bài kiểm tra của mình thành các bộ / đồ đạc tương tự như các khuôn khổ xUnit khác và sẽ gọi các phương thức thiết lập trên loại bộ / đồ đạc trước mỗi Test*phương thức.


0

Shameless plug, tôi đã tạo https://github.com/houqp/gtest để giúp giải quyết chính xác vấn đề này.

Đây là một ví dụ nhanh:

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

Bạn có thể tạo bất kỳ nhóm thử nghiệm nào bạn muốn trong một gói với mỗi nhóm trong số họ bằng cách sử dụng một tập hợp các quy trình thiết lập / chia nhỏ khác nhau.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.