国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

36-代碼測試(上):如何編寫Go語言單元測試和性能測試用例?

這篇具有很好參考價值的文章主要介紹了36-代碼測試(上):如何編寫Go語言單元測試和性能測試用例?。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

?

每種語言通常都有自己的測試包/模塊,Go語言也不例外。在Go中,我們可以通過testing包對代碼進行單元測試和性能測試。?

如何測試 Go 代碼?

Go語言有自帶的測試框架testing,可以用來實現單元測試(T類型)和性能測試(B類型),通過go test命令來執(zhí)行單元測試和性能測試。

go test 執(zhí)行測試用例時,是以go包為單位進行測試的。執(zhí)行時需要指定包名,比如go test 包名,如果沒有指定包名,默認會選擇執(zhí)行命令時所在的包。

go test在執(zhí)行時,會遍歷以_test.go結尾的源碼文件,執(zhí)行其中以Test、Benchmark、Example開頭的測試函數。

為了演示如何編寫測試用例,我預先編寫了4個函數。假設這些函數保存在test目錄下的math.go文件中,包名為test,math.go代碼如下:

package test

import (
	"fmt"
	"math"
	"math/rand"
)

// Abs returns the absolute value of x.
func Abs(x float64) float64 {
	return math.Abs(x)
}

// Max returns the larger of x or y.
func Max(x, y float64) float64 {
	return math.Max(x, y)
}

// Min returns the smaller of x or y.
func Min(x, y float64) float64 {
	return math.Min(x, y)
}

// RandInt returns a non-negative pseudo-random int from the default Source.
func RandInt() int {
	return rand.Int()
}

?

測試命名規(guī)范

在我們對Go代碼進行測試時,需要編寫測試文件、測試函數、測試變量,它們都需要遵循一定的規(guī)范。?

測試文件的命名規(guī)范

Go的測試文件名必須以_test.go結尾。例如,如果我們有一個名為person.go的文件,那它的測試文件必須命名為person_test.go。?

包的命名規(guī)范

Go的測試可以分為白盒測試和黑盒測試。

  • 白盒測試:將測試和生產代碼放在同一個Go包中,這使我們可以同時測試Go包中可導出和不可導出的標識符。當我們編寫的單元測試需要訪問Go包中不可導出的變量、函數和方法時,就需要編寫白盒測試用例。
  • 黑盒測試:將測試和生產代碼放在不同的Go包中。這時,我們僅可以測試Go包的可導出標識符。這意味著我們的測試包將無法訪問生產代碼中的任何內部函數、變量或常量。

在白盒測試中,Go的測試包名稱需要跟被測試的包名保持一致,例如:person.go定義了一個person包,則person_test.go的包名也要為person,這也意味著person.goperson_test.go都要在同一個目錄中。

在黑盒測試中,Go的測試包名稱需要跟被測試的包名不同,但仍然可以存放在同一個目錄下。比如,person.go定義了一個person包,則person_test.go的包名需要跟person不同,通常我們命名為person_test

?

函數的命名規(guī)范

測試用例函數必須以Test、BenchmarkExample開頭,例如TestXxxBenchmarkXxx、ExampleXxxXxx分為任意字母數字的組合,首字母大寫。?

除此之外,還有一些社區(qū)的約束,這些約束不是強制的,但是遵循這些約束會讓我們的測試函數名更加易懂。例如,我們有以下函數:

package main

type Person struct {
	age  int64
}

func (p *Person) older(other *Person) bool {
	return p.age > other.age
}

?

其實,還有其他更好的命名方法。比如,這種情況下,我們可以將函數命名為TestOlderXxx,其中Xxx代表Older函數的某個場景描述。例如,strings.Compare函數有如下測試函數:TestCompare、TestCompareIdenticalStringTestCompareStrings。

變量的命名規(guī)范

Go語言和go test沒有對變量的命名做任何約束。?

單元測試用例通常會有一個實際的輸出,在單元測試中,我們會將預期的輸出跟實際的輸出進行對比,來判斷單元測試是否通過。為了清晰地表達函數的實際輸出和預期輸出,可以將這兩類輸出命名為expected/actual,或者got/want。例如:

