精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

單測時盡量用Fake Object,你學會了嗎?

開發 前端
一些開源項目,比如etcd,也提供了用于測試的自身簡化版的實現(embed)[5]。這一點也值得我們效仿,在團隊內部每個服務的開發者如果都能提供一個服務的簡化版實現,那么對于該服務調用者來說,它的單測就會變得十分容易。

1. 單元測試的難點:外部協作者(external collaborators)的存在

單元測試是軟件開發的一個重要部分,它有助于在開發周期的早期發現錯誤,幫助開發人員增加對生產代碼正常工作的信心,同時也有助于改善代碼設計。**Go語言從誕生那天起就內置Testing框架(以及測試覆蓋率計算工具)**,基于該框架,Gopher們可以非常方便地為自己設計實現的package編寫測試代碼。

注:《Go語言精進之路》vol2[1]中的第40條到第44條有關于Go包內、包外測試區別、測試代碼組織、表驅動測試、管理外部測試數據等內容的系統地講解,感興趣的童鞋可以讀讀。

不過即便如此,在實際開發工作中,大家發現單元測試的覆蓋率依舊很低,究其原因,排除那些對測試代碼不作要求的組織,剩下的無非就是代碼設計不佳,使得代碼不易測;或是代碼有外部協作者(比如數據庫、redis、其他服務等)。代碼不易測可以通過重構來改善,但如果代碼有外部協作者,我們該如何對代碼進行測試呢,這也是各種編程語言實施單元測試的一大共同難點。

為此,《xUnit Test Patterns : Refactoring Test Code》[2]一書中提供了**Test Double(測試替身)**的概念專為解決此難題。那么什么是Test Double呢?我們接下來就來簡單介紹一下Test Double的概念以及常見的種類。

2. 什么是Test Double?

測試替身是在測試階段用來替代被測系統依賴的真實組件的對象或程序(如下圖),以方便測試,這些真實組件或程序即是外部協作者(external collaborators)。這些外部協作者在測試環境下通常很難獲取或與之交互。測試替身可以使開發人員或QA專業人員專注于新的代碼而不是代碼與環境集成。

圖片

測試替身是通用術語,指的是不同類型的替換對象或程序。目前xUnit Patterns[3]至少定義了五種類型的Test Doubles:

  • Test stubs
  • Mock objects
  • Test spies
  • Fake objects
  • Dummy objects

這其中最為常用的是Fake objects、stub和mock objects。下面逐一說說這三種test double:

2.1 fake object

fake object最容易理解,它是被測系統SUT(System Under Test)依賴的外部協作者的“替身”,和真實的外部協作者相比,fake object外部行為表現與真實組件幾乎是一致的,但更簡單也更易于使用,實現更輕量,僅用于滿足測試需求即可。

fake object也是Go testing中最為常用的一類fake object。以Go的標準庫為例,我們在src/database/sql下面就看到了Go標準庫為進行sql包測試而實現的一個database driver:

// $GOROOT/src/database/fakedb_test.go

var fdriver driver.Driver = &fakeDriver{}

func init() {
    Register("test", fdriver)
}

我們知道一個真實的sql數據庫的代碼量可是數以百萬計的,這里不可能實現一個生產級的真實SQL數據庫,從fakedb_test.go源文件的注釋我們也可以看到,這個fakeDriver僅僅是用于testing,它是一個實現了driver.Driver接口的、支持少數幾個DDL(create)、DML(insert)和DQL(selet)的toy版的純內存數據庫:

// fakeDriver is a fake database that implements Go's driver.Driver
// interface, just for testing.
//
// It speaks a query language that's semantically similar to but
// syntactically different and simpler than SQL.  The syntax is as
// follows:
//
//  WIPE
//  CREATE|<tablename>|<col>=<type>,<col>=<type>,...
//    where types are: "string", [u]int{8,16,32,64}, "bool"
//  INSERT|<tablename>|col=val,col2=val2,col3=?
//  SELECT|<tablename>|projectcol1,projectcol2|filtercol=?,filtercol2=?
//  SELECT|<tablename>|projectcol1,projectcol2|filtercol=?param1,filtercol2=?param2

