نكتب تطبيقًا للتعلم في Go و Javascript لتقييم عائدات الأسهم الحقيقية. الجزء 2 - اختبار الخلفية

في الجزء الأول من المقالة ، كتبنا خادم ويب صغيرًا ، والذي يمثل الواجهة الخلفية لنظام المعلومات الخاص بنا. لم يكن هذا الجزء مثيرًا للاهتمام ، على الرغم من أنه أظهر استخدام الواجهة وواحدة من طرق العمل مع goroutines. كل من هذا وآخر يمكن أن تكون مثيرة للاهتمام لبدء المطورين.

الجزء الثاني هو أكثر إثارة للاهتمام ومفيدة ، لأنه في ذلك سوف نكتب اختبارات وحدة لكل من الخادم نفسه وحزمة المكتبة التي تنفذ مستودع البيانات. لنبدأ.


الصورة من هنا

لذلك ، اسمحوا لي أن أذكرك بأن تطبيقنا يتكون من وحدة قابلة للتنفيذ (خادم الويب ، واجهة برمجة التطبيقات) ، وحدة التخزين (هياكل بيانات الكيان ، واجهة التعاقد لموفري التخزين) ووحدات مزود التخزين (في مثالنا ، هناك وحدة واحدة فقط تنفذ واجهة التخزين البيانات في الذاكرة).

سنختبر الوحدة القابلة للتنفيذ وتنفيذ التخزين. وحدة العقد لا تحتوي على كود يمكن اختباره. لا يوجد سوى نوع الإعلانات.
للاختبار ، سوف نستخدم فقط إمكانيات المكتبة القياسية - حزم الاختبار و httptest. في رأيي ، فهي كافية ، على الرغم من وجود العديد من أطر اختبار مختلفة. انظر إليهم ، ربما ستحبهم. من وجهة نظري ، لا تحتاج البرامج الموجودة بالفعل على تلك الأطر (من أنواع مختلفة) الموجودة حاليًا. هذه ليست جافا سكريبت بالنسبة لك ، والتي سيتم مناقشتها في الجزء الثالث من المقال.

أولاً ، بضع كلمات حول منهجية الاختبار التي أستخدمها لبرامج Go.

أولاً ، يجب أن أقول أنني أحب حقًا Go فقط لأنها لا تدفع المبرمج إلى إطار عمل جامد. على الرغم من أن بعض المطورين ، في الإنصاف ، يحبون دفع أنفسهم إلى الإطار الذي تم إحضاره من PL السابق. قل ، وهو نفس روب بايك ، إنه لا يرى مشكلة في نسخ الكود ، إذا كان ذلك أسهل. نسخ لصق هذا حتى في المكتبة القياسية. بدلاً من استيراد الحزمة ، قام أحد مؤلفي اللغة بنسخ نص دالة واحدة (التحقق من Unicode). في هذا الاختبار ، يتم استيراد حزمة Unicode ، لذلك كل شيء على ما يرام.

بالمناسبة ، في هذا المعنى (بمعنى المرونة اللغوية) ، يمكن استخدام تقنية مثيرة للاهتمام عند كتابة الاختبارات. خلاصة القول هي: نعلم أن عقود الواجهة في Go يتم تنفيذها ضمنيًا. وهذا يعني أنه يمكننا إعلان نوع ما ، وكتابة طرق لذلك ، وتنفيذ نوع من العقود. ربما حتى دون معرفة ذلك. هذا معروف ومفهوم. ومع ذلك ، هذا يعمل أيضا في الاتجاه المعاكس. إذا لم يكتب مؤلف بعض الوحدات النمطية واجهة من شأنها أن تساعدنا في إجراء روتين لاختبار الحزمة الخاصة بنا ، فيمكننا حينئذٍ إعلان الواجهة في اختبارنا ، والتي سيتم تنفيذها في حزمة جهة خارجية. فكرة مثمرة ، رغم أنها ليست مفيدة في تطبيق التدريب الخاص بنا.

ثانياً ، بضع كلمات عن وقت كتابة الاختبارات. كما يعلم الجميع ، هناك آراء مختلفة حول متى تكتب اختبارات الوحدة. الأفكار الرئيسية هي كما يلي:

  • نكتب الاختبارات قبل كتابة الكود (TDD). وبالتالي ، نحن نفهم المهمة بشكل أفضل ونضع معايير الجودة.
  • نكتب اختبارات أثناء كتابة التعليمات البرمجية ، أو حتى بعد ذلك بقليل (سننظر في هذا النموذج الأولي التدريجي).
  • سنكتب الاختبارات في وقت لاحق ، إذا كان هناك وقت. وهذه ليست مزحة. في بعض الأحيان تكون هذه الظروف جسديا لا يوجد وقت.

