طرح‌چه

جدیدترین ها

پربازدیدترین ها

  • X
    ###
    ###
    ###
  • X
    ###
    ###
    ###
  • X
    ###
    ###
    ###
تماس با مااوپن سورس
طرح‌چه | طرحی نو در اندازیم

یونیت تست (unit test) | گولنگ به زبان ساده

user avatar

Mahdi.Kh

۵ تیر ۱۴۰۵

یونیت تست (unit test) | گولنگ به زبان ساده
در این قسمت با یونیت تست در گولنگ آشنا می‌شویم و یاد می‌گیریم چطور برای کد خودمان تست بنویسیم.

1

معرفی | گولنگ به زبان ساده

2

آماده سازی محیط توسعه | گولنگ به زبان ساده

3

ساختار برنامه های گولنگی | گولنگ به زبان ساده

4

متغیرها | گولنگ به زبان ساده

5

ثابت ها (constants) | گولنگ به زبان ساده

6

اعداد | گولنگ به زبان ساده

7

بولین (bool) | گولنگ به زبان ساده

8

نوع داده متنی (string) | گولنگ به زبان ساده

9

نوع داده slice | گولنگ به زبان ساده

10

حلقه ها | گولنگ به زبان ساده

11

نوع داده map | گولنگ به زبان ساده

12

تابع (function) | گولنگ به زبان ساده

13

نوع داده struct | گولنگ به زبان ساده

14

اینترفیس (interface) |‌ گولنگ به زبان ساده

15

جنریک ها (generics) | گولنگ به زبان ساده

16

Defer , Panic و Recover | گولنگ به زبان ساده

17

جی‌سان (JSON) | گولنگ به زبان ساده

18

تبدیل داده ها به باینری با استفاده از Gob | گولنگ به زبان ساده

19

معرفی پکیج استاندارد RPC | گولنگ به زبان ساده

20

کنترل خطاها (error) | گولنگ به زبان ساده

21

پکیج و ماژول | گولنگ به زبان ساده

22

reflection | گولنگ به زبان ساده

23

جنریت کردن کد (go generate) | گولنگ به زبان ساده

24

گوروتین ها | گولنگ به زبان ساده

25

چنل‌ها (channels) | گولنگ به زبان ساده

26

کانتکست (context) | گولنگ به زبان ساده

27

یونیت تست (unit test) | گولنگ به زبان ساده

28

استاندارد پکیج time | گولنگ به زبان ساده

29

بررسی استاندارد پکیج sync | گولنگ به زبان ساده

30

معرفی go fix | گولنگ به زبان ساده

31

نوشتن مستندات و استفاده از Godoc | گولنگ به زبان ساده

یونیت تست, پایه‌ترین نوع تست برای کنترل عملکرد صحیح کد است. اگر یک هرم برای انواع مختلف تست داشته باشیم, یونیت تست در پایین ترین قسمت این هرم قرار میگیرد.

tests hierarchy
هرم تست ها (در پایین هرم, بیشترین میزان تست یونیت تست است و هرچه بالاتر برویم حجم تست ها کمتر خواهد شد)

همانطور که میبینیم یونیت تست در پایین ترین سطح هرم قرار دارد بدین معنی که معمولا اولین نوع تستی که شما باید بنویسید یونیت تست است. سطح مقطع هرم نمایانگر حجم تست است. پایین هرم پهن تر از بالای آن است و رفته رفته بالای هرم جمع تر و کوچکتر می‌شود. در اکثر مواقع حجم یونیت تست ها از تمام تست های دیگر بیشتر است.

در گولنگ برای نوشتن یونیت تست و اجرای آن نیاز به نصب ابزارهای جداگانه ندارید و تنها با استفاده از استاندارد پکیج های گولنگ می‌توانید تست های خود را بنویسید و سپس با خط فرمان گولنگ آن‌ها را اجرا کنید.

نوشتن اولین یونیت تست (unit test)

فرض کنید یک کد برای جمع اعداد صحیح به صورت زیر داریم و این کد درون فایل sum.go قرار دارد:

func sum(numbers ...int) int {
    var sum int
    
    for i := range numbers {
        sum += numbers[i]
    }
    
    return sum
}