與此類似的,Go標準庫中還有net/dnsclient_unix_test.go中的fakeDNSServer等。此外,Go標準庫中一些以mock做前綴命名的變量、類型等其實質上是fake object。

我們再來看第二種test double: stub。

2.2 stub

stub顯然也是一個在測試階段專用的、用來替代真實外部協作者與SUT進行交互的對象。與fake object稍有不同的是,stub是一個內置了預期值/響應值且可以在多個測試間復用的替身object。

stub可以理解為一種fake object的特例。

注:fakeDriver在sql_test.go中的不同測試場景中時而是fake object,時而是stub(見sql_test.go中的newTestDBConnector函數)。

Go標準庫中的net/http/httptest就是一個提供創建stub的典型的測試輔助包,十分適合對http.Handler進行測試,這樣我們無需真正啟動一個http server。下面就是基于httptest的一個測試例子:

// 被測對象 client.go

package main

import (
 "bytes"
 "net/http"
)

// Function that uses the client to make a request and parse the response
func GetResponse(client *http.Client, url string) (string, error) {
 req, err := http.NewRequest("GET", url, nil)
 if err != nil {
  return "", err
 }

 resp, err := client.Do(req)
 if err != nil {
  return "", err
 }
 defer resp.Body.Close()

 buf := new(bytes.Buffer)
 _, err = buf.ReadFrom(resp.Body)
 if err != nil {
  return "", err
 }

 return buf.String(), nil
}

// 測試代碼 client_test.go

package main

import (
 "net/http"
 "net/http/httptest"
 "testing"
)

func TestClient(t *testing.T) {
 // Create a new test server with a handler that returns a specific response
 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  w.WriteHeader(http.StatusOK)
  w.Write([]byte(`{"message": "Hello, world!"}`))
 }))
 defer server.Close()

 // Create a new client that uses the test server
 client := server.Client()

 // Call the function that uses the client
 message, err := GetResponse(client, server.URL)

 // Check that the response is correct
 expected := `{"message": "Hello, world!"}`
 if message != expected {
  t.Errorf("Expected response %q, but got %q", expected, message)
 }

 // Check that no errors were returned
 if err != nil {
  t.Errorf("Unexpected error: %v", err)
 }
}

在這個例子中,我們要測試一個名為GetResponse的函數,該函數通過client向url發送Get請求,并將收到的響應內容讀取出來并返回。為了測試這個函數,我們需要“建立”一個與GetResponse進行協作的外部http server,這里我們使用的就是httptest包。我們通過httptest.NewServer建立這個server,該server預置了一個返回特定響應的HTTP handler。我們通過該server得到client和對應的url參數后,將其傳給被測目標GetResponse,并將其返回的結果與預期作比較來完成這個測試。注意,我們在測試結束后使用defer server.Close()來關閉測試服務器,以確保該服務器不會在測試結束后繼續運行。

httptest還常用來做http.Handler的測試,比如下面這個例子:

// handler.go

package main
  
import (
    "bytes"
    "io"
    "net/http"
)

func AddHelloPrefix(w http.ResponseWriter, r *http.Request) {
    b, err := io.ReadAll(r.Body)
    if err != nil {
        w.WriteHeader(http.StatusBadRequest)
        return
    }
    w.Write(bytes.Join([][]byte{[]byte("hello, "), b}, nil))
    w.WriteHeader(http.StatusOK)
}

// handler_test.go

package main
  
import (
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"
)

func TestHandler(t *testing.T) {
    r := strings.NewReader("world!")
    req, err := http.NewRequest("GET", "/test", r)
    if err != nil {
        t.Fatal(err)
    }

    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(AddHelloPrefix)
    handler.ServeHTTP(rr, req)

    if status := rr.Code; status != http.StatusOK {
        t.Errorf("handler returned wrong status code: got %v want %v",
            status, http.StatusOK)
    }

    expected := "hello, world!"
    if rr.Body.String() != expected {
        t.Errorf("handler returned unexpected body: got %v want %v",
            rr.Body.String(), expected)
    }
}