لا أعتقد أن هناك الرأي الصحيح الوحيد حول هذا الموضوع. سوف أشاركني وأطلب من القراء التعليق في التعليقات. رأيي هو هذا:

  • تطوير حزم قائمة بذاتها على TDD ، فإنه يبسط الأمر حقًا ، لا سيما عند بدء تشغيل تطبيق التحقق من عملية كثيفة الاستخدام للموارد. على سبيل المثال ، قمت مؤخرًا بتطوير نظام مراقبة مركبة GPS / GLONASS. لا يمكن تطوير حزم برامج التشغيل الخاصة بالبروتوكولات إلا من خلال الاختبارات ، لأن تشغيل التطبيق والتحقق منه يدويًا يتطلب انتظار البيانات من أجهزة التتبع ، وهو أمر غير مريح للغاية. للاختبارات ، أخذت عينات من حزم البيانات ، وقمت بتسجيلها في اختبارات الجدول ، ولم أقم بتشغيل الخادم حتى تكون برامج التشغيل جاهزة.
  • إذا كانت بنية الشفرة غير واضحة ، فأنا أولاً أحاول عمل نموذج أولي بسيط للعمل. ثم أكتب الاختبارات ، أو حتى أول تلميع الرمز قليلاً وبعد ذلك فقط الاختبارات.
  • بالنسبة للوحدات القابلة للتنفيذ ، أكتب أولاً نموذجًا أوليًا. الاختبارات في وقت لاحق. لا أختبر الشفرة القابلة للتنفيذ الواضحة على الإطلاق (يمكنك كتابة تشغيل خادم http من الرئيسي إلى وظيفة منفصلة والاتصال به في الاختبار ، ولكن لماذا اختبار المكتبة القياسية؟)

بناءً على ذلك ، في تطبيق التدريب الخاص بنا ، تمت كتابة مزود تخزين ذاكرة الوصول العشوائي من خلال الاختبارات. تم إنشاء الخادم القابل للتنفيذ من خلال نموذج أولي.

لنبدأ باختبارات تطبيق المستودع.

في المستودع ، لدينا طريقة المصنع New () ، والتي تُرجع المؤشر إلى مثيل لنوع التخزين. هناك أيضًا طرق للحصول على أسعار الأوراق المالية () ، وإضافة الورق إلى قائمة Add () ، وتهيئة التخزين بالبيانات من خادم Mosbirzh InitData ().

اختبار المنشئ (تُستخدم مصطلحات OOP بحرية ، وبشكل غير رسمي. بما يتوافق تمامًا مع موضع OOP في Go).

//    func TestNew(t *testing.T) { //   - memoryStorage := New() //     var s *Storage //         .   if reflect.TypeOf(memoryStorage) != reflect.TypeOf(s) { t.Errorf(" :  %v,   %v", reflect.TypeOf(memoryStorage), reflect.TypeOf(s)) } //     t.Logf("\n%+v\n\n", memoryStorage) } 

في هذا الاختبار ، دون الحاجة الخاصة ، تم توضيح الطريقة الوحيدة في Go للتحقق من نوع متغير هو الانعكاس (reflect.TypeOf (memoryStorage)). لا ينصح الاستخدام المفرط لهذه الوحدة. التحديات ثقيلة ، ولا تستحق ذلك على الإطلاق. من ناحية أخرى ، ماذا للتحقق في هذا الاختبار إلى جانب عدم وجود خطأ؟

بعد ذلك ، نقوم باختبار استلام علامات الاقتباس وإضافة الورق. تكرر هذه الاختبارات بعضها البعض جزئيًا ، لكن هذا ليس بالأمر الحاسم (في اختبار إضافة الورق ، تسمى طريقة الحصول على علامات اقتباس للتحقق). بشكل عام ، أكتب أحيانًا اختبارًا واحدًا لجميع عمليات CRUD لكيان معين. هذا هو ، في الاختبار الذي أقوم بإنشائه كيان ، قراءته ، تغييره ، قراءته مرة أخرى ، حذفه ، قراءته مرة أخرى. ليست أنيقة جدا ، ولكن العيوب واضحة ليست واضحة.

