7.1 پکیج testify

7.1 پکیج testify

یک کتابخانه مهم برای تست نویسی در زبان گو Testify هست که در زیر به توضیح اون می‌پردازیم. آدرس این کتابخانه در این لینک هست. به طور کلی زبان برنامه نویسی Go دارای یک framework تست سبک وزن است که از دستور go test و testing  package تشکیل شده است.

شما با ایجاد یک فایل با نامی که به test.goـ ختم می شود، یک test می نویسید که حاوی توابعی به نام TestXXX با signature func به صورت (t *testing.T) است. framework تست هر یک از این تابع ها را اجرا می کند. اگر تابع یک تابع شکست مانند t.Error یا t.Fail را فراخوانی کند، آزمایش ناموفق در نظر گرفته می شود. با ایجاد فایل HOME/hello/morestrings/reverse_test.go حاوی کد Go زیر، یک تست به پکیج morestrings اضافه کنید.

 1package morestrings
 2
 3import "testing"
 4
 5func TestReverseRunes(t *testing.T) {
 6    cases := []struct {
 7        in, want string
 8    }{
 9        {"Hello, world", "dlrow ,olleH"},
10        {"Hello, 世界", "界世 ,olleH"},
11        {"", ""},
12    }
13    for _, c := range cases {
14        got := ReverseRunes(c.in)
15        if got != c.want {
16            t.Errorf("ReverseRunes(%q) == %q, want %q", c.in, got, c.want)
17        }
18    }
19}

و تست را با کد زیر اجرا کنید.

1$ cd $HOME/hello/morestrings
2
3$ go test
4
5PASS
6
7ok example/user/hello/morestrings 0.165s
8
9$

همینطور golang مجموعه‌ای از پکیج‌ها با ابزارهای زیادی برای اثبات کردن اینکه کد شما همانطور که می‌خواهید عمل خواهد کرد، فراهم می‌کند.

این ابزارها عبارتند از:

Installation #

به راحتی testity را با یک خط کد نصب کنید، یا آن را با خط دیگری به روز کنید.

1go get github.com/stretchr/testify

سپس بسته های زیر را در دسترس شما قرار می دهد:

1github.com/stretchr/testify/assert
2
3github.com/stretchr/testify/require
4
5github.com/stretchr/testify/mock
6
7github.com/stretchr/testify/suite
8
9github.com/stretchr/testify/http (deprecated)

همینطور package به عنوان testify/assert رو به کد اضافه کنید.

 1package yours
 2
 3import (
 4"testing"
 5
 6"github.com/stretchr/testify/assert"
 7
 8)
 9
10func TestSomething(t *testing.T) {
11
12assert.True(t, true, "True is true!")
13
14}

7.1.1 assert package #

ابزار assert روش‌های مفیدی را ارائه می‌کند که به شما امکان می‌دهد کد تست بهتری را در Go بنویسید. به عنوان مثال:

  • دلیل شکست برنامه را عالی و خوانا را پرینت کنید
  • خوانا بودن و درک راحت کد را ساده کنید.
  • به صورت اختیاری هر assertion را با یک پیام حاشیه نویسی کنید

حالت زیر را در نظر بگیرید:

 1package yours
 2
 3  
 4
 5import (
 6
 7"testing"
 8
 9"github.com/stretchr/testify/assert"
10
11)
12
13func TestSomething(t *testing.T) {
14
15// assert equality
16
17assert.Equal(t, 123, 123, "they should be equal")
18
19  
20// assert inequality
21
22assert.NotEqual(t, 123, 456, "they should not be equal")
23
24
25// assert for nil (good for errors)
26
27assert.Nil(t, object)
28
29
30// assert for not nil (good when you expect something)
31
32if assert.NotNil(t, object) {
33
34
35// now we know that object isn't nil, we are safe to make
36
37// further assertions without causing any errors
38
39assert.Equal(t, "Something", object.Value)
40
41
42	}
43
44
45}

هر تابع assert شی testing.T را به عنوان اولین آرگومان می گیرد، به این صورت است که خطاها را از طریق قابلیت های go test می نویسد.

