في الطريق إلى تغطية رمز 100 ٪ مع الاختبارات في Go باستخدام sql-dumper كمثال

الصورة


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


الغرض من البرنامج


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


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


كان يجب على المتدربين تطوير هذا البرنامج لغرض التدريب واستخدامه لاحقًا في تدريبهم الإضافي. لكن الوضع اتضح أنه رفض هذه الفكرة. لكنني قررت أن أحاول كتابة مثل هذا البرنامج في وقت فراغي لغرض ممارستي للتطوير بلغة Go.


الحل غير كامل ، ولديه عدد من القيود الموضحة في README. على أي حال ، هذا ليس مشروع قتالي.


أمثلة الاستخدام وشفرة المصدر .


الصعوبات الأولى


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


كان الحل لهذا هو استخدام طريقة MapScan من github.com/jmoiron/sqlx ، والتي أنشأت شريحة واجهة بحجم يساوي عدد أعمدة العينة. كان السؤال التالي هو كيفية الحصول على نوع بيانات حقيقي من هذه الواجهات. الحل هو حالة التبديل حسب النوع . لا يبدو هذا الحل جميلًا جدًا ، لأن جميع الأنواع ستحتاج إلى الإدلاء بالسلسلة: الأعداد الصحيحة كما هي ، السلاسل التي يجب الهروب منها وإحاطتها بعلامات اقتباس ، ولكن في نفس الوقت لوصف جميع الأنواع التي قد تأتي من قاعدة البيانات. لم أجد طريقة أكثر أناقة لحل هذه المشكلة.


مع الأنواع ، تم أيضًا إظهار ميزة لغة Go - لا يمكن لمتغير سلسلة النوع أن يأخذ القيمة nil ، ولكن كل من السلسلة الفارغة و NULL يمكن أن تأتي من قاعدة البيانات. لحل هذه المشكلة ، هناك حل في حزمة database/sql - استخدم strut خاص ، الذي يقوم بتخزين القيمة والتوقيع ، سواء كان NULL أم لا.


تجميع وحساب النسبة المئوية لتغطية التعليمات البرمجية عن طريق الاختبارات


للتجميع يمكنني استخدام Travis CI ، للحصول على النسبة المئوية لتغطية الكود مع الاختبارات - المآزر. ملف .travis.yml للتجميع بسيط للغاية:


 language: go go: - 1.9 script: - go get -t -v ./... - go get golang.org/x/tools/cmd/cover - go get github.com/mattn/goveralls - go test -v -covermode=count -coverprofile=coverage.out ./... - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN 

في إعدادات Travis CI ، ما عليك سوى تحديد متغير البيئة COVERALLS_TOKEN ، الذي يجب أن تؤخذ قيمته على الموقع .


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


تغطية 100٪ من الكود تعني أن الاختبارات مكتوبة ، من بين أشياء أخرى ، تنفيذ الكود لكل فرع في if . هذا هو أكبر عمل عند كتابة الاختبارات ، وبشكل عام ، عند تطوير التطبيق.


يمكنك حساب التغطية بالاختبارات محليًا ، على سبيل المثال ، باستخدام نفس go test -v -covermode=count -coverprofile=coverage.out ./... ، ولكن يمكنك القيام بذلك بشكل أفضل في CI ، يمكنك وضع لوحة على Github.


نظرًا لأننا نتحدث عن النرد ، فإنني أجد النرد من https://goreportcard.com مفيدًا ، والذي يحلل المؤشرات التالية:


  • gofmt - تنسيق الكود بما في ذلك تبسيط الانشاءات
  • go_vet - يتحقق من البنى المشبوهة
  • gocyclo - يظهر مشاكل في التعقيد السيكلومي
  • golint - بالنسبة لي هو التحقق من توافر جميع التعليقات اللازمة
  • ترخيص - يجب أن يكون للمشروع ترخيص
  • غير فعال - يتحقق من المهام غير الفعالة
  • خطأ إملائي - يتحقق من الأخطاء المطبعية

صعوبات تغطي الكود بالاختبارات 100٪


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


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


قد يكون هناك خيار آخر لاستخدام قاعدة بيانات في الذاكرة ، على سبيل المثال ، sqlite ( sqlx.Open("sqlite3", ":memory:") ) ، ولكن هذا يعني ضمنيًا أن الرمز يجب أن يكون ضعيفًا إلى محرك قاعدة البيانات قدر الإمكان ، مما يعقد المشروع بشكل كبير ولكن لاختبار الاندماج جيد جدًا.


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


أظهرت اختبارات الكتابة أن الوظيفة التي تتصل بقاعدة البيانات الحقيقية يجب أن يتم نقلها إلى main.go ، بحيث يمكن إعادة تعريفها في اختبارات تلك التي ستعيد مثيل وهمي.