اختبار الاقتباس.

 //    func TestSecurities(t *testing.T) { //     var s *Storage //    ss, err := s.Securities() if err != nil { t.Error(err) } //     t.Logf("\n%+v\n\n", ss) } } 

كل شيء واضح جدا هنا.

الآن اختبار لإضافة ورقة. في هذا الاختبار ، للأغراض التعليمية (دون الحاجة الحقيقية) ، سوف نستخدم تقنية اختبار الجدول مريحة للغاية. جوهرها على النحو التالي: نقوم بإنشاء مجموعة من الهياكل غير المسماة ، كل منها يحتوي على بيانات الإدخال للاختبار والنتيجة المتوقعة. في حالتنا ، نرسل ضمانًا لإضافته ، والنتيجة هي عدد الأوراق المالية في الخزانة (طول الصفيف). بعد ذلك ، نقوم بإجراء اختبار لكل عنصر من عناصر مجموعة الهياكل (استدعاء طريقة الاختبار مع بيانات الإدخال للعنصر) ومقارنة النتيجة بحقل النتيجة للعنصر الحالي. اتضح شيء من هذا القبيل.

 //    func TestAdd(t *testing.T) { //     var s *Storage var security = storage.Security{ ID: "MSFT", } //   var tt = []struct { s storage.Security //   length int //   () }{ { s: security, length: 1, }, { s: security, length: 2, }, } var ss []storage.Security // tc - test case, tt - table tests for _, tc := range tt { //    err := s.Add(security) if err != nil { t.Error(err) } ss, err = s.Securities() if err != nil { t.Error(err) } if len(ss) != tc.length { t.Errorf("  :  %d,   %d", len(ss), tc.length) } } //     t.Logf("\n%+v\n\n", ss) } 

حسنًا ، اختبار لوظيفة تهيئة البيانات.

 //    func TestInitData(t *testing.T) { //     var s *Storage //    err := s.InitData() if err != nil { t.Error(err) } ss, err := s.Securities() if err != nil { t.Error(err) } if len(ss) < 1 { t.Errorf(" :  %d,   '> 1'", len(ss)) } //     t.Logf("\n%+v\n\n", ss[0]) } 

نتيجة لتنفيذ الاختبار بنجاح ، حصلنا على: تغطية تبلغ 17.595 ثانية: 86.0٪ من البيانات.

يمكنك القول إنه سيكون من الجيد أن تحصل مكتبة منفصلة على تغطية 100٪ ، ولكن على وجه التحديد ، لا يمكن على الإطلاق تنفيذ مسارات التنفيذ غير الناجحة (الأخطاء في الوظائف) ، نظرًا لميزات التنفيذ - كل شيء موجود في الذاكرة ، لسنا متصلاً بأي مكان ، ولا نعتمد على أي شيء. هناك معالجة خطأ رسميًا ، نظرًا لأن عقد الواجهة يتسبب في إرجاع الخطأ ويتطلب linter ذلك.

دعنا ننتقل إلى اختبار الحزمة القابلة للتنفيذ - خادم الويب. هنا يجب أن يقال أنه نظرًا لأن خادم الويب عبارة عن بنية فائقة المستوى في برامج Go ، فقد تم تطوير حزمة "net / http / httptest" خصيصًا لاختبار معالجات طلب http. يسمح لك بمحاكاة خادم ويب وتشغيل معالج طلب وتسجيل استجابة خادم الويب في بنية خاصة. سنستخدمها ، إنها بسيطة للغاية ، بالتأكيد ستعجبك.

في الوقت نفسه ، هناك رأي (وليس رأيي فقط) بأن مثل هذا الاختبار قد لا يكون وثيق الصلة بنظام عمل حقيقي. يمكنك ، من حيث المبدأ ، بدء خادم حقيقي واستدعاء معالجات الاتصال الحقيقي في الاختبارات.

صحيح ، هناك رأي آخر (وليس فقط رأيي) بأن عزل منطق الأعمال عن أنظمة معالجة البيانات الحقيقية أمر جيد.

