Go: Go Testing
تأتي المكتبة القياسية للغة Go مزودة بإطار عمل اختبار مدمج — ولا حاجة إلى مكتبات اختبار أو مكتبات تأكيد من جهات خارجية؛ يمكنك كتابة اختبارات عالية الجودة باستخدام حزمة
testingالأساسية فقط.
تُعد سلسلة أدوات الاختبار المدمجة في لغة Go فريدة من نوعها في هذا المجال: حيث تقوم go test بالكشف التلقائي عن وظائف الاختبار، ويُعد الاختبار القائم على الجداول سمة مميزة لمجتمع Go، كما تتوفر اختبارات الأداء وتغطية الكود بشكل جاهز للاستخدام فورًا. في هذا الدرس، ستتقن جميع الجوانب الأساسية لاختبار لغة Go.
1. ستتعلم
testing.T: أساسيات الاختبار الوحدوي- الاختبارات القائمة على الجداول
- الاختبار الفرعي (
t.Run) testing.Bاختبار الأداءTestMain: اختبار نقطة الدخول والإعداد/التفكيك- التغطية
go test -cover httptestاختبار HTTP- دراسة حالة شاملة: مجموعة اختبارات وحدة الدفع
2. قصة حقيقية لمهندس إعادة هيكلة
(1) المشكلة: أدى تغيير دالة واحدة إلى تعطل ثلاث وحدات في آن واحد
أليس هي مهندسة «باك إند» في فريق المدفوعات. وقد طُلب منها إعادة هيكلة منطق حساب الضرائب في وحدة المدفوعات:
"لقد قمت للتو بتغيير اسم أحد الحقول، معتقدًا أن الأمر سيكون على ما يرام. لكن بعد الإصدار، تبين أن 30% من الطلبات تحتوي على حسابات ضريبية خاطئة — وقال مدير المشروع إننا خسرنا 5,000 دولار. وبما أننا لم نختبر ذلك، لم يعرف أحد أين تم إجراء التغيير."
فتحت شفرة وحدة الدفع واكتشفت أن المشروع بأكمله لا يحتوي على أي ملفات اختبار:
// payment.go — no corresponding payment_test.go
func CalculateTax(amount float64, country string) float64 {
// No one knows if this function is correct—there are no tests
switch country {
case "US":
return amount * 0.08
case "CN":
return amount * 0.13
default:
return amount * 0.10
}
}
بعد إجراء الاختبار، لاحظت أليس المشكلة على الفور — فقد كانت قد حددت سابقًا معدل الضريبة لـ «المملكة المتحدة» بنسبة 20٪ بدلاً من معدل ضريبة القيمة المضافة الصحيح البالغ 20٪ (كان المعدل في الواقع 20٪، ولكن في الحالة الاستثنائية التي تتضمن amount=0، كان الناتج NaN).
(2) Go Solution: إطار عمل الاختبار المدمج
// payment_test.go
package main
import "testing"
// Table-driven test
func TestCalculateTax(t *testing.T) {
tests := []struct {
name string
amount float64
country string
want float64
}{
{"US standard", 100.0, "US", 8.0},
{"CN standard", 100.0, "CN", 13.0},
{"UK default", 100.0, "UK", 10.0},
{"zero amount", 0.0, "US", 0.0},
{"negative amount", -50.0, "US", -4.0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := CalculateTax(tt.amount, tt.country)
if got != tt.want {
t.Errorf("CalculateTax(%v, %s) = %v, want %v",
tt.amount, tt.country, got, tt.want)
}
})
}
}
تشغيل الاختبار:
$ go test -v
=== RUN TestCalculateTax/US_standard
=== RUN TestCalculateTax/CN_standard
=== RUN TestCalculateTax/UK_default
=== RUN TestCalculateTax/zero_amount
=== RUN TestCalculateTax/negative_amount
--- PASS: TestCalculateTax (0.00s)
--- PASS: TestCalculateTax/US_standard (0.00s)
--- PASS: TestCalculateTax/CN_standard (0.00s)
--- PASS: TestCalculateTax/UK_default (0.00s)
--- PASS: TestCalculateTax/zero_amount (0.00s)
--- PASS: TestCalculateTax/negative_amount (0.00s)
PASS
ok payment 0.123s
(3) النتائج: مع إجراء الاختبار مقابل عدم إجرائه
| البعد | بدون اختبار | الاختبار |
|---|---|---|
| استعادة الثقة | الخوف من تغيير سطر واحد حتى | تشغيل go test فور إجراء التغييرات |
| مشكلات تحديد الموقع | تقارير أخطاء المستخدم بعد الإطلاق | حالات الفشل في مرحلة التطوير |
| جودة الكود | الحدس | الاعتماد على البيانات |
| البدء | الخوف من إجراء التغييرات | بمجرد إجراء التغييرات وإجراء الاختبارات، يمكنك أن تطمئن |
| تكاليف الإرجاع | التحقق اليدوي | الأتمتة |
3. testing.T: أساسيات اختبار الوحدات
(1) قواعد الدالة الاختبارية
// Rules:
// 1. File name must end with _test.go
// 2. Function signature must be func TestXxx(t *testing.T)
// 3. Xxx must start with an uppercase letter
// math_test.go
package main
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Errorf("Add(2,3) = %d, want %d", got, want)
}
}
func TestSubtract(t *testing.T) {
got := Subtract(5, 3)
want := 2
if got != want {
t.Errorf("Subtract(5,3) = %d, want %d", got, want)
}
}
(2) الطرق الشائعة في الاختبار.T
| الطريقة | الإجراء | هل تواصل؟ |
|---|---|---|
t.Log(args...) |
طباعة سجل (يُعرض فقط عند تحديد الخيار -v) | ✅ |
t.Error(args...) |
وضع علامة «فشل» + متابعة التنفيذ | ✅ |
t.Errorf(format, args...) |
خطأ في التنسيق | ✅ |
t.Fatal(args...) |
وضع علامة «فشل» + إيقاف الاختبار الحالي | ❌ |
t.Fatalf(format, args...) |
خطأ فادح في التنسيق | ❌ |
t.Skip(args...) |
تخطي هذا الاختبار | ✅ |
▶ مثال: أربع طرق لكتابة دالة اختبار
package main
import (
"fmt"
"testing"
)
// Function under test
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, ErrDivisionByZero
}
return a / b, nil
}
var ErrDivisionByZero = fmt.Errorf("division by zero")
// Style 1: Simple assertion
func TestDivideBasic(t *testing.T) {
result, err := Divide(10, 2)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 5.0 {
t.Errorf("got %f, want %f", result, 5.0)
}
}
// Style 2: Error vs Fatal
func TestDivideByZero(t *testing.T) {
_, err := Divide(10, 0)
if err == nil {
t.Fatal("expected error, got nil")
}
if err.Error() != "division by zero" {
t.Errorf("wrong error message: %v", err)
}
}
// Style 3: Table-driven test
func TestDivideTable(t *testing.T) {
tests := []struct {
name string
a, b float64
want float64
wantErr bool
}{
{"10/2", 10, 2, 5, false},
{"0/5", 0, 5, 0, false},
{"-6/3", -6, 3, -2, false},
{"1/0", 1, 0, 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Divide(tt.a, tt.b)
if tt.wantErr {
if err == nil {
t.Error("expected error")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != tt.want {
t.Errorf("got %f, want %f", got, tt.want)
}
})
}
}
4. الاختبار القائم على الجداول (أسلوب Go المميز)
(1) القالب القياسي
package main
import "testing"
func TestMax(t *testing.T) {
// 1. Define test table
tests := []struct {
name string // Test name (for subtests)
a, b int // Input
want int // Expected output
}{
{name: "first larger", a: 10, b: 3, want: 10},
{name: "second larger", a: 3, b: 10, want: 10},
{name: "equal", a: 5, b: 5, want: 5},
{name: "negative", a: -3, b: -10, want: -3},
{name: "zero", a: 0, b: 5, want: 5},
}
// 2. Iterate through table
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 3. Execute + assert
got := Max(tt.a, tt.b)
if got != tt.want {
t.Errorf("Max(%d, %d) = %d, want %d",
tt.a, tt.b, got, tt.want)
}
})
}
}
func Max(a, b int) int {
if a > b {
return a
}
return b
}
▶ مثال: معالجة قائمة + إدخال معقد
package main
import (
"testing"
)
// Function under test: validate password strength
type PasswordStrength int
const (
Weak PasswordStrength = iota
Medium
Strong
)
func CheckPassword(pwd string) PasswordStrength {
if len(pwd) < 6 {
return Weak
}
if len(pwd) >= 12 {
return Strong
}
return Medium
}
func TestCheckPassword(t *testing.T) {
tests := []struct {
name string
pwd string
want PasswordStrength
}{
{"short", "abc", Weak},
{"medium 6", "abcdef", Medium},
{"medium 8", "abcdefgh", Medium},
{"strong 12", "abcdefghijkl", Strong},
{"empty", "", Weak},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := CheckPassword(tt.pwd)
if got != tt.want {
t.Errorf("CheckPassword(%q) = %v, want %v",
tt.pwd, got, tt.want)
}
})
}
}
5. اختبار الأداء testing.B
(1) أساسيات المعايير المرجعية
package main
import (
"testing"
)
// Benchmark function: func BenchmarkXxx(b *testing.B)
func BenchmarkAdd(b *testing.B) {
a, c := 100, 200
for i := 0; i < b.N; i++ {
Add(a, c)
}
}
$ go test -bench=.
goos: darwin
goarch: amd64
pkg: example
BenchmarkAdd-8 1000000000 0.25 ns/op
PASS
ok example 0.3s
▶ مثال: مقارنة أداء طريقتين لتسلسل السلاسل
// concat_test.go
package main
import (
"strings"
"testing"
)
func ConcatPlus(n int) string {
s := ""
for i := 0; i < n; i++ {
s += "a"
}
return s
}
func ConcatBuilder(n int) string {
var sb strings.Builder
sb.Grow(n)
for i := 0; i < n; i++ {
sb.WriteByte('a')
}
return sb.String()
}
func BenchmarkConcatPlus(b *testing.B) {
for i := 0; i < b.N; i++ {
ConcatPlus(1000)
}
}
func BenchmarkConcatBuilder(b *testing.B) {
for i := 0; i < b.N; i++ {
ConcatBuilder(1000)
}
}
$ go test -bench=. -benchmem
BenchmarkConcatPlus-8 13134 91238 ns/op 530296 allocs/op
BenchmarkConcatBuilder-8 283321 4221 ns/op 56 allocs/op
+ 1,000 مرة أبطأ بـ 20 مرة من استخدام «Builder»، وتستهلك ذاكرة أكبر بـ 10,000 مرة — يتيح لك الخيار -benchmem ملاحظة الفرق في تخصيص الذاكرة.
(3) تفسير نتائج المقارنة المعيارية
| عنصر الإخراج | المعنى |
|---|---|
BenchmarkConcatBuilder-8 |
اسم الاختبار-8 (8 وحدات معالجة مركزية) |
283321 |
b.N = 283321 تكرارًا |
4221 ns/op |
4221 نانوثانية لكل عملية |
56 allocs/op |
56 عملية تخصيص للذاكرة لكل عملية |
6. TestMain: نقطة دخول الاختبار
(1) TestMain: الإعداد / الإنهاء
// main_test.go
package main
import (
"fmt"
"os"
"testing"
)
func TestMain(m *testing.M) {
// Setup (runs once for the entire package)
fmt.Println("=== Setup: Initializing database connection ===")
// Run all tests
code := m.Run()
// Teardown
fmt.Println("=== Teardown: Closing database connection ===")
os.Exit(code)
}
func TestSomething(t *testing.T) {
t.Log("Test A")
}
func TestAnother(t *testing.T) {
t.Log("Test B")
}
$ go test -v
=== Setup: Initializing database connection ===
=== RUN TestSomething
main_test.go:16: Test A
--- PASS: TestSomething (0.00s)
=== RUN TestAnother
main_test.go:20: Test B
--- PASS: TestAnother (0.00s)
=== Teardown: Closing database connection ===
PASS
ok example 0.1s
▶ مثال: الدالة المساعدة testing.Helper
package main
import "testing"
func Add(a, b int) int { return a + b }
func assertEqual(t testing.TB, got, want interface{}) {
t.Helper()
if got != want {
t.Errorf("got %v, want %v", got, want)
}
}
func TestWithHelper(t *testing.T) {
assertEqual(t, Add(2, 3), 5)
assertEqual(t, Add(0, 0), 0)
assertEqual(t, Add(-1, 1), 0)
}
t.Helper() أن يتم تتبع رسائل الخطأ إلى رقم السطر الخاص بالمتصل، وليس داخل الدالة المساعدة نفسها. وتُعد هذه إحدى أفضل الممارسات الأساسية عند كتابة دوال المساعدة الخاصة بالاختبار.
7. التغطية
(1) أساس التغطية
// math.go
package main
func IsEven(n int) bool {
return n%2 == 0
}
func IsPositive(n int) bool {
return n > 0
}
// math_test.go
package main
import "testing"
func TestIsEven(t *testing.T) {
tests := []struct {
n int
want bool
}{
{2, true},
{3, false},
}
for _, tt := range tests {
if got := IsEven(tt.n); got != tt.want {
t.Errorf("IsEven(%d) = %v", tt.n, got)
}
}
}
$ go test -coverprofile=coverage.out
ok example 0.1s coverage: 50.0% of statements
$ go tool cover -html=coverage.out # Open coverage report in browser
(2) go test -cover: الأوامر الشائعة
| الأمر | الوظيفة |
|---|---|
go test -cover |
التغطية المعروضة على جهاز العرض |
go test -coverprofile=c.out |
ملف ملف تعريف تغطية الإخراج |
go tool cover -html=c.out |
عرض التقرير المرئي في المتصفح |
go test -covermode=count |
يسجل عدد مرات تنفيذ كل سطر |
8. اختبار HTTP باستخدام httptest
(1) httptest.Server + httptest.ResponseRecorder
package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
// Handler under test
type UserHandler struct{}
func (h UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
resp := map[string]string{"status": "ok", "message": "hello"}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
// Test 1: httptest.ResponseRecorder (testing the handler itself)
func TestUserHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/", nil)
rec := httptest.NewRecorder()
handler := UserHandler{}
handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("got status %d, want %d", rec.Code, http.StatusOK)
}
var resp map[string]string
json.Unmarshal(rec.Body.Bytes(), &resp)
if resp["status"] != "ok" {
t.Errorf("got status %q, want %q", resp["status"], "ok")
}
}
// Test 2: httptest.Server (testing a complete HTTP service)
func TestUserHandlerWithServer(t *testing.T) {
server := httptest.NewServer(UserHandler{})
defer server.Close()
resp, err := http.Get(server.URL)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("got %d, want %d", resp.StatusCode, http.StatusOK)
}
}
▶ مثال: httptest + الاختبار القائم على الجداول
package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
// Handler under test
func greetingHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "World"
}
resp := map[string]string{"message": "Hello, " + name + "!"}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func TestGreetingHandler(t *testing.T) {
tests := []struct {
name string
query string
want string
status int
}{
{"with name", "name=Alice", "Hello, Alice!", 200},
{"empty name", "", "Hello, World!", 200},
{"with special chars", "name=Go+Lang", "Hello, Go Lang!", 200},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("GET", "/?"+tt.query, nil)
rec := httptest.NewRecorder()
greetingHandler(rec, req)
if rec.Code != tt.status {
t.Errorf("status = %d, want %d", rec.Code, tt.status)
}
var resp map[string]string
json.Unmarshal(rec.Body.Bytes(), &resp)
if resp["message"] != tt.want {
t.Errorf("message = %q, want %q", resp["message"], tt.want)
}
})
}
}
(3) وضعان لبرنامج httptest
| الوضع | httptest.NewRecorder | httptest.NewServer |
|---|---|---|
| موضوع الاختبار | معالج واحد | خدمة HTTP كاملة |
| التكاليف العامة للشركة الناشئة | لا شيء | نعم (تستمع على منفذ عشوائي) |
| حالات الاستخدام | الاختبار الفردي | اختبار التكامل |
| هل يمكن اختبار البرمجيات الوسيطة؟ | ✅ تم إنشاؤها يدويًّا | ✅ تلقائيًّا عبر مسار العمل بأكمله |
9. مثال كامل: إعادة هيكلة مجموعة اختبارات وحدة الدفع
// payment_test.go
package main
import (
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
// ---------- Code under test ----------
type PaymentRequest struct {
UserID string `json:"user_id"`
Amount float64 `json:"amount"`
Country string `json:"country"`
}
type PaymentResponse struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
Tax float64 `json:"tax,omitempty"`
Total float64 `json:"total,omitempty"`
}
func CalculateTax(amount float64, country string) (float64, error) {
if amount < 0 {
return 0, errors.New("negative amount")
}
switch country {
case "US":
return amount * 0.08, nil
case "CN":
return amount * 0.13, nil
case "DE":
return amount * 0.19, nil
default:
return amount * 0.10, nil
}
}
func paymentHandler(w http.ResponseWriter, r *http.Request) {
var req PaymentRequest
json.NewDecoder(r.Body).Decode(&req)
tax, err := CalculateTax(req.Amount, req.Country)
if err != nil {
json.NewEncoder(w).Encode(PaymentResponse{
Success: false,
Message: err.Error(),
})
return
}
json.NewEncoder(w).Encode(PaymentResponse{
Success: true,
Tax: tax,
Total: req.Amount + tax,
})
}
// ---------- Test code ----------
func jsonBody(s string) *strings.Reader {
return strings.NewReader(s)
}
// 1. Unit test: CalculateTax table-driven
func TestCalculateTax(t *testing.T) {
tests := []struct {
name string
amount float64
country string
want float64
wantErr bool
}{
{"US $100", 100, "US", 8.0, false},
{"CN $100", 100, "CN", 13.0, false},
{"DE $100", 100, "DE", 19.0, false},
{"UK default", 100, "UK", 10.0, false},
{"zero amount", 0, "US", 0, false},
{"negative amount", -100, "US", 0, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CalculateTax(tt.amount, tt.country)
if tt.wantErr {
if err == nil {
t.Error("expected error")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != tt.want {
t.Errorf("CalculateTax(%v, %s) = %v, want %v",
tt.amount, tt.country, got, tt.want)
}
})
}
}
// 2. Benchmark: different ways to calculate tax
func BenchmarkCalculateTax(b *testing.B) {
for i := 0; i < b.N; i++ {
CalculateTax(100.0, "US")
}
}
// 3. HTTP test: paymentHandler
func TestPaymentHandler(t *testing.T) {
tests := []struct {
name string
body string
wantStatus int
wantSuccess bool
}{
{"US payment", `{"user_id":"u1","amount":100,"country":"US"}`, 200, true},
{"CN payment", `{"user_id":"u2","amount":200,"country":"CN"}`, 200, true},
{"negative amount", `{"user_id":"u3","amount":-50,"country":"US"}`, 200, false},
{"invalid JSON", `not json`, 200, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest("POST", "/pay",
jsonBody(tt.body))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
paymentHandler(rec, req)
if rec.Code != tt.wantStatus {
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
}
var resp PaymentResponse
json.Unmarshal(rec.Body.Bytes(), &resp)
if resp.Success != tt.wantSuccess {
t.Errorf("success = %v, want %v", resp.Success, tt.wantSuccess)
}
})
}
}
sequenceDiagram
participant Dev
participant Terminal as go test
participant Package as Package Under Test
participant Coverage as coverage.out
Dev->>Terminal: go test -v -cover
Terminal->>Package: 1. Find *_test.go
Terminal->>Package: 2. Call TestMain(m)
Package->>Package: 3. Setup
Package->>Package: 4. Execute all TestXxx
Package->>Package: 5. Execute BenchmarkXxx
Package->>Package: 6. Teardown
Package-->>Terminal: PASS / FAIL
Terminal-->>Coverage: Coverage data
Terminal-->>Dev: Result summary
_test.go؛ وإلا فلن تعمل go test. ويجب أن تكون توقيعات الدالة بالضبط func TestXxx(t *testing.T) — حيث تكون المعلمة *testing.T، وليست *testing.TT أو testing.T.
❓ أسئلة شائعة
[]struct كجدول الاختبار، بحيث تحتوي كل حالة على name + المدخلات + الناتج المتوقع. قم بالتكرار عبر الجدول واستخدم t.Run(tt.name, ...) لتنفيذ الاختبارات الفرعية. يعتبر مجتمع Go أن هذه هي الطريقة القياسية لكتابة الاختبارات.go test -bench=. يُشغّل جميع اختبارات الأداء؛ go test -bench=FuncName يُشغّل وظيفة محددة؛ -benchmem يعرض معلومات تخصيص الذاكرة. يتم تحديد b.N تلقائيًا بواسطة الإطار.go test -cover النسبة المئوية؛ وتقوم go test -coverprofile=c.out بإخراج ملف؛ بينما توفر go tool cover -html=c.out عرضًا مرئيًا عبر المتصفح. المعيار هو 70%+، ويُوصى بنسبة 90%+ للمنطق الأساسي.httptest.NewRecorder() لاختبار المعالج مباشرةً (اختبار الوحدة)، وhttptest.NewServer(handler) لتشغيل خادم HTTP حقيقي لأغراض الاختبار (اختبار التكامل). نوصي باستخدام أداة التسجيل (Recorder) أولاً.m.Run()، ويتم تنفيذ التفكيك بعده. ملاحظة: يتم تخزين المخرجات مؤقتًا، لذا يجب عليك استخدام os.Exit(code) للخروج.go test في ذاكرة التخزين المؤقت؟go test بتخزين نتائج الاختبار (بناءً على الكود والبيئة) في ذاكرة التخزين المؤقت. وإذا لم تحدث أي تغييرات عند إعادة تشغيله، فسيعرض (cached). استخدم go test -count=1 لإجباره على إعادة التشغيل.t.Skip("reason") يتخطى الاختبار الحالي؛ testing.Short() يعمل مع go test -short لتخطي الاختبارات التي تستغرق وقتًا طويلاً؛ t.Skipf(format, args...) لتخطي الاختبارات بتنسيق معين.📖 ملخص
- يتضمن لغة Go حزمة
testingمدمجة، لذا لا توجد حاجة إلى إطار عمل اختبار تابع لجهة خارجية - يُعد الاختبار القائم على الجداول سمة مميزة لمجتمع لغة Go:
[]struct+t.Run - يقدم الاختبار الفرعي
t.Runتسلسلاً هرميًا واضحًا لنتائج الاختبار testing.Bاختبار الأداء:b.Nيقوم بالضبط التلقائي،-benchmemيتحقق من الذاكرةTestMain(m)يوفر عمليات الإعداد والإزالة على مستوى الحزمةgo test -coverيقيس مدى تغطية الخطhttptest.NewRecorderوhttptest.NewServerلاختبار HTTP- ملف الاختبار المسمى
_test.go، توقيع الدالةfunc TestXxx(t *testing.T)
📝 تمارين
-
المسألة الأساسية (الصعوبة ⭐): اكتب اختبارًا قائمًا على الجداول لـ
max(nums ...int) intمن الدرس 4 (الدوال)، بحيث يغطي الحالات الخمس التالية: الأعداد الموجبة، والأعداد السالبة، ومزيج من الأعداد الموجبة والسالبة، وعنصر واحد، والوسائط الفارغة. -
تمرين متقدم (درجة الصعوبة ⭐⭐): اكتب اختبارات لـ
Exporterمن الدرس 10 (إدخال/إخراج الملفات و JSON): استخدمos.CreateTempلإنشاء ملف مؤقت لاختبار عملية التصدير؛ استخدمjson.Unmarshalللتحقق من صحة المحتوى؛ استخدمt.Cleanupلمسح الملف المؤقت. -
التحدي (الصعوبة: ⭐⭐⭐): اكتب مجموعة اختبارات كاملة لنظام بوابة الدفع المتعددة من الدرس 7: (1) الاختبار القائم على الجداول لـ
MockPaymentGateway(يعطي نتائج ثابتة)؛ (2) قم بإجراء مقارنة أداء لسرعة تطبيقات Stripe و PayPal؛ (3) استخدمhttptestلاختبار تنسيق استجابة JSON لمعالج الدفع؛ (4) تغطية الكود ≥ 90%.