Thực hành tốt nhất - Kiểm tra

Tôi những ngày đầu trong sự nghiệp lập trình của mình, tôi đã thực sự thấy được giá trị và chủ yếu nghĩ rằng nó trùng lặp với công việc. Tuy nhiên, bây giờ tôi thường nhắm đến phạm vi kiểm tra 90 - 100% cho mọi thứ tôi viết. Và tôi thường tin rằng thử nghiệm trên mọi lớp là một cách thực hành tốt (chúng tôi sẽ quay lại vấn đề này).

Trong thực tế, khi nhìn vào các cơ sở mã mà tôi có trước mặt tôi hàng ngày, những cơ sở tôi sợ nhất để thay đổi là những cơ sở có phạm vi kiểm tra ít nhất. Và điều đó cuối cùng làm giảm năng suất của tôi và sản phẩm của chúng tôi. Vì vậy, với tôi, khá rõ ràng rằng phạm vi kiểm tra cao cả bằng chất lượng cao hơn và năng suất cao hơn.

Kiểm tra trên mọi lớp

Chúng tôi sẽ đi sâu vào một ví dụ ngay lập tức. Giả sử bạn có một ứng dụng có cấu trúc như sau.

Mô hình ứng dụng

Có một số thành phần được chia sẻ, như các mô hình và trình xử lý. Sau đó, bạn có một vài cách tương tác khác nhau với ứng dụng này, ví dụ: CLI, API HTTP hoặc RPC tiết kiệm. Tôi đã tìm thấy nó là một thực hành tốt để đảm bảo bạn kiểm tra không chỉ các mô hình hoặc chỉ các trình xử lý, mà tất cả chúng. Ngay cả đối với các tính năng tương tự. Bởi vì điều đó nhưng thực sự đúng là nếu bạn đã triển khai hỗ trợ cho Tính năng X trong trình xử lý, thì nó thực sự có sẵn thông qua các giao diện HTTP và Thrift chẳng hạn.

Điều này là bạn sẽ tự tin hơn khi thực hiện các thay đổi đối với logic của mình, thậm chí đi sâu vào cốt lõi của ứng dụng.

Kiểm tra dựa trên bảng

Trong hầu hết các trường hợp khi bạn kiểm tra một phương thức mà bạn muốn kiểm tra một vài tình huống trên hàm. Thông thường với các thông số đầu vào khác nhau hoặc phản ứng giả khác nhau. Tôi muốn nhóm tất cả các thử nghiệm này thành một chức năng Test * và sau đó có một vòng lặp chạy qua tất cả các trường hợp thử nghiệm. Dưới đây là một ví dụ cơ bản:

func TestDivision (t * tests.T) {
    kiểm tra: = [] struct {
        x float64
        y nổi64
        kết quả float64
        lỗi
    } {
        {x: 1.0, y: 2.0, kết quả: 0.5, err: nil},
        {x: -1.0, y: 2.0, kết quả: -0,5, err: nil},
        {x: 1.0, y: 0.0, kết quả: 0.0, err: ErrZeroDivision},
    }
    cho _, test: = phạm vi kiểm tra {
        kết quả, err: = split (test.x, test.y)
        khẳng định.IsType (t, test.err, err)
        khẳng định.Equal (t, test.result, kết quả)
    }
}

Các thử nghiệm trên không bao gồm tất cả mọi thứ, nhưng phục vụ như một ví dụ cho cách kiểm tra các kết quả và lỗi dự kiến. Đoạn mã trên cũng sử dụng gói làm chứng tuyệt vời cho các xác nhận.

Một cải tiến, kiểm tra dựa trên bảng với các trường hợp thử nghiệm được đặt tên

Nếu bạn có nhiều thử nghiệm hoặc thường là các nhà phát triển mới không quen thuộc với cơ sở mã, có thể hữu ích để đặt tên cho các thử nghiệm của bạn. Dưới đây là một ví dụ ngắn về những gì sẽ trông như thế nào

tests: = map [chuỗi] struct {
    số int
    lỗi smsErr
    lỗi
} {
    "thành công": {0132423444, nil, nil},
    "lỗi lan truyền": {0132423444, sampleErr, sampleErr},
}

Lưu ý rằng có một sự khác biệt ở đây giữa việc có một bản đồ và một lát cắt. Bản đồ không đảm bảo trật tự, trong khi lát cắt.

Chế nhạo bằng cách nhạo báng

Các giao diện tự nhiên là các điểm tích hợp siêu tốt cho các thử nghiệm, vì việc triển khai giao diện có thể dễ dàng được thay thế bằng triển khai giả. Tuy nhiên, viết giả có thể khá tẻ nhạt và nhàm chán. Để làm cho cuộc sống dễ dàng hơn, tôi đã sử dụng sự nhạo báng để tạo ra các bản giả của mình dựa trên một giao diện nhất định.

Hãy cùng xem cách làm việc với nó. Giả sử chúng ta có giao diện sau.