وبهذا المعنى ، يمكننا القول أننا نكتب اختبارات للوحدة ، وليس لاختبارات التكامل التي تتضمن حزم وخدمات أخرى. رغم أنني هنا أيضًا أرى أن بعض المرونة في Go تتيح لك عدم التركيز على المصطلحات وكتابة تلك الاختبارات الأكثر ملاءمة لمهامك. اسمحوا لي أن أقدم لكم مثالًا: بالنسبة لاختبارات معالجات طلبات واجهة برمجة التطبيقات ، قمت بنسخ نسخة مبسطة من قاعدة البيانات على خادم حقيقي على الشبكة ، وتمت التهيئة باستخدام مجموعة بيانات صغيرة وقمت بإجراء اختبارات على بيانات حقيقية. ولكن هذا النهج هو الظرفية للغاية.

العودة إلى اختبارات خادم الويب لدينا. من أجل كتابة اختبارات مستقلة عن التخزين الفعلي ، نحتاج إلى تطوير تخزين كعب روتين. هذا ليس بالأمر الصعب على الإطلاق ، نظرًا لأننا نعمل مع المستودع من خلال الواجهة (انظر الجزء الأول). كل ما نحتاج إلى القيام به هو الإعلان عن نوع بيانات خاص بنا وتنفيذ أساليب عقد واجهة التخزين الخاصة به ، حتى مع وجود بيانات فارغة. شيء مثل هذا:

 //    -    type stub int //      var securities []storage.Security //    // ******************************* //     // InitData      func (s *stub) InitData() (err error) { //   -   var security = storage.Security{ ID: "MSFT", Name: "Microsoft", IssueDate: 1514764800, // 01/01/2018 } var quote = storage.Quote{ SecurityID: "MSFT", Num: 0, TimeStamp: 1514764800, Price: 100, } security.Quotes = append(security.Quotes, quote) securities = append(securities, security) return err } // Securities      func (s *stub) Securities() (data []storage.Security, err error) { return securities, err } //   // ***************** 

الآن يمكننا تهيئة مساحة التخزين الخاصة بنا بكعب. كيف نفعل ذلك؟ لغرض تهيئة بيئة الاختبار في إصدار بعض الإصدارات غير القديمة جدًا ، تمت إضافة وظيفة:

 func TestMain(m *testing.M) 

تتيح لك هذه الوظيفة تهيئة وتشغيل جميع الاختبارات. يبدو شيء مثل هذا:

 //    -   func TestMain(m *testing.M) { //     -    db = new(stub) //   () db.InitData() //     os.Exit(m.Run()) } 

الآن يمكننا كتابة اختبارات لمعالجات طلب API. لدينا نقطتان نهائيتان لواجهة برمجة التطبيقات ، ومعالجان ، ومن ثم اختباران. إنها متشابهة للغاية ، لذلك هنا هو الأول منها.

 //    func TestSecuritiesHandler(t *testing.T) { //     req, err := http.NewRequest(http.MethodGet, "/api/v1/securities", nil) if err != nil { t.Fatal(err) } // ResponseRecorder    rr := httptest.NewRecorder() handler := http.HandlerFunc(securitiesHandler) //       handler.ServeHTTP(rr, req) //  HTTP-  if rr.Code != http.StatusOK { t.Errorf(" :  %v,   %v", rr.Code, http.StatusOK) } //  ()     json    var ss []storage.Security err = json.NewDecoder(rr.Body).Decode(&ss) if err != nil { t.Fatal(err) } //       t.Logf("\n%+v\n\n", ss) } 

إن جوهر الاختبار هو: إنشاء طلب http ، وتحديد هيكل لتسجيل استجابة الخادم ، وبدء معالج الطلب ، وفك تشفير نص الاستجابة (json في الهيكل). حسنًا ، من أجل الوضوح ، نطبع الإجابة.

اتضح شيء مثل:
=== RUN TestSecuritiesHandler
0xc00005e3e0
- PASS: TestSecuritiesHandler (0.00s)
c: \ Users \ dtsp \ YandexDisk \ go \ src \ moex_etf \ server \ server_test.go: 96:
[{ID: MSFT الاسم: Microsoft IssueDate: 1514764800 اقتباسات: [{SecurityID: MSFT Num: 0 TimeStamp: 1514764800 Price: 100}]}]

تمر
حسنا moex_etf / الخادم 0.307s
النجاح: مرت الاختبارات.
كود جيثب .

في الجزء الأخير والأخير من المقالة ، سنقوم بتطوير تطبيق ويب لعرض الرسوم البيانية لعائدات الأسهم الحقيقية على صناديق الاستثمار المتداولة في بورصة موسكو.

Source: https://habr.com/ru/post/ar440568/


All Articles