在這個例子中,我們創建一個新的http.Request對象,用于向/test路徑發出GET請求。然后我們創建一個新的httptest.ResponseRecorder對象來捕獲服務器的響應。 我們定義一個簡單的HTTP Handler(被測函數): AddHelloPrefix,該Handler會在請求的內容之前加上"hello, "并返回200 OK狀態代碼作為響應體。之后,我們在handler上調用ServeHTTP方法,傳入httptest.ResponseRecorder和http.Request對象,這會將請求“發送”到處理程序并捕獲響應。最后,我們使用標準的Go測試包來檢查響應是否具有預期的狀態碼和正文。

在這個例子中,我們利用net/http/httptest創建了一個測試服務器“替身”,并向其“發送”間接預置信息的請求以測試Go中的HTTP handler。這個過程中其實并沒有任何網絡通信,也沒有http協議打包和解包的過程,我們也不關心http通信,那是Go net/http包的事情,我們只care我們的Handler是否能按邏輯運行。

fake object與stub的優缺點基本一樣。多數情況下,大家也無需將這二者劃分的很清晰。

2.3 mock object

和fake/stub一樣,mock object也是一個測試替身。通過上面的例子我們看到fake建立困難(比如創建一個近2千行代碼的fakeDriver),但使用簡單。而mock object則是一種建立簡單,使用簡單程度因被測目標與外部協作者交互復雜程度而異的test double,我們看一下下面這個例子:

// db.go 被測目標

package main

// Define the `Database` interface
type Database interface {
    Save(data string) error
    Get(id int) (string, error)
}

// Example functions that use the `Database` interface
func saveData(db Database, data string) error {
    return db.Save(data)
}

func getData(db Database, id int) (string, error) {
    return db.Get(id)
}

// 測試代碼

package main

import (
 "testing"

 "github.com/stretchr/testify/assert"
 "github.com/stretchr/testify/mock"
)

// Define a mock struct that implements the `Database` interface
type MockDatabase struct {
 mock.Mock
}

func (m *MockDatabase) Save(data string) error {
 args := m.Called(data)
 return args.Error(0)
}

func (m *MockDatabase) Get(id int) (string, error) {
 args := m.Called(id)
 return args.String(0), args.Error(1)
}

func TestSaveData(t *testing.T) {
 // Create a new mock database
 db := new(MockDatabase)

 // Expect the `Save` method to be called with "test data"
 db.On("Save", "test data").Return(nil)

 // Call the code that uses the database
 err := saveData(db, "test data")

 // Assert that the `Save` method was called with the correct argument
 db.AssertCalled(t, "Save", "test data")

 // Assert that no errors were returned
 assert.NoError(t, err)
}

func TestGetData(t *testing.T) {
 // Create a new mock database
 db := new(MockDatabase)

 // Expect the `Get` method to be called with ID 123 and return "test data"
 db.On("Get", 123).Return("test data", nil)

 // Call the code that uses the database
 data, err := getData(db, 123)

 // Assert that the `Get` method was called with the correct argument
 db.AssertCalled(t, "Get", 123)

 // Assert that the correct data was returned
 assert.Equal(t, "test data", data)

 // Assert that no errors were returned
 assert.NoError(t, err)
}

在這個例子中,被測目標是兩個接受Database接口類型參數的函數:saveData和getData。顯然在單元測試階段,我們不能真正為這兩個函數傳入真實的Database實例去測試。

這里,我們沒有使用fake object,而是定義了一個mock object:MockDatabase,該類型實現了Database接口。然后我們定義了兩個測試函數,TestSaveData和TestGetData,它們分別使用MockDatabase實例來測試saveData和getData函數。

在每個測試函數中,我們對MockDatabase實例進行設置,包括期待特定參數的方法調用,然后調用使用該數據庫的代碼(即被測目標函數saveData和getData)。然后我們使用github.com/stretchr/testify中的assert包,對代碼的預期行為進行斷言。

注:除了上述測試中使用的AssertCalled方法外,MockDatabase結構還提供了其他方法來斷言方法被調用的次數、方法被調用的順序等。請查看github.com/stretchr/testify/mock包的文檔,了解更多信息。

3. Test Double有多種,選哪個呢?