if c.expected != actual {
  t.Fatalf("Expected User-Agent '%s' does not match '%s'", c.expected, actual)
}

或者:

if got, want := diags[3].Description().Summary, undeclPlural; got != want {
  t.Errorf("wrong summary for diagnostic 3\ngot:  %s\nwant: %s", got, want)
}

其他的變量命名,我們可以遵循Go語言推薦的變量命名方法,例如:

  • Go中的變量名應該短而不是長,對于范圍有限的局部變量來說尤其如此。
  • 變量離聲明越遠,對名稱的描述性要求越高。
  • 像循環(huán)、索引之類的變量,名稱可以是單個字母(i)。如果是不常見的變量和全局變量,變量名就需要具有更多的描述性。

?

單元測試

單元測試用例函數以?Test?開頭,例如?TestXxx?或?Test_xxx?(?Xxx?部分為任意字母數字組合,首字母大寫)。函數參數必須是?*testing.T,可以使用該類型來記錄錯誤或測試狀態(tài)。

我們可以調用?testing.T?的?Error?、Errorf?、FailNow?、Fatal?、FatalIf?方法,來說明測試不通過;調用?Log?、Logf?方法來記錄測試信息。函數列表和相關描述如下表所示:

36-代碼測試(上):如何編寫Go語言單元測試和性能測試用例?,Go項目開發(fā)實戰(zhàn),go,架構下面的代碼是兩個簡單的單元測試函數(函數位于文件math_test.go中):

func TestAbs(t *testing.T) {
    got := Abs(-1)
    if got != 1 {
        t.Errorf("Abs(-1) = %f; want 1", got)
    }
}

func TestMax(t *testing.T) {
    got := Max(1, 2)
    if got != 2 {
        t.Errorf("Max(1, 2) = %f; want 2", got)
    }
}

執(zhí)行go test命令來執(zhí)行如上單元測試用例:

$ go test
PASS
ok      github.com/marmotedu/gopractise-demo/31/test    0.002s

go test命令自動搜集所有的測試文件,也就是格式為*_test.go的文件,從中提取全部測試函數并執(zhí)行。
go test還支持下面三個參數。

  • -v,顯示所有測試函數的運行細節(jié):
$ go test -v
=== RUN   TestAbs
--- PASS: TestAbs (0.00s)
=== RUN   TestMax
--- PASS: TestMax (0.00s)
PASS
ok      github.com/marmotedu/gopractise-demo/31/test    0.002s
  • -run < regexp>,指定要執(zhí)行的測試函數:
$ go test -v -run='TestA.*'
=== RUN   TestAbs
--- PASS: TestAbs (0.00s)
PASS
ok      github.com/marmotedu/gopractise-demo/31/test    0.001s

上面的例子中,我們只運行了以TestA開頭的測試函數。

  • -count N,指定執(zhí)行測試函數的次數:
$ go test -v -run='TestA.*' -count=2
=== RUN   TestAbs
--- PASS: TestAbs (0.00s)
=== RUN   TestAbs
--- PASS: TestAbs (0.00s)
PASS
ok      github.com/marmotedu/gopractise-demo/31/test    0.002s

多個輸入的測試用例

前面介紹的單元測試用例只有一個輸入,但是很多時候,我們需要測試一個函數在多種不同輸入下是否能正常返回。這時候,我們可以編寫一個稍微復雜點的測試用例,用來支持多輸入下的用例測試。例如,我們可以將TestAbs改造成如下函數:

func TestAbs_2(t *testing.T) {
    tests := []struct {
        x    float64
        want float64
    }{
        {-0.3, 0.3},
        {-2, 2},
        {-3.1, 3.1},
        {5, 5},
    }

    for _, tt := range tests {
        if got := Abs(tt.x); got != tt.want {
            t.Errorf("Abs() = %f, want %v", got, tt.want)
        }
    }
}

上述測試用例函數中,我們定義了一個結構體數組,數組中的每一個元素代表一次測試用例。數組元素的的值包含輸入和預期的返回值:

tests := []struct {
    x    float64
    want float64
}{
    {-0.3, 0.3},
    {-2, 2},
    {-3.1, 3.1},
    {5, 5},
}

上述測試用例,將被測函數放在for循環(huán)中執(zhí)行:

   for _, tt := range tests {
        if got := Abs(tt.x); got != tt.want {
            t.Errorf("Abs() = %f, want %v", got, tt.want)
        }
    }

?

上面的測試用例中,我們通過got != tt.want來對比實際返回結果和預期返回結果。我們也可以使用github.com/stretchr/testify/assert包中提供的函數來做結果對比,例如:

func TestAbs_3(t *testing.T) {
    tests := []struct {
        x    float64
        want float64
    }{
        {-0.3, 0.3},
        {-2, 2},
        {-3.1, 3.1},
        {5, 5},
    }

    for _, tt := range tests {
        got := Abs(tt.x)
        assert.Equal(t, got, tt.want)
    }
}

使用assert來對比結果,有下面這些好處:

  • 友好的輸出結果,易于閱讀。
  • 因為少了if got := Xxx(); got != tt.wang {}的判斷,代碼變得更加簡潔。
  • 可以針對每次斷言,添加額外的消息說明,例如assert.Equal(t, got, tt.want, "Abs test")

assert包還提供了很多其他函數,供開發(fā)者進行結果對比,例如Zero、NotZero、Equal、NotEqual、Less、True、Nil、NotNil等。如果想了解更多函數,你可以參考go doc github.com/stretchr/testify/assert

自動生成單元測試用例

通過上面的學習,你也許可以發(fā)現,測試用例其實可以抽象成下面的模型:

36-代碼測試(上):如何編寫Go語言單元測試和性能測試用例?,Go項目開發(fā)實戰(zhàn),go,架構用代碼可表示為:

func TestXxx(t *testing.T) {
    type args struct {
        // TODO: Add function input parameter definition.
    }

    type want struct {
         // TODO: Add function return parameter definition.
    }
    tests := []struct {
        name string
        args args
        want want
    }{
        // TODO: Add test cases.
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Xxx(tt.args); got != tt.want {
                t.Errorf("Xxx() = %v, want %v", got, tt.want)
            }
        })
    }
}

既然測試用例可以抽象成一些模型,那么我們就可以基于這些模型來自動生成測試代碼。Go社區(qū)中有一些優(yōu)秀的工具可以自動生成測試代碼,我推薦你使用gotests工具。

下面,我來講講gotests工具的使用方法,可以分成三個步驟。

第一步,安裝gotests工具:

$ go get -u github.com/cweill/gotests/...

gotests命令執(zhí)行格式為:gotests [options] [PATH] [FILE] ...。gotests可以為PATH下的所有Go源碼文件中的函數生成測試代碼,也可以只為某個FILE中的函數生成測試代碼。

第二步,進入測試代碼目錄,執(zhí)行gotests生成測試用例:

$ gotests -all -w .

?

第三步,添加測試用例:

生成完測試用例,你只需要添加需要測試的輸入和預期的輸出就可以了。下面的測試用例是通過gotests生成的:

func TestUnpointer(t *testing.T) {
    type args struct {
        offset *int64
        limit  *int64
    }
    tests := []struct {
        name string
        args args
        want *LimitAndOffset
    }{
        // TODO: Add test cases.
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Unpointer(tt.args.offset, tt.args.limit); !reflect.DeepEqual(got, tt.want) {
                t.Errorf("Unpointer() = %v, want %v", got, tt.want)
            }
        })
    }
}

?補全后的測試用例見gorm_test.go文件。

性能測試

?

性能測試的用例函數必須以Benchmark開頭,例如BenchmarkXxxBenchmark_Xxx(?Xxx?部分為任意字母數字組合,首字母大寫)。

函數參數必須是*testing.B,函數內以b.N作為循環(huán)次數,其中N會在運行時動態(tài)調整,直到性能測試函數可以持續(xù)足夠長的時間,以便能夠可靠地計時。下面的代碼是一個簡單的性能測試函數(函數位于文件math_test.go中):

