مرحبا بالعالم!
يمكنك العثور على جميع الشيفرة لهذا الفصل هنا
من التقليد أن يكون برنامجك الأول في اي لغة جديدة مرحبًا بالعالم.
- قم بإنشاء مجلد أينما تريد
- ضع ملفًا جديدًا فيه بالاسم
hello.go
وضع الشيفرة التالية داخله
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}
لتشغيله، اكتب go run hello.go
كيف تم تشغيلة
عندما تكتب برنامجًا في Go، ستكون لديك حزمة main
معرفة مع دالة main
داخلها. الحزم هي طرق لتجميع الشيفرة الخاصة بـ Go ذات الصلة معًا.
كلمة func
هي كيفية تعريف دالة باسم ومحتوى.
باستخدام
import "fmt"
نقوم بتوريد حزمة تحتوي على دالة Println
التي نستخدمها للطباعة.
كيفية الاختبار
كيف يمكنك اختبار هذا؟ من الجيد فصل شيفرتك “المجالية” عن العالم الخارجي (الآثار الجانبية). fmt.Println
هو اثر جانبي (الطباعة إلى stdout) والنص الذي نرسله هو مجالنا.
لذا دعنا نفصل هذه القضايا حتى يكون من الأسهل اختبارها.
package main
import "fmt"
func Hello() string {
return "Hello, world"
}
func main() {
fmt.Println(Hello())
}
لقد قمنا بإنشاء دالة جديدة مرة أخرى باستخدام func
ولكن هذه المرة قمنا بإضافة كلمة رئيسية أخرى string
في التعريف. هذا يعني أن هذه الدالة تقوم بارجاع string
.
string يعني سلسلة من الاحرف (نص)
الآن قم بإنشاء ملف جديد يسمى hello_test.go
حيث سنكتب اختبارًا لدالتنا Hello
.
package main
import "testing"
func TestHello(t *testing.T) {
got := Hello()
want := "Hello, world"
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
الوحدات في Go؟
الخطوة التالية هي تشغيل الاختبارات. أدخل go test
في الطرفية الخاصة بك. إذا نجحت الاختبارات، فمن المحتمل أنك تستخدم نسخة سابقة من Go. ومع ذلك، إذا كنت تستخدم Go 1.16 أو أحدث، فمن المرجح أن الاختبارات لن تعمل على الإطلاق. بدلاً من ذلك، سترى رسالة خطأ مثل هذه في الطرفية:
$ go test
go: cannot find main module; see 'go help modules'
ما هي المشكلة؟ بكلمة واحدة، الوحدات. لحسن الحظ، من السهل حل المشكلة. أدخل
go mod init hello
في الطرفية الخاصة بك. سينشئ ذلك ملفًا جديدًا بالمحتويات التالية:
module hello
go 1.16
يخبر هذا الملف أدوات go
معلومات أساسية حول شيفرتك. إذا كنت تخطط لتوزيع تطبيقك، فستضمن حيث تكون الشيفرة متاحة للتنزيل بالإضافة إلى معلومات حول الاعتماديات. في الوقت الحالي، ملف الوحدة الخاص بك هو أدنى حد، ويمكنك تركه بهذه الحالة. لقراءة المزيد حول الوحدات، يمكنك التحقق من المرجع في توثيق Golang. يمكننا العودة إلى اختبار وتعلم Go الآن لأن الاختبارات يجب أن تعمل، حتى على Go 1.16.
في الفصول القادمة، ستحتاج إلى تشغيل go mod init SOMENAME
في كل مجلد جديد قبل تشغيل الأوامر مثل go test
أو go build
.
العودة إلى الاختبار
قم بتشغيل go test
في الطرفية الخاصة بك. يجب أن ينجح الاختبار! فقط للتحقق، حاول تعمدًا كسر الاختبار عن طريق تغيير النص في المتغير want
.
لاحظ كيف لم تكن مضطرًا لاختيار بين إطارات اختبار متعددة ثم معرفة كيفية التثبيت. كل ما تحتاجه مدمج في لغة البرمجة والصيغة النحوية هي نفس الصيغة التي ستكتب بها بقية الشيفرة.
كتابة الاختبارات
كتابة اختبار هو مثل كتابة دالة، مع بعض القواعد
- يجب أن يكون في ملف بأسم مثل
xxx_test.go
- يجب أن تبدأ دالة الاختبار بالكلمة
Test
- تأخذ دالة الاختبار معامل واحد فقط
t *testing.T
- من أجل استخدام نوع
*testing.T
، تحتاج إلى استيراد “testing”، كما فعلنا مع “fmt” في الملف الآخر
في الوقت الحالي، من الكافي معرفة أن t
من نوع *testing.T
هو مدخلك الخاص الى إطار الاختبار حتى تتمكن من فعل أشياء مثل t.Fail()
عندما تريد ان يفشل التشغيل.
لقد غطينا بعض المواضيع الجديدة:
الشرط if
العبارات الشرطية if
في Go تشبه إلى حد كبير لغات البرمجة الأخرى.
تعريف المتغيرات
نقوم بتعريف بعض المتغيرات بناءً على الصيغة varName := value
، مما يتيح لنا إعادة استخدام بعض القيم في اختبارنا لتحسن مقرؤيتها.
التابعة t.Errorf
نحن نقوم باستدعاء التابعة Errorf
على t
الخاص بنا والتي ستقوم بطباعة رسالة وإخفاق الاختبار. الحرف f
يشير إلى الصيغة التي تسمح لنا ببناء سلسلة نصية مع قيم مدخلة في القيم النائبة %q
. عندما تجعل الاختبار يفشل، يجب أن يكون توضح كيفية عمله.
يمكنك قراءة المزيد حول القيم النائبة في مستندات fmt go. بالنسبة للاختبارات، %q
مفيد جدًا حيث يضع قيمك في علامات اقتباس مزدوجة.
سنقوم لاحقًا باستكشاف الفرق بين التوابع (methods) والدوال (functions).
الامر Go doc
ميزة أخرى في Go هي الوثائق. يمكنك تشغيل الوثائق محليًا عن طريق تشغيل godoc -http :8000
. إذا ذهبت إلى localhost:8000/pkg سترى جميع الحزم المثبتة على نظامك.
تمتلك الغالبية العظمى من المكتبات القياسية وثائق ممتازة مع أمثلة. من الجيد الانتقال إلى http://localhost:8000/pkg/testing/ لرؤية ما هو متاح لك.
إذا لم يكن لديك الأمر godoc
، فقد تكون تستخدم الإصدار الأحدث من Go (1.14 أو أحدث) الذي لا يتضمن بعد godoc
. يمكنك تثبيته يدويًا بتشغيل الامر go install golang.org/x/tools/cmd/godoc@latest
في الطرفية.
مرحبًا، أنت
الآن بعد أن اصبح لدينا اختبار، يمكننا تكرار تطوير البرنامج بأمان.
في المثال السابق، كتبنا الاختبار بعد كتابة الشيفرة فقط لتحصل على مثال على كيفية كتابة اختبار وتعريف دالة. من هذه النقطة وما بعدها، سنقوم بكتابة الاختبارات أولاً.
متطلبنا التالي هو السماح بتحديد مستلم للتحية.
لنبدأ بتوثيق هذه المتطلبات في اختبار. هذا هو التطوير القائم او المعتمد على الاختبارات والذي يسمح لنا بالتأكد من أن الاختبار الخاص بنا يختبر بالفعل ما نريد. عندما تكتب الاختبارات بشكل متأخر، هناك خطر أن يستمر اختبارك في النجاح حتى لو لم تعمل الشيفرة كما هو مقصود لها ان تعمل.
package main
import "testing"
func TestHello(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
الآن قم بتشغيل go test
، يجب أن يحدث خطأ في الترجمة
./hello_test.go:6:18: too many arguments in call to Hello
have (string)
want ()
عند استخدام لغة محددة النوع (statically typed) مثل Go، من المهم أن تستمع إلى المترجم.
في هذه الحالة، المترجم يخبرك بما تحتاج إلى القيام به للمتابعة. علينا تغيير دالتنا Hello
لتقبل معامل من نوع string.
قم بتحرير دالة Hello
لتقبل معامل من نوع string.
func Hello(name string) string {
return "Hello, world"
}
إذا حاولت تشغيل الاختبارات مرة أخرى، ستفشل الترجمة لملف hello.go
لأنك لم تقم بتمرير معامل name. قم بإرسال “world” لتجعلها تترجم بنجاح.
func main() {
fmt.Println(Hello("world"))
}
الان عندما تقوم بتشغيل الاختبار يجب ان ترى شيئاً مماثل لـ:
hello_test.go:10: got 'Hello, world' want 'Hello, Chris''
لدينا الآن برنامج يترجم بنجاح ولكنه لا يلبي متطلباتنا وفقًا للاختبار.
لنجعل الاختبار ينجح بواسطة استخدام معامل الاسم ودمجه مع Hello
func Hello(name string) string {
return "Hello, " + name
}
عند تشغيل الاختبارات، يجب أن تنجح الآن. عادةً كجزء من دورة TDD، يجب أن نقوم الآن بالـ تنقيح (اعادة الكتابة).
ملاحظة حول التحكم في الشيفرة المصدرية
في هذه المرحلة، إذا كنت تستخدم التحكم في الشيفرة المصدرية git
(الذي يجب عليك استخدامه!)، يجب أن تقوم بـ commit
للشيفرة كما هي. لدينا برنامج يعمل معتمد على اختبار.
لكن لا يجب عليك دفع الشيفرة إلى (main) حتى الآن، سنقوم بالتنقيح واعادة الكتابة بعد ذلك. من الجميل أن تقوم بـ commit في هذه المرحلة في حالة دخولك في فوضى أثناء اعادة الكتابة - يمكنك دائمًا العودة إلى النسخة التي كانت تعمل مسبقاً.
لا يوجد الكثير لاعادة كتابتة هنا، ولكن يمكننا أن نقدم ميزة لغوية أخرى في go، الثوابت (constants).
الثوابت
تُعرّف كما يلي:
const englishHelloPrefix = "Hello, "
الان بأمكاننا اعادة كتابة شيفرتنا كالتالي
const englishHelloPrefix = "Hello, "
func Hello(name string) string {
return englishHelloPrefix + name
}
بعد اعادة الكتابة، أعد تشغيل الاختبارات للتأكد من أنك لم تقم بتخريب شيئًا في الشيفرة.
من الجيد التفكير في إنشاء الثوابت لتقديم معنى للقيم وأحيانًا لدعم الأداء.
مرحبًا، بالعالم… مجدداً
المتطلب التالي هو عندما يتم استدعاء دالتنا بمعلمة سلسلة فارغة، يجب أن تعيد بشكل افتراضي طباعة “Hello, World” بدلاً من “Hello, “.
ابدأ بكتابة اختبار جديد.
func TestHello(t *testing.T) {
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
if got != want {
t.Errorf("got %q want %q", got, want)
}
})
t.Run("say 'Hello, World' when an empty string is supplied", func(t *testing.T) {
got := Hello("")
want := "Hello, World"
if got != want {
t.Errorf("got %q want %q", got, want)
}
})
}
هنا نقوم بإدخال أداة أخرى في مجموعة أدوات الاختبارات الخاصة بنا، وهي الاختبارات الفرعية. في بعض الأحيان، من المفيد تجميع الاختبارات حول “شيء” معين ثم إنشاء اختبارات فرعية تصف سيناريوهات مختلفة.
فائدة هذا النهج هو أنه يمكنك إعداد شيفرة مشتركة يمكن استخدامها في الاختبارات الأخرى.
بينما لدينا اختبار يفشل، دعنا نقوم بإصلاح الشيفرة باستخدام if
.
const englishHelloPrefix = "Hello, "
func Hello(name string) string {
if name == "" {
name = "World"
}
return englishHelloPrefix + name
}
إذا قمنا بتشغيل الاختبارات، يجب أن نرى أنها تلبي المتطلب الجديد وأننا لم نقم بتخريب الوظائف الأخرى بطريق الخطأ.
من المهم أن تكون اختباراتك واضحة المواصفات لما يجب على الشيفرة أن تفعله. ولكن هناك شيفرة متكررة عندما نتحقق من ما إذا كانت الرسالة هي ما نتوقعه.
اعادة الكتابة ليس للشيفرة الإنتاجية فقط!
الآن بعد أن تم تجاوز الاختبارات، يمكننا ويجب علينا أن نقوم بأعادة كتابة اختباراتنا ايضا.
func TestHello(t *testing.T) {
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
assertCorrectMessage(t, got, want)
})
t.Run("empty string defaults to 'world'", func(t *testing.T) {
got := Hello("")
want := "Hello, World"
assertCorrectMessage(t, got, want)
})
}
func assertCorrectMessage(t testing.TB, got, want string) {
t.Helper()
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
هنا قمنا باعادة كتابة شيفرة التأكد في دالة جديدة. هذا يقلل من التكرار ويحسن قراءة اختباراتنا. يجب أن نمرر t *testing.T
لنستطيع إخبار شيفرة الاختبار بالاخفاق عند الحاجة.
بالنسبة للدوال المساعدة مثل التي كتبناها، يجب أن تقبل testing.TB
وهي واجهة تُستوفى من قِبل *testing.T
و *testing.B
، بحيث يمكنك استدعاء الدوال المساعدة من اختبار، أو اختبار أداء (لا تقلق إذا كانت كلمات مثل “واجهة” لا تعني لك شيئًا الآن، سنتعرف على ذلك لاحقًا).
نحتاج t.Helper()
هنا لإخبار اطار الاختبارات أن هذه الدالة هي مساعد. عند فشل الاختبار، بفعل ذلك عندما يخفق الاختبار ستقوم بأرجاع السطر البرمجي الذي حدث فيه الخطأ في داله الاختبار بدلاً من داخل مساعد الاختبار. سيُساعد ذلك المطورين الآخرين في تتبع المشاكل بشكل أسهل. إذا لم تزل لا تفهم، قم بتعليق الكود، اجعل اختبار يخفق، ولاحظ مخرجات الاختبار. التعليقات في Go هي طريقة رائعة لإضافة معلومات إضافية لشيفرتك، أو في هذه الحالة، طريقة سريعة لإخبار المترجم بتجاهل السطر. يمكنك تعليق الكود t.Helper()
عن طريق إضافة شرطتين مائلتين //
في بداية السطر. يجب أن ترى أن السطر يتحول إلى اللون الرمادي أو يتغير لونه إلى لون آخر غير لون الشيفرة الأخرى للدلالة على أنه تم تعليقه الآن.
التحكم في الشيفرة المصدرية مجدداً
الآن بعدما اصبحنا راضون عن الشيفرة، يمكننا التعديل على الـ commit السابق حتى نقوم بتحديث النسخة الممتازة من الشيفرة مع اختبارها.
الانضباط
لنراجع دورة العمل مرة أخرى:
- كتابة اختبار
- التأكد من ترجمة البرنامج من دون اخطاء
- تشغيل الاختبار، رؤية اخفاقة والتأكد من أن رسالة الخطأ ذات معنى
- كتابة الشيفرة الكافية لجعل الاختبار ينجح
- اعادة الكتابة
يمكن أن يبدو هذا المسار مملًا، ولكن التمسك بدورة التغذية الراجعة هو أمر مهم.
لا يساعد ذلك فقط في التأكد من أن لديك اختبارات ذات صلة، بل يساعد أيضًا في ضمان تصميم برمجيات جيدة من خلال اعادة الكتابة متأمناً بالاختبارات.
رؤية اخفاق الاختبار هو فحص مهم لأنه يتيح لك أيضًا رؤية شكل رسالة الخطأ. بالنسبة للمطور، قد يكون من الصعب جدًا العمل مع شيفرة ما عندما لا توفر الاختبارات الغير ناجحة فكرة واضحة عن المشكلة.
من خلال التأكد من أن اختباراتك سريعة وإعداد أدواتك بحيث يكون تشغيل الاختبارات بسيطًا، يمكنك ذلك من كتابة البرنامج بشكل اسرع وافضل.
لكن عند عدم كتابة الاختبارات، فأنت تلتزم بفحص الشيفرة يدويًا عن طريق تشغيل البرنامج الخاص بك، مما سوف يجعلك بطيئاً في التطوير ولن توفر لنفسك أي وقت، خاصة على المدى الطويل.
لنستمر! المزيد من المتطلبات
لدينا المزيد من المتطلبات الآن. نحتاج الآن لدعم معلمة ثانية، تحديد لغة التحية. إذا تم تمرير لغة لم نتعرف عليها، سنقوم باخيار اللغة الإنجليزية بشكل افتراضي.
يجب أن نكون واثقين من أنه يمكننا استخدام TDD لتطوير هذه الوظيفة بسهولة!
اكتب اختبارًا لمستخدم يمرر الإسبانية. أضفه إلى الحزمة الاختبار السابق.
t.Run("in Spanish", func(t *testing.T) {
got := Hello("Elodie", "Spanish")
want := "Hola, Elodie"
assertCorrectMessage(t, got, want)
})
تذكر ألا تحتال! اكتب الاختبار أولاً. عندما تحاول تشغيل الاختبار، يجب أن يقوم المترجم بالشكوى لأنك تستدعي Hello
بمعاملين بدلاً من واحد.
./hello_test.go:27:19: too many arguments in call to Hello
have (string, string)
want (string)
حل مشكلة الترجمة عن طريق إضافة معامل نصي إضافي إلى Hello
اسمه language
من نوع string
.
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
return englishHelloPrefix + name
}
عندما تحاول تشغيل الاختبار مرة أخرى، سيشتكي من عدم تمرير عدد كافٍ من المعاملات إلى Hello
في الاختبارات الأخرى وفي hello.go
.
./hello.go:15:19: not enough arguments in call to Hello
have (string)
want (string, string)
قم بتصحيحها عن طريق تمرير نص فارغ. الآن يجب أن تعمل جميع الاختبارات، باستثناء المتطلب الجديد.
hello_test.go:29: got 'Hello, Elodie' want 'Hola, Elodie'
يمكننا استخدام if
هنا للتحقق مما إذا كانت اللغة تساوي “Spanish”، وإذا كانت كذلك يتم تغيير الرسالة.
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
if language == "Spanish" {
return "Hola, " + name
}
return englishHelloPrefix + name
}
الاختبارات يجب أن تنجح الآن.
الآن حان الوقت لاعادة الكتابة. ربما رأيت بعض المشاكل في الشيفرة، “نصوص سحرية”، بعضها متكرر. حاول تنقيحها بنفسك، ومع كل تغيير تأكد من إعادة تشغيل الاختبارات للتأكد من أن اعادة الكتابة لم تؤدي إلى تخريب أي شيء.
const spanish = "Spanish"
const englishHelloPrefix = "Hello, "
const spanishHelloPrefix = "Hola, "
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
if language == spanish {
return spanishHelloPrefix + name
}
return englishHelloPrefix + name
}
الفرنسية
- اكتب اختبارًا يؤكد أنه إذا قمت بتمرير
"French"
ستحصل على"Bonjour, "
- تأكد من فشل الاختبار، وتحقق من سهولة قراءة رسالة الخطأ
- قم بأصغر تغيير معقول في الشيفرة
قد تكون كتبت شيئًا يبدو تقريبًا مثل هذا
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
if language == spanish {
return spanishHelloPrefix + name
}
if language == french {
return frenchHelloPrefix + name
}
return englishHelloPrefix + name
}
switch
عندما تكون هناك الكثير من السطور التي تحتوي على if
التي تفحص قيمة معينة، فمن الشائع استخدام switch
بدلاً من ذلك. يمكننا استخدام switch
لاعادة كتابة الشيفرة لجعلها مقروءة أكثر وقابلة للتوسع إذا أردنا إضافة دعم لغات أخرى لاحقًا.
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
prefix := englishHelloPrefix
switch language {
case "French":
prefix = frenchHelloPrefix
case "Spanish":
prefix = spanishHelloPrefix
}
return prefix + name
}
اكتب اختبارًا الآن يتضمن تحية بلغتك المفضلة وسترى مدى بساطة الاضافة الى دالتنا الرائعة.
آخر اعادة كتابة؟
يمكن أن نقول ربما دالتنا بدأت تصبح كبيرة قليلاً. أبسط اعادة كتابة لها سيكون باستخراج بعض الوظائف إلى دالة أخرى.
const (
french = "French"
spanish = "Spanish"
englishHelloPrefix = "Hello, "
spanishHelloPrefix = "Hola, "
frenchHelloPrefix = "Bonjour, "
)
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
return greetingPrefix(language) + name
}
func greetingPrefix(language string) (prefix string) {
switch language {
case french:
prefix = frenchHelloPrefix
case spanish:
prefix = spanishHelloPrefix
default:
prefix = englishHelloPrefix
}
return
}
بعض المفاهيم الجديدة:
- في توقيع دالتنا، قمنا بإنشاء قيمة إرجاع مسماة
(prefix string)
.- سيتم إنشاء متغير يسمى
prefix
في الدالة.- سيتم تعيينه بقيمة “صفر”. هذا يعتمد على نوع البيانات، على سبيل المثال، للأعداد الصحيحة
int
هو 0 والنصوصstring
هو""
.- يمكنك إرجاع ما تم تعيينه إليه ببساطة باستدعاء
return
دونreturn prefix
.
- يمكنك إرجاع ما تم تعيينه إليه ببساطة باستدعاء
- سيتم تعيينه بقيمة “صفر”. هذا يعتمد على نوع البيانات، على سبيل المثال، للأعداد الصحيحة
- سيتم عرض هذا في Go Doc لدالتك مما يجعل مقصود الكود واضحاً.
- سيتم إنشاء متغير يسمى
- الـ
default
في حالةswitch
سيتم التحول إليه إذا لم تتطابق أي من بياناتcase
الأخرى. - اسم الدالة يبدأ بحرف صغير. في Go، تبدأ الوظائف العامة (التي يمكن استخدامها من خارج الحزمة) بحرف كبير والخاصة تبدأ بحرف صغير. نحن لا نريد أن تكون تفاصيل خوارزميتنا معروضة للكل، لذا جعلنا هذه الدالة خاصة.
- أيضًا، يمكننا تجميع الثوابت في كتلة بدلاً من تعريفها كل واحدة في سطر منفصل. من الجيد استخدام سطر بين مجموعات الثوابت ذات الصلة لتحسين مقرؤيتها.
ختامًا
من كان يتوقع أنه بالامكان الحصول على الكثير من برنامج بسيط مثل “Hello, world”؟
الآن يجب أن تكون لديك فهم بعض جوانب لغة Go فيما يتعلق بالـ:
كتابة الاختبارات
- تعريف الدوال، مع المعلمات وأنواع الإرجاع
if
،const
وswitch
- تعريف المتغيرات والثوابت
عملية TDD ولماذا تلك الخطوات مهمة
- كتابة اختبار فاشل ورؤية فشله حتى نتأكد من أننا قد كتبنا اختبارًا ذا صلة لمتطلباتنا ورأينا أنه ينتج وصفًا سهل الفهم الاخفاق
- كتابة أقل كمية من الكود لجعل الاختبار ينجح حتى نتأكد من وجود برنامج يعمل
- ثم اعادة الكتابة، مدعومين بأمان اختباراتنا للتأكد من وجود كود جيد المستوى وسهل العمل معه
في حالتنا، لقد تقدمنا من Hello()
إلى Hello("name")
، ثم إلى Hello("name", "French")
في خطوات صغيرة وسهلة الفهم.
هذا بالطبع بسيط مقارنة بالبرمجيات في “العالم الحقيقي” لكن المبادئ تظل قائمة. TDD هو مهارة تحتاج إلى ممارسة لتطويرها، ولكن من خلال تقسيم المشكلات إلى مكونات أصغر يمكنك اختبارها، ستكون لديك سهولة أكبر في كتابة البرمجيات.