<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 56, comments - 77, trackbacks - 0, articles - 1
      BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

    敏捷質(zhì)疑: TDD

    Posted on 2008-07-13 21:40 切爾斯基 閱讀(4735) 評論(4)  編輯  收藏

    Q: 為什么通過單元測試發(fā)現(xiàn)的 Bug 很少 ?

    A: 單元測試不是用來發(fā)現(xiàn) Bug 的, 而是用來預(yù)防 Bug 的. 如果采用 TDD, 測試用例完成之時, 產(chǎn)品代碼尚未編寫, Bug更無從談起.

    Q: 那是否寫單元測試就能提高代碼質(zhì)量了 ?

    A: 關(guān)于這一點, 似乎有人不這么看, <<TDD Opinion: Quality Is a Function of Thought and Reflection, Not Bug Prevention>>. 不錯, 代碼質(zhì)量并不必然關(guān)聯(lián)到單元測試, 諸如凈室軟件開發(fā)之類的方法依然可以在沒有單元測試的情況下得到高質(zhì)量的代碼, 但這是另外一個問題. 或許主觀上, TDD的本質(zhì)更接近于促使你把質(zhì)量內(nèi)建在思維中, 但客觀上, 在其它條件都相同的情況下, 單元測試依然能夠起到預(yù)防 Bug 的作用.

    Q: 單元測試怎么能反映/代替需求 ?

    A: 單元測試未必能直接反映宏觀上的需求, 但

    1. 功能測試和集成測試能夠反映宏觀需求.

    2. 單元測試能夠反映系統(tǒng)的其它部分對當前單元的需求.

    而從文本的角度, 測試用例的名字就是需求的描述. 換句話說, 你從傳統(tǒng)的需求文檔中把描述摳出來, 放到測試代碼中作為測試用例的名字, 你便擁有了可執(zhí)行的需求文檔

    一個 RSpec 寫的功能測試用例 (不要懷疑, 它確實是可以運行的):

      it "should show welcome message after login" do

        login_as_chelsea

        get :index

        response.should have_text(/歡迎 chelsea/)

      end

      it "should not show welcome message after logout" do

        logout

        get :index

        response.should_not have_text(/歡迎/)

      end

    單元測試的例子:

        public void testShouldBeFreeFrom2amTo5am() throws Exception { //直接業(yè)務(wù)需求

            ...

        }

        public void testShouldThrowExceptionIfCannotFindConfigFile() throws Exception { //來自系統(tǒng)其它部分的需求

            ...

        }

    測試用例并不排斥業(yè)務(wù)層面的需求文檔, 一個高層的, 突出業(yè)務(wù)價值的需求/愿景描述對于快速理解系統(tǒng)是非常有幫助的, 但/只是測試用例以另一種方式描述了真實的系統(tǒng), 它具有兩個突出的優(yōu)點:

    1. 它不會說謊, 即永遠與系統(tǒng)真實的行為同步

    2. 它是可執(zhí)行的, 它可以不知疲倦的, 成本極低的, 時時刻刻, 反反復復的追問你的系統(tǒng)是否符合需求

    Q: 需求變了怎么辦? 豈不是有大量測試用例需要修改?

    A: 難道不是應(yīng)該的嗎? 難道以前的需求文檔在需求發(fā)生變化時不需要修改?  哦, 或許它們不需要, 因為沒人會關(guān)心, 對代碼也沒什么影響, 需求文檔在度過最初的幾周后便被扔在配置庫里再也沒人管它了.

    是的, 這關(guān)系到你的測試策略. 然而通常的測試策略對單元測試的要求都是盡可能周全. 于是這就是一個測試設(shè)計的問題. 是的,測試代碼也需要設(shè)計, 也需要重構(gòu), 也需要 Domain Specific.

    Q: 我的單元測試編譯鏈接速度很慢, 而且有些條件很難測, 比如內(nèi)存不足, 或者環(huán)境很難搭建, 比如需要網(wǎng)絡(luò)或數(shù)據(jù)庫, 怎么解決?

    A: 這是集成測試, 不是單元測試. 你一定把系統(tǒng)所有的組件都編譯鏈接起來了. 那么如果你的測試失敗了, 是哪一部分的問題呢?

    通常單元測試需要滿足一個條件: 不依賴任何其它單元, 即隔離性. 實現(xiàn)手段就是在測試環(huán)境中能夠輕易的假冒依賴, 并設(shè)定依賴按照我們的意愿進行工作. 一個例子就是你的代碼依賴 malloc 獲取內(nèi)存, 而你想測試內(nèi)存不足的情況. 那么我們應(yīng)在能夠/需要在單元測試中使用使用一個假冒的 malloc 來代替真正的 malloc, 并且我們能控制假冒的 malloc 返回 NULL 以模擬內(nèi)存不足的情況. 關(guān)于如何做到這一點, 可參考一些成熟的"假冒"框架, 如 mockcpp 等.

    Q: 我原來的測試都是用真實的代碼來跑, 一個測試能覆蓋多個單元. 你現(xiàn)在都把依賴替換掉了, 那被替換掉的模塊有問題怎么辦? 怎么保證集成真實的代碼后還能正確工作?

    A: 其它單元有其它單元自己的單元測試, 各自關(guān)注自己. 集成測試像以前一樣, 該怎么測還怎么測, 并不是有了單元測試就不要其它測試了.

    Q: 單元測試就是設(shè)計? 單元測試怎么能反映/代替設(shè)計 ?

    A: 單元測試反映的是局部的設(shè)計, 局限于本單元以及與之交互的其它單元. 前面說的單元測試能夠反映系統(tǒng)的其它部分對當前單元的需求, 所謂設(shè)計就是單元之間的職責劃分, 交互和依賴關(guān)系

    當你試圖測試一個單元時, 卻發(fā)現(xiàn)需要創(chuàng)建大量的其它對象, 而且按照你腦海中的實現(xiàn), 有些對象是在單元內(nèi)部創(chuàng)建的, 根本無法在測試環(huán)境中假冒它們. 這時候, 你即使只是為了減少測試的難度, 也會逼迫自己思考:

    1. 這個單元是否做了太多的事, 承擔了額外的職責, 違反了單一職責原則?

    2. 是否應(yīng)該把依賴讓外界設(shè)置進來, 而不是自己在內(nèi)部創(chuàng)建, 這樣測試時就能把依賴設(shè)置為假冒的實現(xiàn)?

    是的, 單元測試警示你思考一下自己的設(shè)計

    Q: 單元測試是設(shè)計, 還有人說源代碼是設(shè)計, 到底是測試是設(shè)計還是源代碼是設(shè)計?

    A: 這實際上是另外一種角度. 源代碼就是設(shè)計的論斷基于兩個假設(shè)

    1. 設(shè)計階段中工程師的工作產(chǎn)物, 也就是他的設(shè)計, 是應(yīng)該能夠在實施階段被不同的實施者嚴格并且?guī)缀跻荒R粯拥膶崿F(xiàn)

    2. 軟件開發(fā)人員也是工程師, 即軟件工程師

    如果我們認同這兩個假設(shè), 那么軟件工程師的什么產(chǎn)物能夠被嚴格并且重復實現(xiàn)的呢? 是你的Word形式的"設(shè)計"文檔嗎? 是CAD工具畫出的UML圖嗎? 都不是, 因為它們都不精確, 有無數(shù)種實現(xiàn)方式, 根本談不到嚴格, 不同的開發(fā)人員會有完全不同的實現(xiàn). 事實上, 只有源代碼,才能滿足這個約束. 這樣軟件的設(shè)計階段, 就是直到軟件工程師完成源代碼的那一刻, 而軟件的實施階段, 其實就只剩編譯和部署了. 跑題了.

    Q: 單元測試是需求"文檔", 單元測試又是設(shè)計"文檔", 它怎么能既是需求又是設(shè)計呢?

    A: 名字和斷言描述需求, 環(huán)境設(shè)置描述設(shè)計 ...

    Q: 既然單元測試描述的是需求, 它就應(yīng)該是黑盒測試了? 可單元測試不一直都被認為是白盒測試嗎?

    A: 黑白都是相對于你觀察的層次. 相對于其它從外部觀察"系統(tǒng)"行為, 不涉及源代碼的測試來說, 單元測試深入到內(nèi)部觀察盒子的行為, 所以是白盒. 而具體到每個單元測試用例, 依然在盡可能的從外部觀察"單元"的行為, 所以又是黑盒.

    Q: 但是你們常用的 Mock 技術(shù), 明顯把單元測試推向白盒的境地.

    A: 說來話長, 但可以先說結(jié)論: 基于狀態(tài)的測試 over 基于交互/行為的測試, 雖然右邊的也有巨大的價值, 但我們認為左邊的更穩(wěn)定和更富有對系統(tǒng)的洞察力

    基于狀態(tài)的測試描述的是需求, 基于交互行為的測試描述的是實現(xiàn). 相對于需求來說, 實現(xiàn)更易發(fā)生變化, 尤其在另外一種實踐"重構(gòu)"的沖擊下, 描述實現(xiàn)的測試將被修改的面目全非, 帶來相當?shù)姆倒ず途S護成本

    一種例外, 就是交互本身就是需求, 這時 mock 是合適的選擇. 一個杜撰的例子參見<<TDD: Tricky Driven Design 3, 方法>>中最后銀行API的例子

    而現(xiàn)實生活中, 存在一些情況, 雖然使用 mock 可能帶來后期的維護成本, 但它帶來的好處也是不可代替的. 比如對先期整體測試代碼的編碼量的降低. 這在 C/C++ 項目中尤其明顯:

    受限于C/C++的編譯模型, 使用常用的預(yù)處理期接入點和編譯鏈接接入點技術(shù)來接入 stub 實現(xiàn)時, 要小心維護頭文件的防衛(wèi)宏, 頭文件的名稱, 不同環(huán)境下構(gòu)建腳本的include路徑設(shè)置, 庫路徑設(shè)置等. 手工寫stub的方式變的及其繁瑣和容易出錯. 這時候, 一個易用的 mock 框架如 mockcpp 等將節(jié)省大量的編碼和先期維護工作

    而幾乎所有的mock框架, 都支持將 mock 對象退化為 stub, 如 mockcpp 中 mock 對象的 defaults() 設(shè)置, 或者 JMock 2 中的 Allowing . 事實上, 這是我推薦的 mock 使用方式: 通常情況下讓它退化為簡單的stub, 必要時才使用它強大的期待設(shè)置和驗證能力.

    通常單元測試有兩個公認的約束需要滿足:

    1. 隔離依賴.

    重申一遍結(jié)論就是: 在滿足單元測試的快和隔離依賴的前提下,

    1. 優(yōu)先選擇基于狀態(tài)的黑盒測試(可使用手寫stub或mock退化的stub)

    2. 除非交互和行為本身就是需求(可使用mock對象的全部特性)

    Q: 怎么測 private 函數(shù)?

    A: 把它變成 public 的.

    我是認真的. 如果發(fā)現(xiàn) private 函數(shù)無法簡單的通過某個public函數(shù)的測試來覆蓋而需要專門的測試, 意味著你的單元可能承擔了太多的職責, 應(yīng)該拆分到一個單獨的單元中, 并開放為 public 函數(shù).

    如果使用 C++, 在測試環(huán)境中 #define private public.

    如果使用 g++, 在測試環(huán)境中加入 -fno-access-control.

    Q: 類似 private, 一些意圖實現(xiàn)良好設(shè)計的語言特性, 如 static, sealed, final, 非虛函數(shù)等, 卻總是給代碼的易測試性帶來麻煩, 該如何取舍?

    A: 沒什么好辦法. 這些語言特性和測試的目的是相同的, 都是為提高代碼質(zhì)量, 減少出錯的可能, 雖殊途同歸, 但卻互相限制, 效果也不一樣.

    我認為工業(yè)界是時候嚴肅認真的考慮測試環(huán)境了, 最好在語言中內(nèi)建對測試的支持, 一些為產(chǎn)品環(huán)境設(shè)計的語言特性, 應(yīng)該在測試環(huán)境中關(guān)閉, 而在產(chǎn)品環(huán)境中生效. 其實之前很多編譯器都支持 Release 和 Debug 兩種環(huán)境, 也是從代碼質(zhì)量的方面考慮的. 現(xiàn)在毫無疑問證實單元測試比 Debug 更有效, 是時候與時俱進增加對 Test 的支持而逐漸罷黜對 Debug 的支持.

    在語言本身增加對測試的支持之前, 我們不得不想辦法在測試環(huán)境中繞過語言特性的限制, 尤其對遺留系統(tǒng), 代碼已經(jīng)存在的情況. 比如對于 C++ 中的 static 函數(shù), 可以將整個被測單元 #include, 或者 #define static 為空. 宏代表了一層間接, 在測試環(huán)境中, 這層間接是至關(guān)重要的. 其它方法可參考 <<Working Effectively with Legacy Code>>, <<假冒的藝術(shù)>>中的介紹.

    Q: 剛才提到了要支持"測試"而不是"Debug", 測試和Debug難道有什么矛盾嗎?

    A: 有. 如果你發(fā)現(xiàn)不得不 Debug, 就是測試粒度太粗, 步子邁的太大, 產(chǎn)品代碼過長等導致的, 甚至可能你卷入了過多的單元而破壞了測試的隔離性. Debug還是代碼邏輯不清, 行為難以斷言的表現(xiàn).  用測試幫你定位錯誤.

    Q: 我知道為遺留系統(tǒng)增加新特性是要先寫測試保證系統(tǒng)原來的行為, 可遺留代碼很龐大, 我甚至都不知道系統(tǒng)目前的行為, 怎么辦?

    A: 特征測試: 保持代碼行為的測試, 獲取當前運行結(jié)果, 來填充測試, 以獲取系統(tǒng)目前行為. 其實測試可以分為兩類: 試圖說明想要實現(xiàn)的目標, 或者試圖保持代碼中既有的行為; 在特性實現(xiàn)后, 前者會轉(zhuǎn)化為后者. 詳細信息請參見<<Working Effectively with Legacy Code>>

    Q: 有成熟的關(guān)于在遺留系統(tǒng)上實踐 TDD 或者單元測試的實踐嗎?

    A: 還是<<Working Effectively with Legacy Code>>, 或者<<在大型遺留系統(tǒng)基礎(chǔ)上運作重構(gòu)項目>>

    Q: 前面經(jīng)常說到 C++ 或其它面向?qū)ο笳Z言, 卻沒有提到 C, 那么過程式語言中如何應(yīng)用 TDD ? 有什么不一樣?

    A: 基本一樣, 并且在過程式語言中應(yīng)用 TDD, 可能會導出面向?qū)ο箫L格的設(shè)計. 比如如果直接調(diào)用某個函數(shù), 那么不得不通過編譯時替換或鏈接時替換來接入假的實現(xiàn). 這樣其實比較麻煩, 因此可能會促使你選用函數(shù)指針 ,以便方便的在測試環(huán)境中進行替換. 隨著時間的推移, 你會發(fā)現(xiàn)一組組概念相關(guān)的函數(shù)指針出現(xiàn)了, 那么把它們和它們操作的數(shù)據(jù)綁定在一起, 定義一個 struct, 就形成了一種對象風格. 當然這反而可能會令你的代碼更復雜, 這需要在實踐中取舍.

    也有可能在過程式語言中你覺得 TDD 對設(shè)計的促進不大, 而且測試用例也比較枯燥, 就是測個分支, 返回值什么的. 是的, 邏輯就隱藏在分支和返回值中, 如果習慣了過程式思維并不打算改變, TDD 對設(shè)計的影響則更多的體現(xiàn)在依賴管理上, 如頭文件和編譯單元的職責劃分. 如果把不同職責的函數(shù)混在一個編譯單元里面, 則很難實施鏈接替換等手段, 除非你選擇一個類似 mockcpp 的框架, 不需要鏈接替換.

    Q: 如果使用 TDD, 那么測試人員怎么安排? 是不是一開始就要進入項目組? 可那時還沒有產(chǎn)品代碼,測什么?

    A: 是, 是一開始就要進入項目組, 可不是因為 TDD.  是, 測試人員是一開始沒什么可測的, 可不代表就沒活干.

    TDD是一種開發(fā)方法, 是開發(fā)人員參與的活動. 其效果是以可執(zhí)行的形式文檔化你的需求, 迫使你分清職責隔離依賴以驅(qū)動你的設(shè)計, 編織安全網(wǎng)以扼殺Bug在搖籃狀態(tài)防止逃逸. 可傳統(tǒng)測試人員的活動是試圖找到已經(jīng)逃逸的Bug. 這兩種活動都是必要的, 而且毫不沖突, 互為補充.

    那么測試人員在新的特性還沒開發(fā)完成之前做什么呢? 除了提前寫測試用例, 無論是自動化的還是非自動化的, 而需要測試人員參加的一項重要活動, 就是參與特性驗收條件的制定. 之前經(jīng)常發(fā)生開發(fā)人員按照自己的理解去編碼, 測試人員按照自己的理解去測試, 直到開發(fā)完成, 測試過程中才發(fā)現(xiàn)理解的不一致, 開始產(chǎn)生爭執(zhí)并阻塞等待業(yè)務(wù)分析人員(如果幸運的話)或者行政主管(如果開發(fā)過程混亂的話)的仲裁. 解決辦法就是就在開始開發(fā)新特性前的一剎那, 由業(yè)務(wù)分析人員, 測試人員, 開發(fā)人員進行一次討論, 就驗收條件達成一致并形成記錄, 然后測試人員和開發(fā)人員分頭去寫測試和實現(xiàn).

    Q: 之前會有一個階段, 就是一組相關(guān)的特性開發(fā)完成后, 測試人員接手測試, 幾輪Bug修復過去后, 產(chǎn)品基本穩(wěn)定就可以發(fā)布了. 現(xiàn)在測試人員提前介入到每個迭代中, 針對單個特性進行測試, 那如何保證產(chǎn)品集成起來的質(zhì)量?

    A: 跟以前一樣, 該有那么個集成測試階段還得有那么個集成測試階段, 取決于產(chǎn)品當時的質(zhì)量狀態(tài). 并不是說有了迭代級別, 單個特性級別的測試就不需要發(fā)布級別的集成測試了, 兩者沒有任何矛盾.

    Q: 那么測試人員提前進入迭代有什么好處?

    A: 盡早發(fā)現(xiàn)問題, 降低修復錯誤的成本. 有幾種手段, 一是前面提到與業(yè)務(wù)人員和開發(fā)者一起討論驗收條件, 這樣就能防止理解偏差而導致的返工. 二是開發(fā)完成立即測試, 發(fā)現(xiàn)問題立即反饋, 這樣開發(fā)人員對代碼依然印象深刻,能快速定位和修復錯誤. 這樣流入最后集成測試階段的Bug就會少, 會縮短最后的集成測試時間, 保證產(chǎn)品更平穩(wěn)的發(fā)布.

    Q: 有時候后續(xù)的特性會影響前面的特性, 那么迭代過程中測試人員只測單個特性, 怎么保證以前的特性依然工作?

    A: 幾個手段. 測試盡量自動化, 以便能夠持續(xù)集成. 再就是做好依賴管理, 每當一個新特性完成, 就應(yīng)該能夠發(fā)現(xiàn)它影響的其它特性, 看看是否應(yīng)該補充一些集成測試.

    Q: 有時候開發(fā)人員完成一個特性時已接近迭代結(jié)束, 測試人員沒有時間進行充分測試, 怎么辦?

    A: 下個迭代測唄, 并且在計算開發(fā)速度時, 只應(yīng)該計算本迭代通過測試人員驗收的特性, 那些僅僅是開發(fā)人員完成, 沒有經(jīng)過測試人員充分測試的特性不計在內(nèi). 這種情況是不可避免的. 但我們能通過一些手段讓測試與開發(fā)更加同步, 盡量縮短滯后性, 包括讓測試人員與開發(fā)人員更緊密合作, 盡量讓測試用例自動化等.

    Q: 我還是覺得在開發(fā)迭代過程中, 測試人員的工作量不飽滿.

    A: 如果這不是您的感覺, 而是事實, 并且前面測試人員必須要做的工作也都做了, 還是不飽滿, 那么恭喜你, 可以省下一些測試人員, 去做別的事了. 但不推薦的是, 不要讓測試人員同時為兩個團隊工作. 這會大大增加溝通的成本. 你會經(jīng)常發(fā)現(xiàn), 當你的開發(fā)者想找測試人員協(xié)助時, 卻找不到人了, 于是你的團隊便被堵塞在那里. 而測試人員本身的Context切換也是痛苦的.

    Q: 你們說驗收測試應(yīng)該由客戶來編寫, 可在我們這里根本不可能.

    A: 驗收, 當然是由客戶來驗收, 這在理論上是毫無疑問的, 而且肯定在各行各業(yè)發(fā)生著. 只是具體到測試用例的編寫和執(zhí)行, 無論是自動化的還是非自動化的, 都需要掌握一定的技術(shù), 需要周密的思考, 需要專門的時間, 客戶可能無法同時滿足這幾個條件, 我們要盡力爭取, 爭取不到, 便只好通過更充分的交流來彌補越俎代庖的失真. 這時業(yè)務(wù)分析人員和測試人員要通力合作, 完成驗收測試的編寫.

    Q: 你們說你們之前的項目產(chǎn)品代碼和測試代碼的比例大約 1:3, 這不是平白增加了 3 倍的工作量嗎?

    A: 是增加了 3 倍的代碼量而不是工作量. 它節(jié)省了你幾十人做幾個月龐大的預(yù)先設(shè)計的工作量, 節(jié)省了你詳細設(shè)計每個模塊并為之編寫幾百頁詳設(shè)文檔的時間, 節(jié)省了無數(shù)不眠之夜通宵Debug的時間, 它節(jié)省了集成階段修復難以計數(shù)的Bug的工作量, 甚至它縮減了你產(chǎn)品代碼的數(shù)量, 大量的重復代碼被消除了, 大量過度設(shè)計的復雜代碼被廢除了, 你的代碼更易理解了, 添加新特性更容易了, 發(fā)現(xiàn)的Bug更易定位了, 以致于大大減少了長達數(shù)年的生命周期內(nèi)維護的工作量. 有點夸張了? 可這就是 TDD 和敏捷開發(fā)帶給我們的好處(如果你已經(jīng)實踐了)和vision(如果你還在觀望)

    Q: 我們也做單元測試, 但是是先寫產(chǎn)品代碼后寫測試的. 難道非得 TDD, 非得測試先行嗎?

    A: 沒什么事是非做不可的. 取決于你要什么. TDD 只是以可驗證的方式迫使你將質(zhì)量內(nèi)建在思維中, 長期的測試先行將歷練你思維的質(zhì)量. 而事后的單元測試只是惶恐的跟隨者.


    評論

    # re: 敏捷質(zhì)疑: TDD  回復  更多評論   

    2008-08-20 15:48 by rocket
    應(yīng)該再加一個:
    Q:UT是測試接口還是測試實現(xiàn)?
    A:UT測試的是代碼,是為了讓代碼正確的工作,所以你要測試接口的話,那就必須要接口里可以寫代碼:)

    # re: 敏捷質(zhì)疑: TDD  回復  更多評論   

    2008-09-18 18:12 by sp123馬甲
    加了這個就必須再加一個:
    Q: 是否先寫實現(xiàn)代碼然后寫測試代碼?
    A: 不是這是基本的TDD概念問題哦!測試先行,測試的整個流程都可以針對接口,只有第一句實例化被測試對象時才需要針對實際的實現(xiàn)。即使如此,測試寫下來之后,你無法編譯通過,這正好是TDD的第一個動作,你需要補充一個實現(xiàn)來讓編譯通過!

    # re: 敏捷質(zhì)疑: TDD  回復  更多評論   

    2008-12-18 21:12 by lilee
    寫的很好,可否轉(zhuǎn)載?

    # re: 敏捷質(zhì)疑: TDD  回復  更多評論   

    2008-12-18 21:25 by chelsea
    @lilee

    注明出處就可以.

    只有注冊用戶登錄后才能發(fā)表評論。


    網(wǎng)站導航:
     
    主站蜘蛛池模板: 久久精品国产亚洲av麻豆色欲| 国产在线观看免费视频播放器| 亚洲伊人久久大香线蕉综合图片| 亚洲av成人一区二区三区在线播放 | 国产精品区免费视频| 亚洲中文字幕无码爆乳AV| 十八禁视频在线观看免费无码无遮挡骂过| 亚洲乱码日产精品a级毛片久久| 二级毛片免费观看全程| 国产亚洲日韩一区二区三区| 久别的草原电视剧免费观看| 亚洲精品第五页中文字幕 | 日韩精品视频免费观看| 亚洲AV无码一区二区三区鸳鸯影院| 国产免费黄色大片| 国产精品极品美女自在线观看免费| 亚洲码国产精品高潮在线| 美丽姑娘免费观看在线观看中文版| 亚洲视频一区二区三区| 免费看AV毛片一区二区三区| 全部在线播放免费毛片| 亚洲成A人片在线观看无码不卡 | 中文字幕亚洲免费无线观看日本 | 国产成人精品日本亚洲18图| 永久免费看mv网站入口| 中文字字幕在线高清免费电影| 亚洲精品自产拍在线观看动漫| 毛片A级毛片免费播放| 一级做a爰黑人又硬又粗免费看51社区国产精品视 | 四虎国产精品永久免费网址| 亚洲另类古典武侠| 国产91久久久久久久免费| a级毛片免费观看视频| 亚洲免费视频播放| 亚洲国产高清精品线久久| 亚洲免费视频在线观看| 午夜亚洲国产理论片二级港台二级 | 日本在线高清免费爱做网站| 视频一区在线免费观看| 老汉色老汉首页a亚洲| 四虎永久免费地址在线网站 |