func BenchmarkRandInt(b *testing.B) {
    for i := 0; i < b.N; i++ {
        RandInt()
    }
}

go test命令默認不會執(zhí)行性能測試函數,需要通過指定參數-bench <pattern>來運行性能測試函數。-bench后可以跟正則表達式,選擇需要執(zhí)行的性能測試函數,例如go test -bench=".*"表示執(zhí)行所有的壓力測試函數。執(zhí)行go test -bench=".*"后輸出如下:

$ go test -bench=".*"
goos: linux
goarch: amd64
pkg: github.com/marmotedu/gopractise-demo/31/test
BenchmarkRandInt-4      97384827                12.4 ns/op
PASS
ok      github.com/marmotedu/gopractise-demo/31/test    1.223s

上面的結果只顯示了性能測試函數的執(zhí)行結果。BenchmarkRandInt性能測試函數的執(zhí)行結果如下:

BenchmarkRandInt-4   	90848414	        12.8 ns/op

每個函數的性能執(zhí)行結果一共有3列,分別代表不同的意思,這里用上面的函數舉例子:

  • BenchmarkRandInt-4BenchmarkRandInt表示所測試的測試函數名,4表示有4個CPU線程參與了此次測試,默認是GOMAXPROCS的值。
  • 90848414?,說明函數中的循環(huán)執(zhí)行了90848414次。
  • 12.8 ns/op,說明每次循環(huán)的執(zhí)行平均耗時是?12.8?納秒,該值越小,說明代碼性能越高。

如果我們的性能測試函數在執(zhí)行循環(huán)前,需要做一些耗時的準備工作,我們就需要重置性能測試時間計數,例如:

func BenchmarkBigLen(b *testing.B) {
    big := NewBig()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        big.Len()
    }
}

當然,我們也可以先停止性能測試的時間計數,然后再開始時間計數,例如:

func BenchmarkBigLen(b *testing.B) {
	b.StopTimer() // 調用該函數停止壓力測試的時間計數
	big := NewBig()
	b.StartTimer() // 重新開始時間
	for i := 0; i < b.N; i++ {
		big.Len()
	}
}

B類型的性能測試還支持下面 4 個參數。

  • benchmem,輸出內存分配統(tǒng)計:
$ go test -bench=".*" -benchmem
goos: linux
goarch: amd64
pkg: github.com/marmotedu/gopractise-demo/31/test
BenchmarkRandInt-4      96776823                12.8 ns/op             0 B/op          0 allocs/op
PASS
ok      github.com/marmotedu/gopractise-demo/31/test    1.255s

指定了-benchmem參數后,執(zhí)行結果中又多了兩列: 0 B/op,表示每次執(zhí)行分配了多少內存(字節(jié)),該值越小,說明代碼內存占用越?。? allocs/op,表示每次執(zhí)行分配了多少次內存,該值越小,說明分配內存次數越少,意味著代碼性能越高。

  • benchtime,指定測試時間和循環(huán)執(zhí)行次數(格式需要為Nx,例如100x):
$ go test -bench=".*" -benchtime=10s # 指定測試時間
goos: linux
goarch: amd64
pkg: github.com/marmotedu/gopractise-demo/31/test
BenchmarkRandInt-4? ? ? 910328618? ? ? ? ? ? ? ?13.1 ns/op
PASS
ok? ? ? github.com/marmotedu/gopractise-demo/31/test? ? 13.260s
$ go test -bench=".*" -benchtime=100x # 指定循環(huán)執(zhí)行次數
goos: linux
goarch: amd64
pkg: github.com/marmotedu/gopractise-demo/31/test
BenchmarkRandInt-4? ? ? ? ? ?100? ? ? ? ? ? ? ? 16.9 ns/op
PASS
ok? ? ? github.com/marmotedu/gopractise-demo/31/test? ? 0.003s
  • cpu,指定GOMAXPROCS。
  • timeout,指定測試函數執(zhí)行的超時時間:
$ go test -bench=".*" -timeout=10s
goos: linux
goarch: amd64
pkg: github.com/marmotedu/gopractise-demo/31/test
BenchmarkRandInt-4      97375881                12.4 ns/op
PASS
ok      github.com/marmotedu/gopractise-demo/31/test    1.224s

總結

代碼開發(fā)完成之后,我們需要為代碼編寫單元測試用例,并根據需要,給一些函數編寫性能測試用例。Go語言提供了?testing?包,供我們編寫測試用例,并通過?go test?命令來執(zhí)行這些測試用例。

go test在執(zhí)行測試用例時,會查找具有固定格式的Go源碼文件名,并執(zhí)行其中具有固定格式的函數,這些函數就是測試用例。這就要求我們的測試文件名、函數名要符合?go test?工具的要求:Go的測試文件名必須以?_test.go?結尾;測試用例函數必須以?Test?、?Benchmark?、?Example?開頭。此外,我們在編寫測試用例時,還要注意包和變量的命名規(guī)范。

Go項目開發(fā)中,編寫得最多的是單元測試用例。單元測試用例函數以?Test?開頭,例如?TestXxx?或?Test_xxx?(Xxx?部分為任意字母數字組合,首字母大寫)。函數參數必須是?*testing.T?,可以使用該類型來記錄錯誤或測試狀態(tài)。我們可以調用?testing.T?的?Error?、Errorf?、FailNow?、Fatal?、FatalIf?方法,來說明測試不通過;調用?Log?、Logf?方法來記錄測試信息。

下面是一個簡單的單元測試函數:

func TestAbs(t *testing.T) {
? ? got := Abs(-1)
? ? if got != 1 {
? ? ? ? t.Errorf("Abs(-1) = %f; want 1", got)
? ? }
}

編寫完測試用例之后,可以使用?go test?命令行工具來執(zhí)行這些測試用例。
此外,我們還可以使用gotests工具,來自動地生成單元測試用例,從而減少編寫測試用例的工作量。

我們在Go項目開發(fā)中,還經常需要編寫性能測試用例。性能測試用例函數必須以Benchmark開頭,以*testing.B?作為函數入參,通過?go test -bench <pattern>?運行。文章來源地址http://www.zghlxwxcb.cn/news/detail-847581.html

課后練習

  1. 編寫一個?PrintHello?函數,該函數會返回?Hello World?字符串,并編寫單元測試用例,對?PrintHello?函數進行測試。
  2. 思考一下,哪些場景下采用白盒測試,哪些場景下采用黑盒測試?