هر تابع assert یک bool برمی‌گرداند که نشان می‌دهد آیا assert موفقیت‌آمیز بوده است یا خیر، این برای شرایطی مفید است که می‌خواهید تحت شرایط خاصی به assertion بیشتر ادامه دهید.

اگر بارها assert می کنید، از موارد زیر استفاده کنید:

 1package yours
 2
 3  
 4
 5import (
 6
 7"testing"
 8
 9"github.com/stretchr/testify/assert"
10
11)
12
13  
14
15func TestSomething(t *testing.T) {
16
17assert := assert.New(t)
18
19  
20
21// assert equality
22
23assert.Equal(123, 123, "they should be equal")
24
25  
26
27// assert inequality
28
29assert.NotEqual(123, 456, "they should not be equal")
30
31  
32
33// assert for nil (good for errors)
34
35assert.Nil(object)
36
37  
38
39// assert for not nil (good when you expect something)
40
41if assert.NotNil(object) {
42
43  
44
45// now we know that object isn't nil, we are safe to make
46
47// further assertions without causing any errors
48
49assert.Equal("Something", object.Value)
50
51	}
52
53}

7.1.2 require package #

بسته require همان توابع سراسری را ارائه می‌کند که بسته assert داراست، اما به جای برگرداندن یک نتیجه boolean، تست فعلی را terminate می‌کند.

برای توضیح بیشتر در این مورد باید گزینه t.FailNow را برررسی کنی. FailNow عملکرد را به‌عنوان ناموفق علامت‌گذاری می‌کند و اجرای آن را با فراخوانی runtime.Goexit متوقف می‌کند (که سپس همه calls معوق را در گوروتین فعلی اجرا می‌کند). همینطور اجرای تست‌های بعد از این مورد ادامه خواهد داشت. FailNow باید از گوروتینی که تست یا تابع benchmark را اجرا می کند فراخوانی شود، نه از دیگر گوروتین های ایجاد شده در طول تست. فراخوانی FailNow دیگر برنامه‌ها را متوقف نمی‌کند.

7.1.3 mock package #

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

همیشه Mocking زمانی استفاده میشه که یک متد یا کلاس، وابستگی یا وابستگی هایی داره که توی تست‌ها ایجاد مشکل میکنه.

مثلا یک سرویس رو باید تست کنیم که داخل اون از سرویس notification_sender استفاده شده یعنی به سرویس یا کلاس notification_sender وابستگی داره، درنتیجه هربار که اون تست رو انجام میدید یه notification هم ارسال میشه که این کار درست نیست

در این صورت میایم و سرویس یا کلاس notification_sender رو Mock میکنیم که دیگه notification ارسال نکنه ولی جواب رو true رو برگردونه گه این به معنی درست کار کردن بخش مورد نظر سیستم هست.

این باعث میشه گه بتونیم عملکر صحیح همون متد رو تست کنیم به جای اینکه تست مون را درگیر و وابسته به عوامل دیگه مثل ارسال notification کنیم.

حالا بر میگردم به پیاده سازی این مکانیزم در زبان گو

در واقع Package mock سیستمی را ارائه می دهد که توسط آن می توان object ها را mock کرد و تأیید کرد که فراخوانی ها همانطور که انتظار می رود انجام می شوند.

بسته mock مکانیزمی را برای نوشتن آسان اشیاء mock فراهم می کند که می تواند در هنگام نوشتن کد آزمایشی به جای اشیاء واقعی استفاده شود.

همیشه Package mock یک شی به نام Mock را ارائه می دهد که فعالیت را در یک شی دیگر دنبال می کند. معمولاً مطابق کد زیر در یک شیء آزمایشی تعبیه می شود:

1type MyTestObject struct {
2// add a Mock object instance 
3mock.Mock
4// other fields go here as normal 
5}

هنگام پیاده سازی متدهای یک interface، توابع خود را برای فراخوانی متد Mock.Called(args…) مرتبط می کنید و مقادیر مناسب را برمی گردانید.

به عنوان مثال، برای mock کردن یک متد که نام و سن یک فرد را ذخیره می کند و سال تولد را به همراه یک خطا را برمی گرداند، می توانید این کد را بنویسید:

