go定时器 每天重复_通过测试学习Go:Hello, World
點擊上方藍色“Go語言中文網(wǎng)”關(guān)注我們,設(shè)個星標,每天學(xué)習(xí)?Go?語言
你可以在這里查看本章的所有代碼[1]
按照傳統(tǒng),我們學(xué)習(xí)新語言編寫的第一個程序都是 Hello,world。
在之前的章節(jié)[2]中,我們討論了 Go 死板的文件存放位置。
按照以下路徑創(chuàng)建目錄?$GOPATH/src/github.com/{your-user-id}/hello。
如果你使用的是基于 Unix 的系統(tǒng),你的名字是「bob」并且很樂于遵循 Go 關(guān)于?$GOPATH?的約定(這是最簡單的設(shè)置方式)。那么你可以執(zhí)行以下命令?mkdir -p $GOPATH/src/github.com/bob/hello?快速創(chuàng)建目錄。
在該目錄下創(chuàng)建一個?hello.go?的文件并寫入以下代碼,鍵入?go run hello.go?來運行程序。
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}
它是如何運行的
當你使用 Go 編寫程序時,你將定義一個?main?包,并在其中定義一個?main?函數(shù)。包是一種將相關(guān)的 Go 代碼組合到一起的方式。
func?關(guān)鍵字通過一個名稱和函數(shù)體來定義函數(shù)。
通過?import "fmt"?導(dǎo)入一個包含?Println?函數(shù)的包,我們用它來打印輸出。
如何測試
你會如何測試這個程序?將你「領(lǐng)域」內(nèi)的代碼和外部世界(會引起副作用)分離開會更好。fmt.Println?會產(chǎn)生副作用(打印到標準輸出),我們發(fā)送的字符串在自己的領(lǐng)域內(nèi)。[^注1]
[^注1]: 原文: How do you test this? It is good to separate your "domain" code from the outside world (side-effects). The?fmt.Println?is a side effect (printing to stdout) and the string we send in is our domain.
所以為了更容易測試,我們把這些問題拆分開。
package main
import "fmt"
func Hello() string {
return "Hello, world"
}
func main() {
fmt.Println(Hello())
}
我們再次使用?func?創(chuàng)建了一個新函數(shù),但是這次我們在定義中添加了另一個關(guān)鍵字?string。這意味著這個函數(shù)返回一個字符串。
現(xiàn)在創(chuàng)建一個名為?hello_test.go?的新文件,我們將在這里為?Hello?函數(shù)編寫一個測試
package main
import "testing"
func TestHello(t *testing.T) {
got := Hello()
want := "Hello, world"
if got != want {
t.Errorf("got '%s' want '%s'", got, want)
}
}
在解釋之前,讓我們先運行一下代碼。在終端運行?go test,它應(yīng)該已經(jīng)通過了!為了檢驗測試,可以嘗試通過改變?want?字符串來破壞測試。
注意,你不必在多個測試框架之間進行選擇,然后理解如何安裝它們。你需要的一切都內(nèi)建在語言中,語法與你將要編寫的其余代碼相同。
編寫測試
編寫測試和寫函數(shù)很類似,其中有一些規(guī)則
程序需要在一個名為?
xxx_test.go?的文件中編寫測試函數(shù)的命名必須以單詞?
Test?開始測試函數(shù)只接受一個參數(shù)?
t *testing.T
現(xiàn)在這些信息足以讓我們明白,類型為?*testing.T?的變量?t?是你在測試框架中的 "hook"(鉤子),所以當你想讓測試失敗時可以執(zhí)行?t.Fail()?之類的操作。
我們再討論一些新的話題:
if
Go 的?if?語句非常類似于其他編程語言。
聲明變量
我們使用?varName := value?的語法聲明變量,它允許我們在測試中重用一些值使代碼更具可讀性。
t.Errorf
我們調(diào)用?t?的?Errorf?方法打印一條消息并使測試失敗。f?表示格式化,它允許我們構(gòu)建一個字符串,并將值插入占位符值?%s?中。當你測試失敗時,它能夠讓你清楚測試是如何運行的。
稍后我們將探討方法和函數(shù)之間的區(qū)別。
Go 文檔
Go 的另一個高質(zhì)量特征是文檔。通過運行?godoc -http :8000,可以在本地啟動文檔。如果你訪問?localhost:8000/pkg[3],將看到系統(tǒng)上安裝的所有包。
大多數(shù)標準庫都有優(yōu)秀的文檔和示例。瀏覽?http://localhost:8000/pkg/testing/[4]?是非常值得的,去看一下你有什么可以用的。
Hello, YOU
現(xiàn)在有了測試,就可以安全地迭代我們的軟件了。
在上一個示例中,我們在寫好代碼?之后?編寫了測試,以便你學(xué)會如何編寫測試和聲明函數(shù)。從此刻起,我們將?首先編寫測試。
我們的下一個需求是讓我們指定問候的接受者。
讓我們從在測試中捕獲這些需求開始。這是基本的測試驅(qū)動開發(fā),可以確保我們的測試用例?確實?是測試我們想要的。當你回顧編寫測試時,存在一個風(fēng)險:即使代碼沒有按照預(yù)期工作,測試也可能繼續(xù)通過。
package main
import "testing"
func TestHello(t *testing.T) {
got := Hello("Chris")
want := "Hello, Chris"
if got != want {
t.Errorf("got '%s' want '%s'", got, want)
}
}
這時運行?go test,你應(yīng)該會獲得一個編譯錯誤
./hello_test.go:6:18: too many arguments in call to Hello
have (string)
want ()
當使用像 Go 這樣的靜態(tài)類型語言時,聆聽編譯器?是很重要的。編譯器理解你的代碼應(yīng)該如何拼接到一起工作,所以你就不必關(guān)心這些了。
在這種情況下,編譯器告訴你需要怎么做才能繼續(xù)。我們必須修改函數(shù)?Hello?來接受一個參數(shù)。
修改?Hello?函數(shù)以接受字符串類型的參數(shù)
func Hello(name string) string {
return "Hello, world"
}
如果你嘗試再次運行測試,main.go?將無法編譯,因為你沒有傳遞參數(shù)。傳遞參數(shù) "world" 讓它通過。
func main() {
fmt.Println(Hello("world"))
}
現(xiàn)在,當你運行測試時,你應(yīng)該看到類似的內(nèi)容
hello_test.go:10: got 'Hello, world' want 'Hello, Chris''
我們最終得到了一個可編譯的程序,但是根據(jù)測試它并沒有達到我們的要求。
為了使測試通過,我們使用?name?參數(shù)并用?Hello,?字符串連接它,
func Hello(name string) string {
return "Hello, " + name
}
現(xiàn)在再運行測試就應(yīng)該通過了。通常作為 TDD 周期的一部分,我們該著手?重構(gòu)?了。
關(guān)于版本控制的一點說明
此時,如果你正在使用版本控制(你應(yīng)該這樣做!)我將按原樣?提交?代碼。因為我們擁有一個測試支持的可用軟件。
不過我不會推送到主分支上,因為我下一步計劃重構(gòu)。現(xiàn)在提交很合適,當重構(gòu)中陷入混亂時你總是可以回到可用版本。
這里沒有太多可重構(gòu)的,但我們可以介紹一下另一種語言特性?常量。
常量
通常我們這樣定義一個常量
const helloPrefix = "Hello, "
現(xiàn)在我們可以重構(gòu)代碼
const helloPrefix = "Hello, "
func Hello(name string) string {
return helloPrefix + name
}
重構(gòu)之后,重新測試,以確保沒有破壞任何東西。
常量應(yīng)該可以提高應(yīng)用程序的性能,它避免了每次調(diào)用?Hello?時創(chuàng)建?"Hello, "?字符串實例。
顯然,對于這個例子來說,性能提升是微不足道的!但是創(chuàng)建常量的價值是可以快速理解值的含義,有時還可以幫助提高性能。
再次回到 Hello, world
下一個需求是當我們的函數(shù)用空字符串調(diào)用時,它默認為打印 "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 '%s' want '%s'", 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 '%s' want '%s'", got, want)
}
})
}
這里我們將介紹測試庫中的另一個工具 -- 子測試。有時,對一個「事情」進行分組測試,然后再對不同場景進行子測試非常有效。
這種方法的好處是,你可以建立在其他測試中也能夠使用的共享代碼。
當我們檢查信息是否符合預(yù)期時,會有重復(fù)的代碼。
重構(gòu)不?僅僅?是針對程序的代碼!
重要的是,你的測試?清楚地說明?了代碼需要做什么。
我們可以并且應(yīng)該重構(gòu)我們的測試。
func TestHello(t *testing.T) {
assertCorrectMessage := func(t *testing.T, got, want string) {
t.Helper()
if got != want {
t.Errorf("got '%s' want '%s'", 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)
})
}
我們在這里做了什么?
我們將斷言重構(gòu)為函數(shù)。這減少了重復(fù)并且提高了測試的可讀性。在 Go 中,你可以在其他函數(shù)中聲明函數(shù)并將它們分配給變量。你可以像調(diào)用普通函數(shù)一樣調(diào)用它們。我們需要傳入?t *testing.T,這樣我們就可以在需要的時候令測試代碼失敗。
t.Helper()?需要告訴測試套件這個方法是輔助函數(shù)(helper)。通過這樣做,當測試失敗時所報告的行號將在函數(shù)調(diào)用中而不是在輔助函數(shù)內(nèi)部。這將幫助其他開發(fā)人員更容易地跟蹤問題。如果你仍然不理解,請注釋掉它,使測試失敗并觀察測試輸出。
現(xiàn)在我們有了一個寫得很好的失敗測試,讓我們使用?if?修復(fù)代碼。
const helloPrefix = "Hello, "
func Hello(name string) string {
if name == "" {
name = "World"
}
return helloPrefix + name
}
如果我們運行測試,應(yīng)該看到它滿足了新的要求,并且我們沒有意外地破壞其他功能。
回到版本控制
現(xiàn)在我們對代碼很滿意,我將修改之前的提交,所以我們只提交認為好的版本及其測試。
規(guī)律
讓我們再次回顧一下這個周期
編寫一個測試
讓編譯通過
運行測試,查看失敗原因并檢查錯誤消息是很有意義的
編寫足夠的代碼以使測試通過
重構(gòu)
從表面上看,這可能看起來很乏味,但堅持反饋循環(huán)非常重要。
它不僅確保你有?相關(guān)的測試,還可以確保你通過重構(gòu)測試的安全性來?設(shè)計優(yōu)秀的軟件。
查看測試失敗是一個重要的檢查手段,因為它還可以讓你看到錯誤信息。作為一名開發(fā)人員,如果測試失敗時不能清楚地說明問題所在,那么使用這個代碼庫可能會非常困難。
通過確保你的測試?快速?并建立你的工具,以便運行測試足夠簡單,你在編寫代碼時就可以進入流暢的狀態(tài)。
如果不寫測試,你提交的時候通過運行軟件來手動檢查你的代碼,這會打破你的流暢狀態(tài),而且你任何時候都無法將自己從這種狀態(tài)中拯救出來,尤其是從長遠來看。
繼續(xù)前進!更多需求
天吶,我們有更多的需求了。我們現(xiàn)在需要支持第二個參數(shù),指定問候的語言。如果一種我們不能識別的語言被傳進來,就默認為英語。
通過 TDD 輕松實現(xiàn)這一功能,我們是有信心的!
為使用西班牙語的用戶編寫測試,將其添加到現(xiàn)有的測試用例中。
t.Run("in Spanish", func(t *testing.T) {
got := Hello("Elodie", "Spanish")
want := "Hola, Elodie"
assertCorrectMessage(t, got, want)
})
記住不要欺騙自己!先編寫測試。當你嘗試運行測試時,編譯器?應(yīng)該?會出錯,因為你用兩個參數(shù)而不是一個來調(diào)用?Hello。
./hello_test.go:27:19: too many arguments in call to Hello
have (string, string)
want (string)
通過向?Hello?添加另一個字符串參數(shù)來修復(fù)編譯問題
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
return helloPrefix + name
}
當你嘗試再次運行測試時,它會抱怨在其他測試和?main.go?中沒有傳遞足夠的參數(shù)給?Hello
./hello.go:15:19: not enough arguments in call to Hello
have (string)
want (string, string)
通過傳遞空字符串來修復(fù)它們。現(xiàn)在,除了我們的新場景外,你的所有測試都應(yīng)該編譯并通過
hello_test.go:29: got 'Hola, Elodie' want 'Hello, Elodie'
這里我們可以使用?if?檢查語言是否是「西班牙語」,如果是就修改信息
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
if language == "Spanish" {
return "Hola, " + name
}
return helloPrefix + name
}
測試現(xiàn)在應(yīng)該通過了。
現(xiàn)在是?重構(gòu)?的時候了。你應(yīng)該在代碼中看出了一些問題,其中有一些重復(fù)的「魔術(shù)」字符串。自己嘗試重構(gòu)它,每次更改都要重新運行測試,以確保重構(gòu)不會破壞任何內(nèi)容。
const spanish = "Spanish"
const helloPrefix = "Hello, "
const spanishHelloPrefix = "Hola, "
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
if language == spanish {
return spanishHelloPrefix + name
}
return helloPrefix + 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 helloPrefix + name
}
switch
當你有很多?if?語句檢查一個特定的值時,通常使用?switch?語句來代替。如果我們希望稍后添加更多的語言支持,我們可以使用?switch?來重構(gòu)代碼,使代碼更易于閱讀和擴展。
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
prefix := helloPrefix
switch language {
case french:
prefix = frenchHelloPrefix
case spanish:
prefix = spanishHelloPrefix
}
return prefix + name
}
編寫一個測試,添加用你選擇的語言寫的問候,你應(yīng)該可以看到擴展這個?神奇?的函數(shù)是多么簡單。
最后一次重構(gòu)?
你可能會抱怨說也許我們的函數(shù)正在變得很臃腫。對此最簡單的重構(gòu)是將一些功能提取到另一個函數(shù)中。
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 = englishPrefix
}
return
}
一些新的概念:
在我們的函數(shù)簽名中,我們使用了?命名返回值(
prefix string)。這將在你的函數(shù)中創(chuàng)建一個名為?
prefix?的變量。你只需調(diào)用?
return?而不是?return prefix?即可返回所設(shè)置的值。它將被分配「零」值。這取決于類型,例如?
int?是 0,對于字符串它是?""。這將顯示在 Go Doc 中,所以它使你的代碼更加清晰。
如果沒有其他?
case?語句匹配,將會執(zhí)行?default?分支。函數(shù)名稱以小寫字母開頭。在 Go 中,公共函數(shù)以大寫字母開始,私有函數(shù)以小寫字母開頭。我們不希望我們算法的內(nèi)部結(jié)構(gòu)暴露給外部,所以我們將這個功能私有化。
總結(jié)
誰會知道你可以從?Hello, world?中學(xué)到這么多東西呢?
現(xiàn)在你應(yīng)該以下內(nèi)容有了一定的理解:
Go 的一些語法
編寫測試
用參數(shù)和返回類型聲明函數(shù)
if,else,switch聲明變量和常量
TDD 過程以及步驟的重要性
編寫一個失敗的測試,并查看失敗信息,可以看到我們已經(jīng)為需求寫了一個?相關(guān)?的測試,并且看到它產(chǎn)生了一個?易于理解的失敗描述
編寫最少量的代碼以使其通過,因此我們知道我們有可工作軟件
然后?重構(gòu),支持我們測試的安全性,以確保我們擁有易于使用的精心制作的代碼
在我們的例子中,我們通過小巧易懂的步驟從?Hello()?到?Hello("name"),到?Hello("name", "french")。
與「現(xiàn)實世界」的軟件相比,這當然是微不足道的,但原則依然通用。TDD 是一門需要通過開發(fā)去實踐的技能,通過將問題分解成更小的可測試的組件,你編寫軟件將會更加輕松。
作者:Chris James[5]譯者:Donng[6]校對:polaris1119[7],pityonline[8]
本文由?GCTT[9]?原創(chuàng)編譯,Go 中文網(wǎng)[10]?榮譽推出
推薦閱讀:
通過測試學(xué)習(xí)Go:安裝 Go,搭建開發(fā)環(huán)境
通過測試學(xué)習(xí) Go 語言
喜歡該系列的朋友,歡迎關(guān)注“Go語言中文網(wǎng)”:
參考資料
[1]你可以在這里查看本章的所有代碼:?https://github.com/quii/learn-go-with-tests/tree/master/hello-world
[2]章節(jié):?https://github.com/studygolang/learn-go-with-tests/blob/master/zh-CN/install-go.md#Go-環(huán)境
[3]localhost:8000/pkg:?localhost:8000/pkg
[4]http://localhost:8000/pkg/testing/:?http://localhost:8000/pkg/testing/
[5]Chris James:?https://dev.to/quii
[6]Donng:?https://github.com/Donng
[7]polaris1119:?https://github.com/polaris1119
[8]pityonline:?https://github.com/pityonline
[9]GCTT:?https://github.com/studygolang/GCTT
[10]Go 中文網(wǎng):?https://studygolang.com/
總結(jié)
以上是生活随笔為你收集整理的go定时器 每天重复_通过测试学习Go:Hello, World的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 比熊狗多少钱一只?有成都的朋友吗?
- 下一篇: bootstrap跟vue冲突吗_知道微