بدلا من تقديم
هذا المقال مخصص لأولئك الذين جاءوا مثلي من عالم Django. لذا ، أفسدنا Django. يتعين على المرء فقط إجراء الاختبارات ، حيث سيقوم هو نفسه ، تحت غطاء محرك السيارة ، بإنشاء قاعدة بيانات اختبارية ، وتشغيل عمليات الترحيل ، وبعد التشغيل سينظف بنفسه. ملائم؟ من المؤكد. الأمر مجرد أن الأمر يستغرق وقتًا لتشغيل عمليات الترحيل - عربة ، ولكن يبدو أن هذا دفع معقول مقابل الراحة ، بالإضافة إلى وجود دائمًا--reuse-db... تزداد الصدمة الثقافية حدة عندما يأتي Junglers المخضرمين إلى لغات أخرى ، مثل Go. بمعنى ، كيف لا توجد عمليات أتمتة قبل وبعد؟ الأيدي؟ والقاعدة؟ الأيدي أيضا؟ وبعد الاختبارات؟ ماذا ، والإطار بيديك؟ حسنًا ، إذن ، يبدأ المبرمج ، الذي يتخلل الشفرة بالتنهدات والتنهدات ، في كتابة jungu in Go في مشروع منفصل. بالطبع ، كل شيء يبدو حزينًا للغاية. ومع ذلك ، في Go ، من الممكن تمامًا كتابة اختبارات وحدة سريعة وموثوقة دون استخدام خدمات الجهات الخارجية مثل قاعدة بيانات الاختبار أو ذاكرة التخزين المؤقت.
ستكون هذه قصتي.
ماذا نختبر؟
لنتخيل أننا بحاجة إلى كتابة دالة تتحقق من وجود موظف في قاعدة البيانات عن طريق رقم الهاتف.
func CheckEmployee(db *sqlx.DB, phone string) (error, bool) {
err := db.Get(`SELECT * FROM employees WHERE phone = ?`, phone)
if err != nil {
return err, false
}
return nil, true
}
حسنًا ، لقد كتبوا. كيف تختبرها؟ يمكنك بالطبع إنشاء قاعدة بيانات اختبارية قبل إجراء الاختبارات ، وإنشاء جداول فيها ، وبعد تشغيل قاعدة البيانات هذه ، قم بتعطيلها برفق.
ولكن هناك طريقة أخرى.
واجهات
, , , Get. , -, , , , , , .
. Go? , — -, , , , , . , ?
.
:
type ExampleInterface interface {
Method() error
}
, , :
type ExampleStruct struct {}
func (es ExampleStruct) Method() error {
return nil
}
, ExampleStruct ExampleInterface , , - ExampleInterface, ExampleStruct.
?
, Get, , , , , Get sqlx.Get .
Talk is cheap, let's code!
:
Get(dest interface{}, query string, args ...interface{}) error
, Get :
type BaseDBClient interface {
Get(interface{}, string, ...interface{}) error
}
:
func CheckEmployee(db BaseDBClient, phone string) (err error, exists bool) {
var employee interface{}
err = db.Get(&employee, `SELECT name FROM employees WHERE phone = ?`, phone)
if err != nil {
return err, false
}
return nil, true
}
, , , , sqlx.Get, sqlx, , BaseDBClient.
, .
, , .
, BaseDBClient:
type TestDBClient struct {}
func (tc *TestDBClient) Get(interface{}, string, ...interface{}) error {
return nil
}
, , , , , , , .
, — CheckEmployee :
func TestCheckEmployee() {
test_client := TestDBClient{}
err, exists := CheckEmployee(&test_client, "nevermind")
assert.NoError(t, err)
assert.Equal(t, exists, true)
}
, . , , :
type BaseDBClient interface {
Get(interface{}, string, ...interface{}) error
}
type TestDBClient struct {
success bool
}
func (t *TestDBClient) Get(interface{}, string, ...interface{}) error {
if t.success {
return nil
}
return fmt.Errorf("This is a test error")
}
func TestCheckEmployee(t *testing.T) {
type args struct {
db BaseDBClient
}
tests := []struct {
name string
args args
wantErr error
wantExists bool
}{
{
name: "Employee exists",
args: args{
db: &TestDBClient{success: true},
},
wantErr: nil,
wantExists: true,
}, {
name: "Employee don't exists",
args: args{
db: &TestDBClient{success: false},
},
wantErr: fmt.Errorf("This is a test error"),
wantExists: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotErr, gotExists := CheckEmployee(tt.args.db, "some phone")
if !reflect.DeepEqual(gotErr, tt.wantErr) {
t.Errorf("CheckEmployee() gotErr = %v, want %v", gotErr, tt.wantErr)
}
if gotExists != tt.wantExists {
t.Errorf("CheckEmployee() gotExists = %v, want %v", gotExists, tt.wantExists)
}
})
}
}
! , , , , , go.
, , .
بالطبع ، هذا النهج له عيوبه. على سبيل المثال ، إذا كان منطقك مرتبطًا بنوع من منطق قاعدة البيانات الداخلية ، فلن يتمكن هذا الاختبار من الكشف عن الأخطاء التي تسببها قاعدة البيانات. لكنني أعتقد أن الاختبار بمشاركة قاعدة بيانات وخدمات الجهات الخارجية لم يعد متعلقًا باختبارات الوحدة ، بل إنها اختبارات تكامل أو حتى اختبارات e2e ، وهي إلى حد ما خارج نطاق هذه المقالة.
شكرا لقراءة وكتابة الاختبارات!