1func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) {
2
3args := o.Called(firstname, lastname, age)
4
5return args.Int(0), args.Error(1)
6
7}

متدهای Int، Error و Bool نمونه‌هایی از strongly typed getters هستند که موقعیت index آرگومان را می‌گیرند. با توجه به این لیست argument:

1(12, true, "Something")

شما می توانید آنها را با strongly typed مانند این بخوانید:

1args.Int(0) 
2args.Bool(1) 
3args.String(2)

برای اشیاء از type مورد نظر، از روش generic مثل Arguments.Get(index) استفاده کنید و یک type assertion ایجاد کنید:

1return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine)

این ممکن است باعث panic شود اگر شیئی که دریافت می کنید nil باشد (تعریف type assertion ناموفق خواهد بود)، در این موارد ابتدا باید nil را بررسی کنید. یک تابع تست نمونه که قطعه کدی را که به یک شی خارجی testObj متکی است test می کند، می تواند موارد مورد نظر testify و assert را طوری تنظیم کند که به نظر واقعاً چنین رفتاری در برنامه رخ داده است. به عنوان مثال کد زیر:

  1package yours
  2
  3  
  4
  5import (
  6
  7"testing"
  8
  9"github.com/stretchr/testify/mock"
 10
 11)
 12
 13  
 14
 15/*
 16
 17Test objects
 18
 19*/
 20
 21  
 22
 23// MyMockedObject is a mocked object that implements an interface
 24
 25// that describes an object that the code I am testing relies on.
 26
 27type MyMockedObject struct{
 28
 29mock.Mock
 30
 31}
 32
 33  
 34
 35// DoSomething is a method on MyMockedObject that implements some interface
 36
 37// and just records the activity, and returns what the Mock object tells it to.
 38
 39//
 40
 41// In the real object, this method would do something useful, but since this
 42
 43// is a mocked object - we're just going to stub it out.
 44
 45//
 46
 47// NOTE: This method is not being tested here, code that uses this object is.
 48
 49func (m *MyMockedObject) DoSomething(number int) (bool, error) {
 50
 51  
 52
 53args := m.Called(number)
 54
 55return args.Bool(0), args.Error(1)
 56
 57  
 58
 59}
 60
 61  
 62
 63/*
 64
 65Actual test functions
 66
 67*/
 68
 69  
 70
 71// TestSomething is an example of how to use our test object to
 72
 73// make assertions about some target code we are testing.
 74
 75func TestSomething(t *testing.T) {
 76
 77  
 78
 79// create an instance of our test object
 80
 81testObj := new(MyMockedObject)
 82
 83  
 84
 85// setup expectations
 86
 87testObj.On("DoSomething", 123).Return(true, nil)
 88
 89  
 90
 91// call the code we are testing
 92
 93targetFuncThatDoesSomethingWithObj(testObj)
 94
 95  
 96
 97// assert that the expectations were met
 98
 99testObj.AssertExpectations(t)
100
101  
102  
103
104}
105
106  
107
108// TestSomethingWithPlaceholder is a second example of how to use our test object to
109
110// make assertions about some target code we are testing.
111
112// This time using a placeholder. Placeholders might be used when the
113
114// data being passed in is normally dynamically generated and cannot be
115
116// predicted beforehand (eg. containing hashes that are time sensitive)
117
118func TestSomethingWithPlaceholder(t *testing.T) {
119
120  
121
122// create an instance of our test object
123
124testObj := new(MyMockedObject)
125
126  
127
128// setup expectations with a placeholder in the argument list
129
130testObj.On("DoSomething", mock.Anything).Return(true, nil)
131
132  
133
134// call the code we are testing
135
136targetFuncThatDoesSomethingWithObj(testObj)
137
138  
139
140// assert that the expectations were met
141
142testObj.AssertExpectations(t)
143
144  
145  
146
147}
148
149  
150
151// TestSomethingElse2 is a third example that shows how you can use
152
153// the Unset method to cleanup handlers and then add new ones.
154
155func TestSomethingElse2(t *testing.T) {
156
157  
158
159// create an instance of our test object
160
161testObj := new(MyMockedObject)
162
163  
164
165// setup expectations with a placeholder in the argument list
166
167mockCall := testObj.On("DoSomething", mock.Anything).Return(true, nil)
168
169  
170
171// call the code we are testing
172
173targetFuncThatDoesSomethingWithObj(testObj)
174
175  
176
177// assert that the expectations were met
178
179testObj.AssertExpectations(t)
180
181  
182
183// remove the handler now so we can add another one that takes precedence
184
185mockCall.Unset()
186
187  
188
189// return false now instead of true
190
191testObj.On("DoSomething", mock.Anything).Return(false, nil)
192
193  
194
195testObj.AssertExpectations(t)
196
197}