從mock object的例子來看,測試代碼的核心就是mock object的構建與mock object的方法的參數和返回結果的設置,相較于fake object的簡單直接,mock object在使用上較為難于理解。而且對Go語言來說,mock object要與接口類型聯合使用,如果被測目標的參數是非接口類型,mock object便“無從下嘴”了。此外,mock object使用難易程度與被測目標與外部協作者的交互復雜度相關。像上面這個例子,建立mock object就比較簡單。但對于一些復雜的函數,當存在多個外部協作者且與每個協作者都有多次交互的情況下,建立和設置mock object就將變得困難并更加難于理解。

mock object僅是滿足了被測目標對依賴的外部協作者的調用需求,比如設置不同參數傳入下的不同返回值,但mock object并未真實處理被測目標傳入的參數,這會降低測試的可信度以及開發人員對代碼正確性的信心。

此外,如果被測函數的輸入輸出未發生變化,但內部邏輯發生了變化,比如調用的外部協作者的方法參數、調用次數等,使用mock object的測試代碼也需要一并更新維護。

而通過上面的fakeDriver、fakeDNSSever以及httptest應用的例子,我們看到:作為test double,fake object/stub有如下優點:

  • 我們與fake object的交互方式與與真實外部協作者交互的方式相同,這讓其顯得更簡單,更容易使用,也降低了測試的復雜性;
  • fake objet的行為更像真正的協作者,可以給開發人員更多的信心;
  • 當真實協作者更新時,我們不需要更新使用fake object時設置的expection和結果驗證條件,因此,使用fake object時,重構代碼往往比使用其他test double更容易。

不過fake object也有自己的不足之處,比如:

  • fake object的創建和維護可能很費時,就像上面的fakeDriver,源碼有近2k行;
  • fake object可能無法提供與真實組件相同的功能覆蓋水平,這與fake object的提供方式有關。
  • fake object的實現需要維護,每當真正的協作者更新時,都必須更新fake object。

綜上,測試的主要意義是保證SUT代碼的正確性,讓開發人員對自己編寫的代碼更有信心,從這個角度來看,我們在單測時應首選為外部協作者提供fake object以滿足測試需要。

4. fake object的實現和獲取方法

隨著技術的進步,fake object的實現和獲取日益容易。

我們可以借助類似ChatGPT/copilot的工具快速構建出一個fake object,即便是幾百行代碼的fake object的實現也很容易。

如果要更高的可信度和更高的功能覆蓋水平,我們還可以借助docker來構建“真實版/無閹割版”的fake object。

借助github上開源的testcontainers-go[4]可以更為簡便的構建出一個fake object,并且testcontainer提供了常見的外部協作者的封裝實現,比如:MySQL、Redis、Postgres等。

以測試redis client為例,我們使用testcontainer建立如下測試代碼:

// redis_test.go

package main

import (
 "context"
 "fmt"
 "testing"

 "github.com/go-redis/redis/v8"
 "github.com/testcontainers/testcontainers-go"
 "github.com/testcontainers/testcontainers-go/wait"
)

func TestRedisClient(t *testing.T) {
 // Create a Redis container with a random port and wait for it to start
 req := testcontainers.ContainerRequest{
  Image:        "redis:latest",
  ExposedPorts: []string{"6379/tcp"},
  WaitingFor:   wait.ForLog("Ready to accept connections"),
 }
 ctx := context.Background()
 redisC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
  ContainerRequest: req,
  Started:          true,
 })
 if err != nil {
  t.Fatalf("Failed to start Redis container: %v", err)
 }
 defer redisC.Terminate(ctx)

 // Get the Redis container's host and port
 redisHost, err := redisC.Host(ctx)
 if err != nil {
  t.Fatalf("Failed to get Redis container's host: %v", err)
 }
 redisPort, err := redisC.MappedPort(ctx, "6379/tcp")
 if err != nil {
  t.Fatalf("Failed to get Redis container's port: %v", err)
 }

 // Create a Redis client and perform some operations
 client := redis.NewClient(&redis.Options{
  Addr: fmt.Sprintf("%s:%s", redisHost, redisPort.Port()),
 })
 defer client.Close()

 err = client.Set(ctx, "key", "value", 0).Err()
 if err != nil {
  t.Fatalf("Failed to set key: %v", err)
 }

 val, err := client.Get(ctx, "key").Result()
 if err != nil {
  t.Fatalf("Failed to get key: %v", err)
 }

 if val != "value" {
  t.Errorf("Expected value %q, but got %q", "value", val)
 }
}

