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