到了這里,關于36-代碼測試(上):如何編寫Go語言單元測試和性能測試用例?的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯(lián)網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • Go單元測試與集成測試:編寫可靠的Go測試用例

    Go語言是一種現代編程語言,它具有簡潔的語法、強大的性能和易于使用的并發(fā)特性。隨著Go語言的發(fā)展和廣泛應用,編寫高質量的測試用例變得越來越重要。在Go語言中,我們可以使用內置的testing包來編寫單元測試和集成測試。本文將涵蓋Go單元測試與集成測試的核心概念、

    2024年02月19日
    瀏覽(27)
  • 軟件測試-測試的概念,單元測試的詳細介紹,如何設計測試用例

    軟件測試-測試的概念,單元測試的詳細介紹,如何設計測試用例

    作為測試工程師,你就必須掌握設計開發(fā)測試基礎架構的關鍵技術。第三,隨著自動化測試的規(guī)?;瑴y試數據準備的各種問題被逐漸暴露并不斷放大,成為影響自動化測試效率以及穩(wěn)定性的“攔路虎”。早期的傳統(tǒng)測試數據準備方法,無論是從測試數據準備的時間成本,還

    2024年04月14日
    瀏覽(32)
  • 商城的TPS與并發(fā)用戶數是如何換算的?請編寫商城的性能測試用例?

    商城的TPS與并發(fā)用戶數的換算關系可以通過以下公式計算: TPS = 并發(fā)用戶數 / 平均事務響應時間 其中,平均事務響應時間是指系統(tǒng)處理一個事務所需的平均時間。 下面是商城性能測試的一些用例示例: 用戶登錄: 目標:測試用戶登錄功能的性能和穩(wěn)定性。 測試步驟:模擬

    2024年02月12日
    瀏覽(27)
  • client-go初級篇,從操作kubernetes到編寫單元測試

    client-go初級篇,從操作kubernetes到編寫單元測試

    歡迎訪問我的GitHub 這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos 本篇概覽 盡管長篇系列《client-go實戰(zhàn)》的內容足夠豐富,然而內容太多每個知識點也有一定深度,對于打算快速學習并開始kubernetes開發(fā)的新手并不友好,因此本篇的目標讀者就是

    2024年02月12日
    瀏覽(44)
  • 【測試開發(fā)】單元測試、基準測試和性能分析(以 Go testing 為例)

    【測試開發(fā)】單元測試、基準測試和性能分析(以 Go testing 為例)

    你寫不出 bug-free 的代碼。 你認為自己寫出了 bug-free 的代碼,但它在你意想不到的地方出錯了。 你覺得自己寫出了永不出錯的代碼,但它的性能十分糟糕。 “測試左移”距離真正落地或許還有不短的距離,但在開發(fā)過程中注重自己的代碼質量,至少養(yǎng)成 寫單測 的習慣還是很

    2024年02月04日
    瀏覽(19)
  • Go語言單元測試

    Go語言單元測試

    Go語言中的測試依賴 go test 命令,go test 命令是一個按照一定約定和組織的測試代碼的驅動程序。在包目錄 內,所有以 _test.go 為后綴名的源代碼文件都是 go test 測試的一部分,不會被 go build 編譯到最終的可執(zhí)行 文件中。 在 *_test.go 文件中有三種類型的函數, 單元測試函數

    2024年02月11日
    瀏覽(25)
  • Go語言測試——【單元測試 | Mock測試 | 基準測試】

    Go語言測試——【單元測試 | Mock測試 | 基準測試】

    作者 :非妃是公主 專欄 :《Golang》 博客主頁 :https://blog.csdn.net/myf_666 個性簽:順境不惰,逆境不餒,以心制境,萬事可成?!鴩?軟件測試 :軟件測試(英語:Software Testing),描述一種用來促進鑒定軟件的正確性、完整性、安全性和質量的過程。換句話說,軟件測

    2024年02月10日
    瀏覽(22)
  • Go語言的單元測試與基準測試詳解

    Go語言的單元測試與基準測試詳解

    單元測試 以一個加法函數為例,對其進行單元測試。 首先編寫add.go文件: 其次編寫add_test.go文件,在go語言中,測試文件均已_test結尾,這里只需要在被測試的文件后加上_test即可。并且測試文件與要被測試的文件需要放在同一個包中,并不像 Java 那樣需要將所有的測試文件

    2024年02月03日
    瀏覽(20)
  • 程序員技能與成長:如何學習新的編程語言和代碼規(guī)范與單元測試

    程序員技能與成長:如何學習新的編程語言和代碼規(guī)范與單元測試

    一名軟件工程師的最大挑戰(zhàn)就是使自己的技術棧跟得上技術的發(fā)展,而在這個技術飛速發(fā)展的時代,保證自己不被淘汰的唯一方法就是不斷學習。 那么,程序員需要掌握多門編程語言嗎?很多初學者都被這個問題所困擾。Google研究總監(jiān) Peter Norvig曾就這個問題給出自己的觀點

    2024年04月10日
    瀏覽(27)
  • 【go語言】3.3.1 單元測試和基準測試

    【go語言】3.3.1 單元測試和基準測試

    Go 語言的 testing 包為編寫單元測試和基準測試提供了強大的支持。單元測試用于驗證代碼的正確性,基準測試用于測量代碼的性能。 在Go語言中,單元測試和基準測試是兩種常用的測試方法,用于測試和評估代碼的質量和性能。 單元測試是一種針對代碼中最小可測試單元(函

    2024年02月08日
    瀏覽(24)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包