運行該測試將看到類似如下結果:

$go test
2023/04/15 16:18:20 github.com/testcontainers/testcontainers-go - Connected to docker: 
  Server Version: 20.10.8
  API Version: 1.41
  Operating System: Ubuntu 20.04.3 LTS
  Total Memory: 10632 MB
2023/04/15 16:18:21 Failed to get image auth for docker.io. Setting empty credentials for the image: docker.io/testcontainers/ryuk:0.3.4. Error is:credentials not found in native keychain

2023/04/15 16:19:06 Starting container id: 0d8341b2270e image: docker.io/testcontainers/ryuk:0.3.4
2023/04/15 16:19:10 Waiting for container id 0d8341b2270e image: docker.io/testcontainers/ryuk:0.3.4
2023/04/15 16:19:10 Container is ready id: 0d8341b2270e image: docker.io/testcontainers/ryuk:0.3.4
2023/04/15 16:19:28 Starting container id: 999cf02b5a82 image: redis:latest
2023/04/15 16:19:30 Waiting for container id 999cf02b5a82 image: redis:latest
2023/04/15 16:19:30 Container is ready id: 999cf02b5a82 image: redis:latest
PASS
ok   demo 73.262s

我們看到建立這種真實版的“fake object”的一大不足就是依賴網絡下載container image且耗時過長,在單元測試階段使用還是要謹慎一些。testcontainer更多也會被用在集成測試或冒煙測試上。

一些開源項目,比如etcd,也提供了用于測試的自身簡化版的實現(embed)[5]。這一點也值得我們效仿,在團隊內部每個服務的開發者如果都能提供一個服務的簡化版實現,那么對于該服務調用者來說,它的單測就會變得十分容易。

5. 參考資料

  • 《xUnit Test Patterns : Refactoring Test Code》- https://book.douban.com/subject/1859393/
  • Test Double Patterns - http://xunitpatterns.com/Test%20Double%20Patterns.html
  • The Unit in Unit Testing - https://www.infoq.com/articles/unit-testing-approach/
  • Test Doubles — Fakes, Mocks and Stubs - https://blog.pragmatists.com/test-doubles-fakes-mocks-and-stubs-1a7491dfa3da


本文轉載自微信公眾號「TonyBai 」,可以通過以下二維碼關注。轉載本文請聯系

公眾號。

責任編輯:武曉燕 來源: TonyBai
相關推薦

2024-01-19 08:25:38

死鎖Java通信

2023-01-10 08:43:15

定義DDD架構

2024-02-04 00:00:00

Effect數據組件

2023-07-26 13:11:21

ChatGPT平臺工具

2024-01-02 12:05:26

Java并發編程

2023-08-01 12:51:18

WebGPT機器學習模型

2023-01-30 09:01:54

圖表指南圖形化

2022-07-08 09:27:48

CSSIFC模型

2024-08-06 09:47:57

2023-10-10 11:04:11

Rust難點內存

2024-07-31 08:39:45

Git命令暫存區

2023-12-12 08:02:10

2024-05-06 00:00:00

InnoDBView隔離

2023-08-26 21:34:28

Spring源碼自定義

2022-07-13 08:16:49

RocketMQRPC日志

2023-03-26 22:31:29

2023-07-30 22:29:51

BDDMockitoAssert測試

2023-01-31 08:02:18

2024-02-02 11:03:11

React數據Ref

2023-10-06 14:49:21

SentinelHystrixtimeout
點贊
收藏

51CTO技術棧公眾號

