精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

一区二区欧美在线| 欧美专区亚洲专区| 久久久久久久久一区二区| 欧美三级韩国三级日本三斤在线观看 | 青青草在线视频免费观看| 青青草国产精品97视觉盛宴| 欧美成人精品在线播放| 久久精品成人av| 深夜福利一区二区三区| 日韩欧美在线免费观看| 老司机午夜网站| 黄色软件在线| 成人午夜精品一区二区三区| 国产精品久久久久久久久久久新郎 | 日韩精品一区二区三区免费观影 | 国产女人精品视频| 91视频免费网址| 正在播放日韩欧美一页| 亚洲小视频在线| 国产视频精品视频| 亚洲免费一区| 色94色欧美sute亚洲线路一久| 强开小嫩苞一区二区三区网站| 日本福利片在线| 国产sm精品调教视频网站| 国产欧美一区二区三区四区| 丰满少妇乱子伦精品看片| 欧美aa国产视频| 自拍偷拍免费精品| 国产肥白大熟妇bbbb视频| aiss精品大尺度系列| 欧美一区二区三区视频免费播放| 女人另类性混交zo| 看黄在线观看| 亚洲成av人片在线| 97超碰在线视| av观看在线| 亚洲欧美日韩国产手机在线| 亚洲精品日韩在线观看| 欧美色综合一区二区三区| 92国产精品观看| 国产一区免费在线| 女人18毛片一区二区三区| 韩国女主播成人在线| 成人综合网网址| 国产又粗又猛又黄又爽无遮挡| 日韩电影一区二区三区四区| 国产成人精品a视频一区www| 国内自拍视频在线播放| 欧美一级网站| 欧美伊久线香蕉线新在线| 国产福利拍拍拍| 亚洲精品乱码| 91成人精品网站| 国产精品视频久久久久久久| 亚洲制服少妇| 日本精品一区二区三区在线| 午夜婷婷在线观看| 久久国产99| 国产成人一区二区在线| 国产免费一区二区三区四区五区| 新67194成人永久网站| 全球成人中文在线| 国产91av在线播放| 狠狠色丁香久久婷婷综合_中| 国产日韩在线播放| 国产精品丝袜黑色高跟鞋| 国产老女人精品毛片久久| 99国产在线观看| 亚洲人妻一区二区三区| 国产人成亚洲第一网站在线播放| 亚洲精品一区二区毛豆| 黄色网在线免费观看| 亚洲一区在线看| 青青草视频在线免费播放 | 欧美二区视频| 亚洲97在线观看| 无码人妻一区二区三区免费| 蜜桃av一区二区在线观看| 91亚洲精华国产精华| 黄色片一区二区三区| 久久综合久久99| 亚洲一区三区视频在线观看| 男人添女人下部高潮视频在线观看 | 国产**成人网毛片九色| 欧美日本亚洲| 国产色在线观看| 天天综合网天天综合色| 国产成人黄色网址| 粉嫩av一区二区| 中文字幕日韩高清| 久久国产精品波多野结衣| 欧美中文日韩| 97超碰资源| 狠狠色伊人亚洲综合网站l| 亚洲欧美日韩在线不卡| 伊人成色综合网| 国产精品亚洲欧美一级在线| 亚洲精品国精品久久99热一| 自拍偷拍第9页| 亚洲视频1区| 91精品视频免费看| 欧美色综合一区二区三区| 一区二区三区在线观看网站| 国产又黄又猛视频| 九色精品蝌蚪| 在线a欧美视频| 日韩黄色三级视频| 国产一区高清在线| 日韩美女一区| 成入视频在线观看| 日韩三级av在线播放| 91资源在线播放| 亚洲一区成人| 国产99午夜精品一区二区三区| 高清美女视频一区| 午夜啪啪免费视频| 三年中文高清在线观看第6集 | 日韩高清欧美激情| av一本久道久久波多野结衣| 国产精品久久一区二区三区不卡 | 在线播放亚洲精品| 91亚洲男人天堂| 男人c女人视频| 91成人小视频| 色哟哟入口国产精品| 特级毛片www| 成人av免费网站| 精品久久久无码人妻字幂| 久久爱.com| 在线精品国产成人综合| 欧美一区二区三区网站| av网站免费线看精品| 久久精品在线免费视频| 亚洲精品aaa| 日韩在线观看你懂的| 在线观看中文字幕网站| 国产精品久久久一本精品| 9久久婷婷国产综合精品性色| 天海翼精品一区二区三区| 久久久中精品2020中文| 韩国av永久免费| 亚洲国产精品综合小说图片区| 成年人性生活视频| 欧美1区免费| 成人在线观看网址| 俺来也官网欧美久久精品| 欧美变态凌虐bdsm| 久久机热这里只有精品| 国产69精品久久久久毛片| 日韩精品在线视频免费观看| 久久婷婷国产| 91精品91久久久久久| 午夜在线视频免费| 欧美性色视频在线| 天堂久久精品忘忧草| 日韩高清中文字幕一区| 亚洲巨乳在线观看| 国产精品亚洲综合在线观看| 久久69精品久久久久久久电影好| 国产后入清纯学生妹| 亚洲成年人影院| 久久国产精品影院| 日本人妖一区二区| 一区二区三区四区不卡| 精品一区二区三区中文字幕视频| 欧美国产激情18| 无码国精品一区二区免费蜜桃| 欧美性色xo影院| 日本激情视频一区二区三区| 国产精品一区二区视频| 亚洲熟妇无码一区二区三区| 校花撩起jk露出白色内裤国产精品| 日本精品中文字幕| 黄网站在线免费看| 欧美精品一区二| 波多野结衣视频在线观看| 亚洲欧美aⅴ...| 一本加勒比波多野结衣| 热久久免费视频| 一级特黄妇女高潮| 天海翼亚洲一区二区三区| 国产精品一区二区三区在线播放| 污污网站在线观看| 亚洲欧美在线一区| 国产伦精品一区二区三区四区 | 在线观看xxxx| 亚洲一区中文日韩| 三年中国中文观看免费播放| 国产精品一区二区黑丝| 欧美视频第一区| 国产精品精品| 另类小说综合网| 国产精品一级在线观看| 日本精品一区二区三区在线播放视频 | 隔壁人妻偷人bd中字| av伊人久久| 狠狠色狠狠色综合人人| 在线观看欧美| 日本最新高清不卡中文字幕| 91精品国产91久久久久久青草| 精品视频一区在线视频| 国产色在线视频| 性做久久久久久久久| 可以直接看的无码av| 国产精品资源网站| 国产又大又硬又粗| 狠狠爱成人网| 综合操久久久| 精品色999| 国产精品久久九九| 国产美女视频一区二区| 国产精品9999| 在线观看福利电影| 久久免费高清视频| 91精品久久| 日韩三级影视基地| 国产高清美女一级毛片久久| 亚洲国产天堂久久综合| 午夜精品久久久久久久99热黄桃| 欧美日韩一区二区三区在线看 | 一级性生活免费视频| 久久久影院官网| 亚洲自拍偷拍精品| 国产成人免费视频| 欧美体内she精高潮| 久久99热狠狠色一区二区| 久久综合久久色| 久久久久久黄| 日韩欧美精品在线观看视频| 99热免费精品在线观看| 精品少妇在线视频| 国产精品大片| 国产精品www在线观看| 欧美二区不卡| 蜜臀精品一区二区| 国精品一区二区| 久久这里只有精品18| 激情视频一区| 日韩精品在线中文字幕| 亚洲午夜伦理| 成人在线播放网址| 好看的av在线不卡观看| av在线com| 亚洲免费精品| 无码人妻丰满熟妇区96| 国产精品日韩欧美一区| 欧美女人性生活视频| 免费视频久久| 99视频免费播放| 美美哒免费高清在线观看视频一区二区| 女人另类性混交zo| 免费人成在线不卡| 亚洲va在线va天堂va偷拍| 精品一区二区在线观看| 黄色片免费网址| 懂色av一区二区三区免费看| 四虎精品一区二区| 97久久精品人人澡人人爽| 插吧插吧综合网| 中文字幕av一区 二区| 黑人操日本美女| 亚洲综合成人在线| 日韩和一区二区| 在线观看中文字幕不卡| 91theporn国产在线观看| 欧美一区国产二区| 少妇高潮一区二区三区69| 亚洲男人天堂手机在线| 在线免费av网站| 欧美黑人又粗大| 伊人久久视频| 成人网在线观看| 欧美a大片欧美片| 亚洲一区二区三区免费观看| 国产精品草草| 国产又粗又长又大的视频| 国产乱人伦精品一区二区在线观看| 在线xxxxx| 日本一区二区视频在线| 欧美成人免费观看视频| 欧美午夜视频在线观看| 国产精品高潮呻吟AV无码| 亚洲第一色在线| 波多野结衣在线网站| 久久99精品久久久久久琪琪| 成人va天堂| 97夜夜澡人人双人人人喊| 九九热爱视频精品视频| 成人高清dvd| 日韩精品五月天| 任你躁av一区二区三区| 日本一区二区三级电影在线观看| 久久久久久久久久网站| 在线亚洲一区二区| 欧美一级一区二区三区| 最新中文字幕亚洲| 色一区二区三区| 91在线观看网站| 成人在线一区| 免费毛片小视频| 国产精品一区一区| 四虎国产成人精品免费一女五男| 五月天激情小说综合| 97精品人妻一区二区三区在线| 亚洲精品美女视频| 大地资源网3页在线观看| 国产成人一区二区三区小说| 激情av综合| 超碰10000| 久久爱www久久做| 人人人妻人人澡人人爽欧美一区| 亚洲国产精品麻豆| 精品国产乱码一区二区三| 一区二区三区国产视频| 一本大道色婷婷在线| 国产成人精品福利一区二区三区| 羞羞答答成人影院www| 国产av人人夜夜澡人人爽| 99久久综合国产精品| 久久久久久国产精品免费播放| 欧美精品aⅴ在线视频| 国产二区在线播放| 欧美在线视频免费播放| 欧美18免费视频| 97视频在线免费| 国产精品996| 久久久久亚洲av无码专区体验| 911精品国产一区二区在线| 成年人在线观看视频| 国产成人精品免高潮费视频| 亚洲成a人片77777在线播放| 欧美精品久久久久久久久久久| 国产xxx精品视频大全| 校园春色 亚洲| 日韩视频免费观看高清在线视频| 久热国产在线| 亚洲影院色无极综合| 欧美永久精品| 成人在线短视频| 亚洲欧美日韩一区二区| 国产视频在线一区| 欧美成人久久久| 亚洲一区二区三区中文字幕在线观看 | 视频一区中文字幕国产| 日本高清www| 欧美优质美女网站| 18免费在线视频| 国产在线视频一区| 91精品久久久久久久蜜月| 自拍一级黄色片| 亚洲午夜视频在线观看| 深爱激情五月婷婷| 欧美专区福利在线| 神马电影久久| 久久人人爽av| 一区二区免费看| 天堂国产一区二区三区| 日本成熟性欧美| 日韩电影二区| 又黄又爽又色的视频| 亚洲国产精品久久不卡毛片| 日色在线视频| 国产日产欧美a一级在线| 亚洲精品小说| av无码一区二区三区| 91高清视频免费看| 精品美女在线观看视频在线观看 | 444亚洲人体| 91久久黄色| 人人人妻人人澡人人爽欧美一区| 欧美色倩网站大全免费| 91国内在线| 你懂的视频在线一区二区| 欧美96一区二区免费视频| 看免费黄色录像| 亚洲精品suv精品一区二区| 欧美日韩亚洲国产| 色一情一乱一乱一区91| 99re成人精品视频| 91福利在线观看视频| 欧美精品videossex88| 国产成人精品免费视| 午夜影院免费观看视频| 欧美日韩亚洲精品内裤| 免费av网站在线看| 久久久久久99| 国产自产视频一区二区三区| 久久青青草视频| 久久久99久久精品女同性| 日韩成人av在线资源| 中文字幕成人免费视频| 精品高清美女精品国产区| 欧美边添边摸边做边爱免费| 国产日韩一区欧美| 久久国产综合精品| 久久露脸国语精品国产91| 久久久av一区| 国产一区二区三区四区五区传媒| 丰满少妇一区二区三区专区|