loại giao diện SMS {
    Gửi (số int, chuỗi văn bản) lỗi
}

Ở đây, một triển khai giả sử dụng giao diện này:

// Messager là một cấu trúc nhắn tin xử lý các loại.
loại Messager struct {
    tin nhắn SMS
}
// SendHelloWorld gửi SMS thế giới Hello.
lỗi func (m * Messager) SendHelloWorld (số int)
    err: = m.sms.Send (số, "Xin chào, thế giới!")
    nếu lỗi! = nil {
        trở lại lỗi
    }
    trở về
}

Bây giờ chúng ta có thể sử dụng Mockery để tạo một bản giả cho giao diện SMS. Ở đây, một cái gì đó trông như thế nào (ví dụ này là sử dụng cờ -inpkg để đặt giả trong cùng gói với giao diện).

// MockSMS là loại giả được tạo tự động cho loại SMS
loại MockSMS struct {
    giả
}
// Send cung cấp một hàm giả với các trường đã cho: number, text
func (_m * MockSMS) Lỗi (số int, chuỗi văn bản) lỗi {
    ret: = _m.Called (số, văn bản)
    lỗi var r0
    nếu rf, ok: = ret.Get (0). (lỗi func (int, chuỗi)); được {
        r0 = rf (số, văn bản)
    } khác {
        r0 = ret.Error (0)
    }
    trở lại r0
}
var _ SMS = (* MockSMS) (không)

Cấu trúc SMS được kế thừa từ chứng nhận mock.Mock, cung cấp cho chúng tôi một số tùy chọn thú vị khi viết các trường hợp thử nghiệm. Vì vậy, bây giờ đã đến lúc viết bài kiểm tra của chúng tôi cho phương thức SendHelloWorld bằng cách sử dụng giả từ Mockery.

func TestSendHelloWorld (t * tests.T) {
    sampleErr: = lỗi. Mới ("một số lỗi")
    tests: = map [chuỗi] struct {
        số int
        lỗi smsErr
        lỗi
    } {
        "thành công": {0132423444, nil, nil},
        "lỗi lan truyền": {0132423444, sampleErr, sampleErr},
    }
    cho _, test: = phạm vi kiểm tra {
        sms: = & MockSMS {}
        sms.On ("Gửi", test.number, "Xin chào, thế giới!"). Trả lại (test.smsErr) .Once ()
        m: = & Messager {
            tin nhắn
        }
   
        err: = m.SendHelloWorld (test.number)
        khẳng định.Equal (t, test.err, err)
        sms.AssertExpectations (t)
    }
}

Có một vài điểm đáng nói trong mã ví dụ trên. Trong thử nghiệm, bạn sẽ nhận thấy rằng tôi khởi tạo MockSMS và sau đó sử dụng .On () Tôi có thể ra lệnh những gì sẽ xảy ra (.Return ()) khi các tham số nhất định được gửi đến giả.

Cuối cùng, tôi đã sử dụng sms.AssertExpectations để đảm bảo rằng giao diện SMS đã được gọi là số lần mong đợi. Trong trường hợp này Một lần ().

Tất cả các tập tin ở trên có thể được tìm thấy trong ý chính này.

Kiểm tra tập tin vàng

Trong một số trường hợp, tôi đã thấy hữu ích khi có thể khẳng định rằng một blob phản hồi lớn vẫn như cũ. Ví dụ, có thể là dữ liệu được trả về từ dữ liệu JSON từ API. Trong trường hợp đó, tôi đã học được từ Michell Hashimoto về việc sử dụng các tập tin vàng kết hợp với một cách thông minh là để lộ cờ dòng lệnh để kiểm tra.

Ý tưởng cơ bản là bạn có thể viết phần thân phản hồi chính xác vào một tệp (tệp vàng). Sau đó, khi chạy các bài kiểm tra, bạn thực hiện so sánh byte giữa tệp vàng và phản hồi kiểm tra.

Để làm cho nó dễ dàng hơn, tôi đã tạo ra gói goldie, xử lý cài đặt cờ dòng lệnh và ghi và so sánh tệp vàng trong suốt.

Dưới đây, một ví dụ về cách sử dụng goldie cho loại thử nghiệm này:

func TestExample (t * tests.T) {
    máy ghi âm: = omeptest.NewRecorder ()

    req, err: = http.NewRequest ("NHẬN", "/ ví dụ", nil)
    khẳng định.Nil (t, err)

    xử lý: = http.HandlerFunc (exampleHandler)
    xử lý.ServeHTTP ()

    goldie.Assert (t, "ví dụ", ghi.Body.Bytes ())
}

Khi bạn cần cập nhật tệp vàng của mình, bạn sẽ chạy như sau:

đi kiểm tra -update. / ...

Và khi bạn chỉ muốn chạy thử nghiệm, bạn đã làm điều đó như bình thường:

đi kiểm tra. / ...

Tạm biệt!

Cảm ơn đã gắn bó đến cùng! Hy vọng bạn đã tìm thấy một cái gì đó hữu ích trong bài viết.