در گولنگ, فایل های تست دارای نام با پسوند _test‍‍ هستند. بدین‌صورت اگر کدهای ما درون فایل sum.go قرارداشته باشند, تست های ما درون فایلی با نام sum_test.go قرار خواهند گرفت. هر تست یک تابع است که نام آن با Test شروع و دارای پارامتری از نوع *testing.T است.

برای کد بالا می‌توانیم درون فایل sum_test.go تست خود را به صورت زیر تست بنویسیم:

func TestSum(t *testing.T) {
    expected := 10
    actual := sum(1,2,3,4)
    
    if expected != actual {
        t.Errorf("expected %d but got %d", expected, actual)
    }
}

برای اجرای تمامی تست ها میتوانیم از دستور go test ./… استفاده کنیم و برای اجرای یک تست خاص می‌توانیم نام تست را با استفاده از -run مشخص کنیم به عنوان مثال در اینجا میتوان از go test -run ^TestSum$ ./... استفاده کرد که  TestSum نمایانگر نام تست مورد نظر است.

با پاس دادن ./… مشخص میکنیم که تمامی گولنگ باید دایرکتوری فعلی و تمامی ساب دایرکتوری ها را به دنبال تست بگردد و در صورت پیدا کردن, آن را اجرا کند.

با اجرا کردن go test -run ^TestSum$ ./... تست ما اجرا می‌شود و در صورتی که پاس شود نتیجه ای مانند زیر را نمایش می‌دهد:

mahdikhanzadi@Mac 01-first-test % go run -run ^TestSum$ ./...
ok    unit-test    0.585s

می‌توانیم تست های بیشتری اضافه کنیم. مثلا دو تست داشته باشیم یکی حالتی که یک عدد به sum پاس میدیم و یکی حالتی که چندین عدد به این تابع به عنوان ورودی میدیم:

func TestSum(t *testing.T) {
    var expected, actual int

    // 1. single parameter
    expected = 3
    actual = sum(3)
    if expected != actual {
        t.Errorf("expected %d but got %d", expected, actual)
    }

    // 2. multiple parameters
    expected = 10
    actual = sum(1,2,3,4)
    
    if expected != actual {
        t.Errorf("expected %d but got %d", expected, actual)
    }
}

درون*testing.T متدهای زیر را می‌توانیم برای گزارش خطا استفاده کنیم:

  • Log : پارامترهای داده شده را در خروجی مینویسد.
  • Logf : همانند Log پارامتر های داده شده را در خروجی مینویسد اما این قابلیت را به ما میدهد تا از فرمت های متنی برای نوشتن در خروجی استفاده کنیم.
  • Errorf : با فراخوانی متد Logf اطلاعات مورد نظر ما را در خروجی مینویسد و سپس متد Fail را فراخوانی میکند.
  • Error : با فراخوانی Log اطلاعات را در خروجی مینویسد و سپس متد Fail را فراخوانی میکند.
  • Fail : تابعی که تست را انجام میدهد به عنوان fail شده علامت گذاری میکند. اجرا شدن تست ها ادامه پیدا می‌کند.
  • FailNow : تابع فعلی که تست را انجام میدهد به عنوان fail شده علامت گذاری میکند و اجرا شدن باقی دستورات را به صورت آنی پایان میدهد (os.Exit را اجرا میکند پس هیچ دستوری بعد از FaileNow اجرا نمیشود حتی دستوراتی که defer شده اند اجرا نخواهند شد)
  • Fatal : ابتدا Log‍ و سپس FailNow را فراخوانی میکند. مشابه Error عمل میکند با این تفاوت که اجرا شدن تست ها ادامه پیدا نمیکند و به صورت آنی متوقف می شوند.
  • Fatalf : ابتدا Logf و سپس FailNow را فراخوانی میکند. مشابه Errorf عمل میکند با این تفاوت که اجرا شدن تست ها ادامه پیدا نمیکند و به صورت آنی متوقف می‌شوند.

تایپ T توابع کاربردی بیشتری دارد که میتونید آنها را درون استاندارد پکیج ببینید.

تست مبتنی بر جدول (table-driven test)