日韩中文字幕二区| 精品乱色一区二区中文字幕| 亚洲国产精品一区二区久久hs| 深夜日韩欧美| 午夜精品久久久久久久| 日韩欧美亚洲日产国| 国产情侣av在线| 国产欧美日韩一级| zzjj国产精品一区二区| 色综合久久五月| 日韩国产大片| 欧美性xxxx18| 欧美日韩dvd| 成人欧美亚洲| 99麻豆久久久国产精品免费优播| 国产精品欧美日韩一区二区| 免费中文字幕视频| 国产一二三四区在线| 97天天综合网| 中文字幕亚洲综合久久菠萝蜜| 国产伦精品一区二区三区视频黑人| 婷婷激情五月综合| 日韩一级大片| 欧美国产高跟鞋裸体秀xxxhd| 欧美18—19性高清hd4k| 国产精伦一区二区三区| 欧美一级在线观看| 少妇一级淫免费放| 裤袜国产欧美精品一区| 香蕉乱码成人久久天堂爱免费| 亚洲自拍偷拍一区二区三区| 成年在线观看免费人视频| 99热在这里有精品免费| 成人午夜影院在线观看| 国产视频一区二区三区四区五区| 日本中文一区二区三区| 日本亚洲精品在线观看| 国产情侣在线视频| 激情欧美丁香| 久久久久久久久久亚洲| 久草视频在线资源| 欧美国产免费| 欧美理论片在线观看| 国产麻豆a毛片| 日韩在线不卡| 中文字幕亚洲色图| 国产7777777| 日韩精品一卡| 日韩有码在线电影| 后入内射无码人妻一区| 欧美成免费一区二区视频| 中文字幕v亚洲ⅴv天堂| 极品蜜桃臀肥臀-x88av| av伊人久久| 中文字幕日韩免费视频| 中文字幕欧美激情极品| 日韩片欧美片| 久久久97精品| 国产精品白嫩白嫩大学美女| 在线观看日韩| 欧美激情在线观看| 亚洲国产成人精品激情在线| 国产欧美日本| 国产精品爱啪在线线免费观看| 成人a v视频| 蜜桃一区二区三区四区| 91久久久在线| 亚洲精品久久久久久久久久| 成人午夜av影视| 国产日韩欧美一区二区| 亚州视频一区二区三区| 日本一区二区三区国色天香| 先锋在线资源一区二区三区| 免费大片黄在线观看视频网站| 一色屋精品亚洲香蕉网站| 懂色av一区二区三区四区五区| 99热国产在线中文| 亚洲成人一二三| 欧美 日韩精品| 久久精品超碰| 日韩精品一区二区三区视频播放| youjizz.com国产| 亚洲欧美校园春色| xxav国产精品美女主播| 麻豆成人在线视频| 嫩草成人www欧美| 成人激情视频在线| www.黄色国产| 久久久91精品国产一区二区精品| 亚洲伊人婷婷| 成人免费一区二区三区牛牛| 色诱亚洲精品久久久久久| 中文字幕在线观看日| 国产成人av毛片| 中文字幕欧美亚洲| 自拍偷拍欧美亚洲| 国产中文一区二区三区| 久中文字幕一区| 国产色在线观看| 日本精品免费观看高清观看| 久草福利在线观看| 国产亚洲精品美女久久久久久久久久| 草民午夜欧美限制a级福利片| 西西44rtwww国产精品| 精品在线观看免费| 久久久亚洲综合网站| 国产美女av在线| 在线观看免费视频综合| 日本精品一二三区| 97精品视频在线看| 欧洲成人性视频| 亚洲AV无码成人片在线观看 | 99久久精品免费观看国产| 欧美三级xxx| 两性午夜免费视频| 成人羞羞网站| 91成品人片a无限观看| 国产伦精品一区二区三区视频痴汉| av午夜精品一区二区三区| 宅男噜噜99国产精品观看免费| 女海盗2成人h版中文字幕| 欧美一卡2卡三卡4卡5免费| 精品无码人妻一区二区免费蜜桃| 在线日韩电影| 91在线看网站| 国产高清一区二区三区视频| 在线亚洲人成电影网站色www| 欧美xxxxx精品| 欧美精品自拍| 4444kk亚洲人成电影在线| caoporn国产精品免费视频| 欧美色播在线播放| 欧美xxxxx精品| 亚洲黄色大片| 精品国产一区二区三| 国产后进白嫩翘臀在线观看视频| 制服丝袜中文字幕亚洲| 任你操精品视频| 日韩高清中文字幕一区| 欧美中日韩一区二区三区| 涩涩视频网站在线观看| 亚洲精品美女在线观看播放| 久久亚洲av午夜福利精品一区| 国产美女一区二区| 国产三级中文字幕| 亚洲精品无播放器在线播放| 色婷婷久久一区二区| 中文在线最新版天堂| 国产精品嫩草久久久久| 自拍偷拍一区二区三区四区| 日韩精品欧美| 91嫩草在线视频| 制服丝袜中文字幕在线| 欧美一级爆毛片| 国产 日韩 欧美 成人| 成人午夜激情影院| 男人添女人下面高潮视频| 小说区图片区色综合区| 欧美中文在线观看国产| 国产小视频在线| 欧美日韩精品一区二区三区四区 | 欧洲一级在线观看| 日本久久一区二区三区| 三级黄色片在线观看| 国产中文一区二区三区| 国产一区二区三区乱码| 久久亚洲道色| 国产精品久久久久久久久| 超碰在线国产| 日韩欧美一区二区久久婷婷| 久久精品国产av一区二区三区| 99国产精品一区| 青青草av网站| 正在播放日韩欧美一页 | 日本黄色大片视频| 日韩欧美亚洲成人| 国产欧美一区二区三区在线观看视频| 久久精品国产亚洲一区二区三区| 少妇高潮大叫好爽喷水| 大奶一区二区三区| 国产精品aaaa| √天堂8在线网| 日韩激情视频在线| 一级爱爱免费视频| 午夜久久电影网| 黄免费在线观看| 国产成人在线网站| 男人日女人bb视频| 91精品国产91久久久久久密臀| 国产精品久久久久久免费观看| 成人国产二区| 欧美精品生活片| 男人天堂网在线| 日韩一级片在线观看| 亚洲精品男人的天堂| 亚洲欧美国产77777| 无码人妻精品一区二区三应用大全| 麻豆一区二区99久久久久| 国产 欧美 日韩 一区| 精品国产一区二区三区久久久樱花| 成人免费xxxxx在线观看| 国产中文在线播放| 欧美xxxx18性欧美| 超碰在线国产| 日韩精品中文字幕在线播放| 亚洲一区二区视频在线播放| 精品久久久视频| 亚洲成人生活片| 日本一区二区免费在线观看视频| 美女流白浆视频| 裸体一区二区三区| 成年人视频在线免费| 在线看片日韩| 国产一区二区三区在线免费| 日韩理论电影| 欧美一级二级三级九九九| 国产精品调教视频| 亚洲自拍偷拍色图| 日本黄色成人| 国产精品99久久99久久久二8| 麻豆mv在线看| 欧美裸体男粗大视频在线观看| jyzzz在线观看视频| 亚洲欧美日韩久久久久久| 成人午夜免费在线观看| 69久久夜色精品国产69蝌蚪网| 日本免费精品视频| 一本在线高清不卡dvd| 国产成人免费观看视频| 亚洲图片一区二区| 久艹视频在线观看| 一区二区三区高清在线| 国产探花在线免费观看| 亚洲免费在线电影| 五月天av网站| 亚洲久草在线视频| 欧美特级一级片| 亚洲欧美经典视频| 成年人av电影| 一区二区三区欧美在线观看| 岛国毛片在线观看| 一区二区三区在线观看视频| 99久久99久久精品国产| 综合久久久久久| 成人免费毛片xxx| 亚洲色图.com| 欧美日韩激情在线观看| 亚洲综合av网| 日本在线观看视频网站| 午夜欧美在线一二页| 亚洲 欧美 视频| 精品毛片网大全| 国产一级免费视频| 在线看国产一区| 88av在线视频| 欧美一级搡bbbb搡bbbb| 成人av手机在线| 亚洲成人久久电影| 欧美美乳在线| 中文字幕在线观看日韩| 成人免费在线| 久久久久久久久国产精品| av影院在线免费观看| 久久久人成影片一区二区三区| 麻豆mv在线观看| 国产精品久久色| 精品一区二区三区视频在线播放| 99www免费人成精品| 欧美人成在线观看ccc36| 日韩中文一区| 女同性一区二区三区人了人一| 免费的一级黄色片| 久久婷婷麻豆| 天堂av.com| av一区二区不卡| 色欲狠狠躁天天躁无码中文字幕 | 日本欧美在线| 99高清视频有精品视频| 亚洲成人一品| 在线国产精品网| 影音先锋中文字幕一区| 精品久久久久久中文字幕2017| 激情综合亚洲精品| 久久人人妻人人人人妻性色av| 国产亚洲精品aa午夜观看| 久久国产美女视频| 一本高清dvd不卡在线观看| 91午夜交换视频| 日韩成人av在线播放| 日韩免费网站| 欧美亚洲在线视频| 国产精品美女久久久久| 蜜桃精品久久久久久久免费影院| 国产高清欧美| 无遮挡又爽又刺激的视频| 国产精品亚洲一区二区三区在线| 久久久久亚洲AV成人无码国产| 欧美激情一区二区在线| 国产在线免费视频| 精品1区2区3区| 人妻偷人精品一区二区三区| 中文字幕av一区二区| 国产高清自产拍av在线| 91牛牛免费视频| 日韩电影免费网址| 欧美视频在线播放一区| 国产伦精品一区二区三区视频青涩| 加勒比一区二区| 一区二区国产盗摄色噜噜| 91成人在线免费| 亚洲视频第一页| 麻豆视频在线看| www 成人av com| 天堂网在线观看国产精品| 无码人妻h动漫| 白白色亚洲国产精品| 手机在线免费看片| 欧美色爱综合网| 国产精品久久一区二区三区不卡 | 99r精品视频| 久操视频免费在线观看| 7799精品视频| 在线视频婷婷| 国产精品视频在线播放| 国产成人三级| 日韩欧美xxxx| 久久综合成人精品亚洲另类欧美| 久久精品国产av一区二区三区| 欧美一级专区免费大片| 黄色网址在线免费播放| 国产精品美女在线观看| 欧美丝袜激情| 波多结衣在线观看| 日本少妇做爰全过程毛片| 日韩一区中文字幕| 在线观看中文字幕2021| 亚洲社区在线观看| 深夜视频一区二区| 日本一区二区三区视频在线观看| av成人激情| 97人妻天天摸天天爽天天| 精品成人av一区| 三级av在线| 全亚洲最色的网站在线观看| 婷婷激情久久| 国产精品wwwww| 欧美高清一级片在线观看| 中文字幕一区二区人妻| 中文字幕精品www乱入免费视频| 国产综合色在线观看| 亚洲韩国在线| 国产一区二区三区免费在线观看| 91n在线视频| 4438亚洲最大| 日韩三级免费| 久久精品午夜一区二区福利| 午夜一区在线| 国产精品国产三级国产专业不 | 欧美日本亚洲视频| 亚洲精品一区在线| 久久黄色片视频| 91视频观看视频| wwwwww在线观看| 久久精品视频在线观看| 伊人久久亚洲| 欧美精品色婷婷五月综合| 欧美激情综合在线| 国产三级自拍视频| 国内精久久久久久久久久人| 亚洲瘦老头同性70tv| 成人性生交免费看| 亚洲精品菠萝久久久久久久| 天堂av一区二区三区| 国产91免费观看| 91精品久久久久久久蜜月| 亚洲国产精品第一页| 色综合天天综合网国产成人综合天 | 开心久久婷婷综合中文字幕| 永久免费网站视频在线观看| caoporn国产精品| 超碰在线免费97| 欧美极品少妇与黑人| 精品国产一区二区三区小蝌蚪| 日韩va在线观看| 精品久久久香蕉免费精品视频| 97电影在线| 国产精选在线观看91| 日本欧美一区二区在线观看| 91日韩中文字幕| 亚洲性xxxx| 国产suv精品一区| 色一情一区二区三区| 五月婷婷另类国产| 免费在线午夜视频| 蜜桃av噜噜一区二区三| 国产一区二区不卡老阿姨| 中文字幕69页| 欧美激情免费视频| 日韩精品水蜜桃| av直播在线观看|