برای اطلاعات بیشتر در مورد نحوه نوشتن کد mock، اسناد API را برای mock package بررسی کنید.

می‌توانید از mockery tool برای تولید خودکار کد ساختگی در برابر یک interface نیز استفاده کنید و استفاده از mockها را بسیار سریع‌تر کنید.

7.1.4 suite package #

بسته‌ی suite قابلیت‌هایی را فراهم می‌کند که شما ممکن است از زبان‌های شی گرا متداول آنها را استفاده کنید. با استفاده از این بسته، شما می‌توانید یک مجموعه test را به عنوان یک ساختار بسازید، روش‌های setup/teardown را برای ساختار خود بسازید و روش‌های test را روی ساختار خود اجرا کنید و با استفاده از ‘go test’ به طور معمول اجرا کنید.

یک مثال از مجموعه آزمون به شرح زیر است:

 1// Basic imports
 2
 3import (
 4
 5"testing"
 6
 7"github.com/stretchr/testify/assert"
 8
 9"github.com/stretchr/testify/suite"
10
11)
12
13  
14
15// Define the suite, and absorb the built-in basic suite
16
17// functionality from testify - including a T() method which
18
19// returns the current testing context
20
21type ExampleTestSuite struct {
22
23suite.Suite
24
25VariableThatShouldStartAtFive int
26
27}
28
29  
30
31// Make sure that VariableThatShouldStartAtFive is set to five
32
33// before each test
34
35func (suite *ExampleTestSuite) SetupTest() {
36
37suite.VariableThatShouldStartAtFive = 5
38
39}
40
41  
42
43// All methods that begin with "Test" are run as tests within a
44
45// suite.
46
47func (suite *ExampleTestSuite) TestExample() {
48
49assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
50
51}
52
53  
54
55// In order for 'go test' to run this suite, we need to create
56
57// a normal test function and pass our suite to suite.Run
58
59func TestExampleTestSuite(t *testing.T) {
60
61suite.Run(t, new(ExampleTestSuite))
62
63}

رای یک مثال کامل‌تر و استفاده از تمامی قابلیت‌های فراهم شده توسط suite package، به مجموعه test مثال ما نگاه کنید.

برای کسب اطلاعات بیشتر در مورد نوشتن مجموعه‌های test، به مستندات API مربوط suite package مراجعه کنید.

شیء Suite شامل متدهای assertion است:

 1// Basic imports
 2
 3import (
 4
 5"testing"
 6
 7"github.com/stretchr/testify/suite"
 8
 9)
10
11  
12
13// Define the suite, and absorb the built-in basic suite
14
15// functionality from testify - including assertion methods.
16
17type ExampleTestSuite struct {
18
19suite.Suite
20
21VariableThatShouldStartAtFive int
22
23}
24
25  
26
27// Make sure that VariableThatShouldStartAtFive is set to five
28
29// before each test
30
31func (suite *ExampleTestSuite) SetupTest() {
32
33suite.VariableThatShouldStartAtFive = 5
34
35}
36
37  
38
39// All methods that begin with "Test" are run as tests within a
40
41// suite.
42
43func (suite *ExampleTestSuite) TestExample() {
44
45suite.Equal(suite.VariableThatShouldStartAtFive, 5)
46
47}
48
49  
50
51// In order for 'go test' to run this suite, we need to create
52
53// a normal test function and pass our suite to suite.Run
54
55func TestExampleTestSuite(t *testing.T) {
56
57suite.Run(t, new(ExampleTestSuite))
58
59}