Hello, World
- İstediğiniz yere bir klasör oluşturun
- İçine
hello.go
adında yeni bir dosya oluşturun ve oluşturduğunuz dosyanın içine aşağıdaki kodu yazın.
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}
Çalıştırmak için
go run hello.go
yazın.Go'da bir program yazmaya başladığınızda, içinde
main
fonksiyonu olan bir main
paketine sahip olacaksınız. Paketler, ilgili Go kodunu birlikte gruplandırmanın yoludur.func
anahtar kelimesi, adı ve gövdesi olan bir fonksiyon tanımlamınızı sağlar.import "fmt"
ile ekrana yazdırma fonksiyonu olan Println
'ı içeren bir paketi içe aktarıyoruz.Bunu nasıl test edersin? "Domain" kodunuzu dış dünyanın vereceği yan etkilerden ayırarak başlayabiliriz.
fmt.Println
bizim yazmadığımız bir fonksiyon ve dış dünyadan gelen bir kod, "Hello World" stringini göndermek ise domain(bizim yazdığımız) kod.Bunları birbirinden ayıralım ve test edilebilir olsun
package main
import "fmt"
func Hello() string {
return "Hello, world"
}
func main() {
fmt.Println(Hello())
}
func
ile başlayan yeni bir fonksiyon oluşturduk ama bu sefer tanımlarken string
keywordunu de kullandık. Yaptığımız tanım ile bu yeni fonksiyonun string
bir değer döneceğini belirttik.Şimdiyse,
hello_test.go
adında bir dosya oluşturalım ve bu dosya ile hello.go
dosyasının içindeki func Hello()
fonksiyonunu test edelim.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)
}
}
Bir sonraki yapacağımız şey, yazdığımız test kodunu çalıştırmak. Terminalde
go test
komutunu çalıştırın. Eğer testler geçerse, büyük ihtimalle Go'nun eski bir sürümünü kullanıyorsun. 1.16 veya üstü bir versiyon kullanıyorsanız testler çalışmayacaktır ve terminalde şöyle bir hata göreceksiniz$ go test
go: cannot find main module; see 'go help modules'
Problem nedir? Tek kelime ile, module. Şanslısın ki, bu problemi çözmek oldukça kolay. Terminalde
go mod init hello
komutunu çalıştırın ve bu komut go.mod
dosyasını şu içeriklerle oluşturacaktırmodule hello
go 1.16
Bu dosya,
go
tarafından kullanılan araçlara(örn: cli) kodunuzla alakalı temel bilgileri verir. Yazdığınız kodu veya uygulamayı dağıtmayı düşünüyorsanız kodun indirilebileceği yerleri ve bağımlılıklarını burada bildirirsiniz. Şimdilik module dosyanız minimum düzeyde ve bu şekilde olması normal. Moduller hakkında daha fazla bilgi edinmek için bu referansa bakabilirsin. Şimdi test etmeye ve Go öğrenmeye dönebiliriz.Sonraki bölümlerde,
go test
veya go build
gibi go cli komutlarını çalıştırmadan önce her yeni projeniz için go mod init SOMENAME
komutunu çalıştırmanız gerekecek.Terminalde
go test
komutunu tekrardan çalıştırın. Testleri geçmiş olmalı. want
string'inin içerisindeki değeri değiştirerek kasıtlı olarak testlerin başarısız olmasını sağlayabilirsiniz.Şunu göz ardı etmeyin, birden çok test çatısı arasından bir seçim yapmanıza veya nasıl kurulacağını öğrenmenize gerek yoktur. İhtiyacınız olan her şey dilde yerleşik olarak bulunur ve test yazarken kullandığınız sözdizimi(syntax) yazacağınız kodun geri kalanında olduğu gibidir.
Test yazmak, bir kaç kurala dayanarak bir fonksiyon yazmaktan farksızdır.
xxx_test.go
gibi adlandırılmış bir .go dosyasının içinde bulunmalıdır- Test fonksiyonlarının isimlendirmesi,
Test
keywordu ile başlamalıdır - Test fonksiyonları sadece bir argüman alır ve oda
t *testing.T
'dır. *testing.T
tipini kullanabilmek için, "testing" paketini eklemelisinimport "testing"
Şimdilik,
*testing.T
tipindeki t
argümanınızın test çerçevesine bir "hook" olduğunu bilmeniz yeterli. Testi başarısız kılmak için, t.Fail()
gibi metotları kullanabilirsin.Bazı yeni konulara değindik
Go dilindeki if ifadeleri, diğer dillere çok benzer.
varName := value
sözdizimi ile tekrar kullanabildiğimiz ve okunabilirliği arttırmak için bazı variablelar tanımladık.t
üzerinde Errorf
method çağırıyoruz, bu metot mesaj yazdıracak ve testi başarsız kılacak. 'f', '%q' gibi yer tutucular ile yer tutucuların yerine gelecek yeni bir string oluşturmamıza izin veren biçimi ifade eder. Testin başarısız olmasını sağladınız da hangi sebeplerden dolayı başarısız olduğunu açıkca belirtmeniz gerekir.Yer tutucu stringler ile alakalı daha ayrıntılı bilgiye fmt go belgesinden ulaşabilirsiniz.
q
, testler için oldukça kullanışlı bir yer tutucudur.Errorf
gibi metotlar ve bizim yazdıklarımız gibi fonksiyonlar arasındaki farka ileride değineceğiz.Go'nun hayatınızın kalitesini artıracak bir diğer özelliği ise dokümantasyondur.
godoc -http :8000
komutunu çalıştararak dökümanlara yerelde göz atabilirsiniz. localhost:8000/pkg adresine gittiğinizde, sisteminizde yüklü bütün paketleri göreceksiniz.Standart kütüphanelerin büyük bir çoğunluğu örneklerle açıklanmış mükemmel dokümantasyona sahiptir. http://localhost:8000/pkg/testing/ adresine giderek Testing konusunda neler yapabileceğinizi görmek faydalı olacaktır.
Eğer
godoc
komutuna ulaşamıyorsanız, go get golang.org/x/tools/cmd/godoc
yazarak edinebilirsiniz.Artık bir testimiz olduğuna göre, yazılımımızı güven içinde yineleyip, geliştirebiliriz.
En son örnekte, testin nasıl yazılacağına ve bir fonksiyon bildireceğine dair bir örnek yapmak için ilk önce kodumuzu yazıp, sonra o koda test kodu yazdık. Artık, ilk önce testimizi yazacağız.
Şimdiki ihtiyacımız, selamlamayı spesifik olarak kime yapacağını belirlemesini sağlamak.
Bu ihtiyacımızı bir test yazarak başlayalım. Bu temel Test Driven Development yaklaşımıdır ve testinizin gerçekten istediğimiz şeyi test ettiğinden emin olmamızı sağlar. İlk önce kodu sonra o koda ait testi yazdığınızda, kod istendiği gibi çalışmasa bile testinizin geçmeye devam etme riski vardır.
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
komutunu çalıştırınca, bir derleme hatası almalısınız./hello_test.go:6:18: too many arguments in call to Hello
have (string)
want ()
Go gibi statik olarak yazılmış bir programlama dili kullanırken derleyiciye kulak vermek önemlidir. Derleyici(compiler) kodunuzun nasıl bir araya gelip çalışması gerektiğini anlar, böylece sizin bunu düşünmenize gerek kalmaz.
Bu durumda, derleyici size ne yapmanız gerektiğini söylüyor.
Hello
fonksiyonunu değiştirmeliyiz ve bir argüman almasını sağlamalıyız.Hello
fonksiyonunu, string türünde bir argümanı kabul edecek şekilde düzenleyin.func Hello(name string) string {
return "Hello, world"
}
Bunu yapar ve tekrardan testleri çalıştırırsanız,
hello.go
dosyası compile olmayacak. Bunu, main
fonksiyonununda olan Hello
fonksiyonunun kullanımını güncelleyerek çözebilirsin.func main() {
fmt.Println(Hello("world"))
}
Testlerinizi çalıştırdığınızda şöyle bir şey görmelisiniz.
hello_test.go:10: got 'Hello, world' want 'Hello, Chris''
Sonunda compile olan bir programımız var fakat bu program testte yazdığımız gereksinimleri karşılamıyor.
Hello
fonksiyonun aldığı name
argümanını Hello,
ile birleştirerek kullanalım ve testi geçelim.func Hello(name string) string {
return "Hello, " + name
}
Testleri tekrar çalıştırdığınızda, geçmiş olmaları gerekir. Normalde Test Driven Development aşamalarının bir parçası olarak şimdi refactor yapmalıyız.
Şimdi, test ile desteklenmiş ve çalışan bir yazılımımız var. Eğer bir source control sistemi kullanıyorsanız(kesinlikle kullanıyor olmalısınız), kodu olduğu gibi
commit
ederdim.Yinede default(main veya master) branch'e pushlamazdım commit'i, çünkü sonradan refactor etmeyi düşünüyorum. Tekrardan refactor edeceğim için herhangi bir karışıklık oluşursa elimizde geri dönebileceğimiz bir versiyonun bulunması güzel, onun için
commit
atabiliriz.Burada gözden geçirecek, tekrardan düzenleyecek pek bir şey yok. Şimdiyse, başka bir programlama dili özelliği olan constant'ı tanıyabiliriz.
Constantlar şu şekilde tanımlanır
const englishHelloPrefix = "Hello, "
Kodumuzu constantlar ile refactor edebiliriz
const englishHelloPrefix = "Hello, "
func Hello(name string) string {
return englishHelloPrefix + name
}
Refactor ettikten sonra hiçbir bir şeyi bozmadığımızdan emin olmak için testleri tekrar çalıştırın.
Bu constant tanımı, her seferinden tekrardan
"Hello, "
'un oluşturulmasından kurtaracağı için uygulamanızın performansını arttırır.Açık olmak gerekirse, çok ahım şahım bir performans artışı olmayacak yani bunu göz ardı edebilirsiniz. Fakat bazı değerleri anlamladırmak ve bazen de performansa artışına yardımcı olması için constantları kullanmayı düşünebilirsiniz.
Şimdiki ihtiyacımız ise fonksiyonumuz boş bir string değeri ile çağrıldığında varsayılan olarak "Hello, " yerine "Hello, World" yazması.
Yeni bir başarısız olacak test yazarak başlayalım.
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)
}
})
}
Burada subtestleri görüyorsunuz, bu testing için kullandığımız yapının içindeki başka bir araçtır. Bazen testleri bir şeyler etrafında gruplamak ve ardından farklı senaryolarda subtestler yazmaya ihtiyaç duyarız.
Bu yaklaşımın yararlarından biri, diğer testlerde de kullanılabilecek paylaşımlı kodlar barındırabilmek.
Tekrar eden bir kod var gördüğünüz gibi, bu kod mesajın nasıl olması gerektiğini kontrol ettiğimiz kod.
Refactor etmek sadece production kodları için geçerli değildir, test kodlarımızı da refactor ederiz.
Testlerinizde bulunan kodların da açık ve net olması önemlidir.
Test kodlarımızı refactor edelim,
func TestHello(t *testing.T) {
assertCorrectMessage := func(t testing.TB, got, want string) {
t.Helper()
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
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)
})
}
Şimdi biz burada ne yaptık?
Testlerin geçmesi için olması gerektiği durumları, assertion olarak bir fonksiyona dönüştürdük. Bu yaptığımız tekrarlamayı azaltıyor ve test kodlarımızın okunabilirliğini arttırıyor. Go'da fonksiyonların içinde başka bir fonksiyon tanımlaması yapabilirsiniz ve bunları değişkenlere atıyabilirsiniz. Fonksiyonların içinde tanımlanan fonksiyonları, normal tanımlanan fonksiyonlar gibi çağırabilirsiniz. Gerektiğinde test kodunun başarısız olduğunu söylemek için
t *testing.T
'i parametre olarak pass etmeliyiz.Helper fonksiyonunda kullandığımız
testing.TB
, testing.T
veya testing.B
tipindeki interfaceleri kabul etmesi için oluşturulmuş başka bir interfacedir. Böylelikle benchmark veya test fonksiyonlarını testing.TB
ile çağırabiliriz.t.Helper()
, test kodlarına bu yöntemin bir yardımcı olduğunu söylemek için gereklidir. Bunu yaparak, testler başarısız olduğunda hangi satır ile testin başarısız olduğu bildirilirken, yardımcı fonksiyonun içindeki satırlar değil, fonksiyon çağrımızı yaptığımız satırlarda hata olduğu bildirilecektir. Bu sayede, geliştiriciler hangi testin başarısız olduğunu kolayca takip edebilecektir. Eğer tam olarak anlamadıysanız, herhangi bir testi başarısız olacak şekile getirin(şuan zaten öyle) ve test çıktısını gözlemleyin ve sonrasında aynı hatalı test kodunu t.Helper()
kodunun başına //
ekleyerek gözlemleyin.Şimdi
Hello()
fonksiyonunu tekrardan refactor ederek, istediğimize ulaşabiliriz.const englishHelloPrefix = "Hello, "
func Hello(name string) string {
if name == "" {
name = "World"
}
return englishHelloPrefix + name
}
Testlerimizi çalıştırırsak, yeni eklediğimiz gereksinimi karşıladığını ve yanlışlıkla başka fonksiyonları bozmadığımızı görebiliriz.
Şimdi yazdığımız koddan memnunuz, tekrardan bu kodları
commit
edebiliriz.TDD disiplini ile ilgili aşamaları tekrar gözden geçirelim,
- Bir test yazın
- Yazdığımız test ile birlikte kodumuzun compile edilmesini sağlayacak duruma getirin
- Testi çalıştırın, başarısz olduğunu görün ve hata mesajının anlamlı olup, olmadığını kontrol edin
- Testi geçecek kadar kod yazın
- Refactor edin
İlk bakışta bu sıkıcı görünebilir, fakat bu TDD aşamalarına bağlı kalmak önemlidir.
Bu yaptığımız sadece ihtiyacınız olan testlere sahip olmanızı sağlamakla kalmaz, aynı zamanda testlerin verdiği güven ile refactorler yaparak çok daha iyi yazılım tasarımlarına sahip olmanızı sağlar.
Testlerin başarısız olduğunu da görmek gerekir, çünkü bu bizlere hata mesajlarının nasıl göründüğü gösterir. Başarısız test mesajlarının sorun ile ilgili net bir fikir vermediği durumlarda, sorunu anlamak ve çözüme kavuşturmak çok zor olur ve hiçbir geliştirici böyle bir code base'de çalışmak istemez.
By ensuring your tests are fast and setting up your tools so that running tests is simple you can get in to a state of flow when writing your code.
Test yazmayarak uzun vadede çok büyük zaman kayıpları yaşarsınız, çünkü herhangi bir değişiklik yaptığınızda yaptığınız değişikliği sizin manuel olarak test etmeniz gerekir ve bu uzun vadede size çok büyük zaman kayıpları yaşatır.
Kodumuzun yapması gereken daha fazla gereksinim var, şimdiyse selamlamanın hangi dilde olacağını belirleyen ikinci bir parametre daha eklemeliyiz. Eğer destek verilmeyen, tanınmayan bir dil geçilirse varsayılan olarak İngilizce'yi kullanacağız.
Bu özelliği, TDD kullanarak kolayca geliştirebiliyor olmamızdan emin olmalıyız.
Mevcut test kodlarına Ispanyolca ile selamlama için bir test yazın.
t.Run("in Spanish", func(t *testing.T) {
got := Hello("Elodie", "Spanish")
want := "Hola, Elodie"
assertCorrectMessage(t, got, want)
})
Hile yapmak yok! Unutma ne olursa olsun, ilk önce test yazılacak. Şimdi test kodlarınızı çalıştırdığınızda, compiler hata verecek bunun sebebi,
Hello()
fonksiyonu şuanda tek bir parametre alıyor fakat biz iki parametre gönderdik../hello_test.go:27:19: too many arguments in call to Hello
have (string, string)
want (string)
Derleme sorunlarını
Hello()
fonksiyonuna bir string parametre daha almasını sağlayarak çözebiliriz.func Hello(name string, language string) string {
if name == "" {
name = "World"
}
return englishHelloPrefix + name
}
Testi tekrar çalıştırdığınızda compiler, bu seferse önceden yazılmış testlerin tek parametre ile çağrıldığından şikayetci olacak.
./hello.go:15:19: not enough arguments in call to Hello
have (string)
want (string, string)
Diğer testlerdeki fonksiyonu çağrılarına, ikinci bir parametre olarak boş bir string yollayın ve artık yazılımınız derlenecek, yeni senaryomuz dışındaysa bütün testler başarılı olacaktır.
hello_test.go:29: got 'Hello, Elodie' want 'Hola, Elodie'
Gönderilen dilin İspanyolcaya eşit olup, olmadığını kontrol etmek için
if
kullanıp, o dile göre selamlama yapabiliriz.func Hello(name string, language string) string {
if name == "" {
name = "World"
}
if language == "Spanish" {
return "Hola, " + name
}
return englishHelloPrefix + name
}
Testlerin hepsi şuanda başarılı olmalıdır.
Şimdiyse yeniden refactor etme zamanı. Görmüş olmalısınız, kodumuzda kendini tekrar eden kısımlar var. İlk önce bu kodu kendiniz düzenlemeye çalışın ve her düzenleme de herhangi bir şeyi bozmadığınızdan emin olmak için testleri çalıştırın.
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
}
- Dil parametresi için
"French"
yollandığı zaman,"Bonjour, "
ile selamlanacağını iddia ettiğiniz bir test yazın - Testin başarısız olduğunu görün ve hata mesajının anlaşılabilir olup, olmadığını kontrol edin
- Sonra Fransızca için yazılan testi geçmek için, kodda en az değişikliği yapın.
Aşağı yukarı aşağıdaki koda benzer bir şeyler yazmış olmalısınız.
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
}
Belirli, ortak bir değeri kontrol ederken birçok
if
ifadesi kullanmak yerine, switch
kullanın. Daha sonra farklı dillerde de destek vermek istersek okumayı kolaylaştırması ve genişletilebilir olması için switch
kullanmak çok daha iyi olur.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
}
Şimdi seçtiğiniz bir konuşma dili için test yazın, mesela Türkçe desteği ekleyin ve yeni dile destek vermek için kodunuzu değiştirmenin, genişletmenin ne kadar kolay olduğunu görün.
Hello()
fonksiyonunun çok kalabalıklaştığını, okumanın zorlaştığını iddia edebilirsiniz ve haklısınız. Ortak olan kısımları alarak yeni bir fonksiyon haline getirerek ona güzel bir isimlendirme yapabilirsiniz.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
}
Yeni kavramlar:
- Yeni fonksiyon tanımlarken ve string bir değer dönüşü yapacağını belirttik.
- Bu yapılan, yazdığımız fonksiyonunda
prefix
adında bir değişken oluşturacak.- Eğer herhangi bir değer seçilmezse,
string
tipinde bir dönüş için""
değeri dönecek. Integer bir dönüş olsaydı bu0
olacaktı.- Bu şekilde bir tanım ile bir değeri dönerken, bu şekilde sadece
return
'da kullanabilirsiniz istersenizreturn prefix
'de tercih edebilirsiniz.
- Bu Go doc'ta fonksiyonunuzun için görüntülenebilecektir ve böylece koduzun ne yaptığı daha anlaşılır hale gelir.
- Gelen parametre değeri, caselerden hiç biri ile eşleşmezse
default
seçilecektir. - Go'da
private
(sadece o paket altında kullanılabilir, dışarıya kapalı) programlama bileşenleri küçük harf ile,public
(dışarıya açık) bileşenlerse büyük harf ile başlar. Algoritmalarımızın içindeki bileşenlerin hepsini dışarıya açmak istemediğimiz zamanprivate
tanım yapmalıyız.
Hello, world
programı yazarken bu kadar çok şey öğrenebileceğini kim bilebilirdi?Şimdiye kadar şunları biraz anlamış olman lazım:
- Test yazmak
- Parametre alan ve dönüş tiplerine sahip fonksiyon tanımlamak
if
,const
veswitch
- Değişken ve constanlar tanımlama
- Başarısız olacağını bilmemize rağmen test yazmak ve o testin hata mesajını görerek herhangi bir eksiklik varsa hata mesajını düzeltmek, sonraysa kodumuzu bu testin gereksinimlerine göre değiştirmek, geliştirmek
- Testi geçmek için yazdığımız az miktarda kodlar sayesinde, çalışan bir yazılımımız olduğunu biliyoruz buda bize geliştirme yaparken doğru yolda olduğumuzu ve bu yolda devam etmemiz gerektiğini belirtiyor
- Ardından refactor etmek, çalıştığını bildiğimiz ve bize güven veren testler sayesinde bir önceki seferde en az seviyede yazıp bıraktığımız kodu, refactor ederek daha iyi bir hale getiriyoruz
Bu örnekte,
Hello()
'dan başlayarak, Hello("name")
sonrasındaysa Hello("name", "French")
e kadar ilerledik.Bu elbette gerçek dünyada üretilen yazılımlar için önemsizdir ancak ilkeler hep aynıdır. Sizin burada öğrendiğiniz disiplini ve TDD akışını gerçek dünyada üretilen yazılımları yazan mühendislerde kullanıyor. TDD yaklaşımı ile geliştirme yapmak için bolca pratik yapmalısınız, fakat yazılımlarınızda sorunları test edebilmek için küçük parçalara bölmek işleri her zaman kolaylaştırır.