تیبل تست یا تست مبتنی بر جدول, یک الگوی استاندارد است که به جای نوشتن توابع تکراری برای هر حالت تست, داده های ورودی و خروجی مورد انتظار را در قالب یک آرایه یا اسلایس تعریف میکنیم و سپس به سادگی با یک حلقه روی عناصر این آرایه حرکت کرده و خروجی را بررسی می‌کنیم.

کد بالا را با روش table-driven میتوان به صورت زیر بازنویسی کرد:

func TestSum(t *testing.T) {
    tests := []struct{
        numbers []int
        expected int
    }{
       { // 1. multiple parameters
           numbers: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
           expected: 55,
       },
       { // 2. single parameter
           numbers: []int{20},
           expected: 20,
       },
    }

    for _, testCase := range tests {
        actual := sum(testCase.numbers...)
        expected := testCase.expected

        if expected != actual {
            t.Errorf("expected %d but got %d", expected, actual)
        }
    }
}

استفاده از Subtest برای جزئیات بیشتر

اگر تیبل تست های ما زیاد باشند (بیشتر از دو یا سه تست برای مثال) در صورتی که پاس نشوند (pass), پیدا کردن اینکه کدامیک از تیبل تست ها پاس نشده است سخت است. با استفاده از t.Run در این مواقع میتوانیم هر کدام از تست ها را به صورت یک زیر تست (subtest) اجرا کنیم تا در خروجی دقیقا مشخص شود کدام تست به مشکل خورده است.

کد بالا را میتوانیم به صورت زیر بازنویسی کنیم:

func TestSum(t *testing.T) {
    tests := []struct{
        testName string
        numbers []int
        expected int
    }{
       {
           testName: "multiple parameters",
           numbers: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
           expected: 55,
       },
       {
           testName: "single parameter",
           numbers: []int{20},
           expected: 20,
       },
    }

    for _, testCase := range tests {
        t.Run(testCase.testName, func(t *testing.T) {
            actual := sum(testCase.numbers...)
            expected := testCase.expected
            
            if expected != actual {
                t.Errorf("expected %d but got %d", expected, actual)
            }        
        })
    }
}

با اجرا کردن تست بالا, هر ساب تست به صورت جداگانه اجرا و نتیجه ی آن در خروجی نمایش داده می‌شود:

golang sub-testing unit-test
نتیجه‌ی اجرا شدن تست‌ها

برای اینکه نتیجه‌ی هر تست را به صورت جداگانه ببینید, باید فلگ -v را استفاده کنید.

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

اجرای تست ها به صورت همزمان (Parallel)

برای اجرای تست ها به صورت همزمان میتوان در ابتدای هر تست متد t.Parallel() را فراخوانی کرد. بدین صورت تست ها به صورت موازی اجرا خواهند شد و زمان اجرا شدن تست ها ممکن است به صورت چشم‌گیری کاهش پیدا کند.

کد بالا را میتوان به شکل زیر بازنویسی کرد:

func TestSum(t *testing.T) {
    t.Parallel()

    tests := []struct{
        testName string
        numbers []int
        expected int
    }{
       {
           testName: "multiple parameters",
           numbers: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
           expected: 55,
       },
       {
           testName: "single parameter",
           numbers: []int{20},
           expected: 20,
       },
    }

    for _, testCase := range tests {
        t.Run(testCase.testName, func(t *testing.T) {
            t.Parallel()

            actual := sum(testCase.numbers...)
            expected := testCase.expected
            
            if expected != actual {
                t.Errorf("expected %d but got %d", expected, actual)
            }        
        })
    }
}

نکته:‌ زمانی که تست ها به صورت موازی اجرا می‌شوند, ممکن است ترتیب آنها در هر بار اجرا متفاوت باشد.

کدهای این قسمت را میتوانید درون گیت هاب نیز ببینید.


قسمت قبل: کانتکست (context) | گولنگ به زبان ساده

قسمت بعد: استاندارد پکیج time | گولنگ به زبان ساده


#گولنگ#golang#go

دیدگاه ها

نیازمند احراز هویت
برای اینکه بتوانید دیدگاه خود را ثبت کنید باید ابتدا وارد حسابتان شوید
هنوز دیدگاهی برای این مقاله ثبت نشده!