بالإضافة إلى العمل مع قاعدة البيانات ، من الضروري جعل العمل مع نظام الملفات تبعية منفصلة. سيسمح ذلك باستبدال تسجيل الملفات الحقيقية بالكتابة على الذاكرة لسهولة الاختبار وتقليل الاقتران. هذه هي الطريقة التي ظهرت بها واجهة FileWriter ، ومعها واجهة الملف التي تقوم FileWriter . لاختبار سيناريوهات الخطأ ، تم إنشاء تطبيقات مساعدة لهذه الواجهات ووضعها في ملف filewriter_test.go ، بحيث لا تقع في البناء العام ، ولكن يمكن استخدامها في الاختبارات.


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


في اختبار Go ، يوجد شيء مثل " وظائف الدال ". هذه هي وظائف الاختبار التي تقارن الإخراج مع ما هو موضح في التعليق داخل هذه الوظيفة. يمكن العثور على أمثلة لهذه الاختبارات في التعليمات البرمجية المصدر لحزم الذهاب . إذا كانت هذه الملفات لا تحتوي على اختبارات example_ ، example_ بالبادئة example_ وتنتهي بـ _test.go . يجب أن يبدأ اسم كل وظيفة اختبار Example . على هذا ، كتبت اختبارًا لكائن يكتب SQL في ملف ، واستبدل السجل الحقيقي في الملف بنموذج ، يمكنك من خلاله الحصول على المحتويات وعرضها. تتم مقارنة هذا الاستنتاج مع المعيار. بشكل ملائم ، لا تحتاج إلى كتابة مقارنة بيديك ، ومن الملائم كتابة بضعة أسطر في التعليقات. ولكن عندما يتعلق الأمر باختبار كائن يكتب البيانات إلى ملف csv ، نشأت صعوبات. وفقًا لـ RFC4180 ، يجب فصل الخطوط في CSV بواسطة CRLF ، واستبدال go fmt باستبدال جميع الخطوط بـ LF ، مما يؤدي إلى حقيقة أن المعيار من التعليق لا يتزامن مع الإخراج الفعلي بسبب فواصل الأسطر المختلفة. اضطررت إلى كتابة اختبار منتظم لهذا الكائن ، أثناء إعادة تسمية الملف أيضًا عن طريق إزالة example_ منه.


يبقى السؤال ، إذا query.go اختبار الملف ، على سبيل المثال ، query.go باستخدام كل من المثال والاختبارات التقليدية ، هل يجب أن يكون هناك ملفان example_query_test.go و query_test.go ؟ هنا ، على سبيل المثال ، يوجد مثال واحد فقط. استخدام البحث عن "go test example" لا يزال ممتعًا.


لقد تعلمت كتابة الاختبارات في Go وفقًا للإرشادات التي تقدمها Google لـ "اختبارات الكتابة". تقترح معظم تلك التي صادفتها ( 1 ، 2 ، 3 ، 4 ) مقارنة النتيجة بالتصميم المتوقع للنموذج


 if v != 1.5 { t.Error("Expected 1.5, got ", v) } 

ولكن عندما يتعلق الأمر بمقارنة الأنواع ، فإن البناء المألوف يتطور تطوريًا إلى كومة من استخدام "انعكاس" أو تأكيد نوع. أو مثال آخر ، عندما تحتاج إلى التحقق من أن الشريحة أو الخريطة لها القيمة اللازمة. يصبح الرمز مرهقًا. لذلك أريد أن أكتب الوظائف المساعدة للاختبار. على الرغم من أن الحل الجيد هنا هو استخدام مكتبة للاختبار. لقد وجدت https://github.com/stretchr/testify . يسمح لك بإجراء مقارنات في سطر واحد . هذا الحل يقلل من كمية التعليمات البرمجية ويبسط قراءة ودعم الاختبارات.


تجزئة الرمز واختباره


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


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


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


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


الخلاصة


قبل هذا المشروع ، لم يكن عليّ تحديد هدف تغطية الرمز بنسبة 100٪ بالاختبارات. يمكنني الحصول على تطبيق عملي في 10 ساعات من التطوير ، لكن الأمر استغرق مني 20-30 ساعة للوصول إلى تغطية 95٪. باستخدام مثال صغير ، حصلت على فكرة عن كيفية تأثير قيمة تغطية الكود على جودتها ، ومقدار الجهد المبذول للحفاظ عليها.


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


يمكنك قراءة المزيد عن هذا في المواد التالية والتعليقات عليها:



المفسد

يتم استخدام كلمة "طلاء" حوالي 20 مرة. آسف.

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


All Articles