《電子技術(shù)應(yīng)用》
您所在的位置:首頁(yè) > 其他 > 業(yè)界動(dòng)態(tài) > 應(yīng)用設(shè)計(jì)模式編寫(xiě)易于單元測(cè)試的代碼

應(yīng)用設(shè)計(jì)模式編寫(xiě)易于單元測(cè)試的代碼

2008-07-24
作者:熊 偉
單元測(cè)試是軟件開(kāi)發(fā)" title="軟件開(kāi)發(fā)">軟件開(kāi)發(fā)的一個(gè)重要組成部分,通過(guò)在軟件設(shè)計(jì)、開(kāi)發(fā)的過(guò)程中合理地運(yùn)用設(shè)計(jì)模式" title="設(shè)計(jì)模式">設(shè)計(jì)模式,不但為系統(tǒng)重構(gòu)、功能擴(kuò)展" title="功能擴(kuò)展">功能擴(kuò)展及代碼維護(hù)提供了方便,同時(shí)也為單元測(cè)試的實(shí)施提供了極大的靈活性,可以有效降低單元測(cè)試編碼的難度,更好地保證軟件開(kāi)發(fā)的質(zhì)量。

引言

設(shè)計(jì)模式是對(duì)被用來(lái)在特定場(chǎng)景下解決一般設(shè)計(jì)問(wèn)題的類和相互通信的對(duì)象的描述,通過(guò)在系統(tǒng)設(shè)計(jì)中引入合適的設(shè)計(jì)模式可以為系統(tǒng)實(shí)現(xiàn)提供更大的靈活性,從而有效地控制變化,更好地應(yīng)對(duì)需求變更或者按需變更系統(tǒng)運(yùn)行路徑等問(wèn)題。

請(qǐng)?jiān)L問(wèn) Java 設(shè)計(jì)模式專題,查看更多關(guān)于 Java 設(shè)計(jì)模式的文章和教程。

單元測(cè)試是軟件開(kāi)發(fā)的一個(gè)重要組成部分,是與編碼實(shí)現(xiàn)同步進(jìn)行的開(kāi)發(fā)活動(dòng),這一點(diǎn)已成為軟件開(kāi)發(fā)者的共識(shí)。適度的單元測(cè)試不但不會(huì)影響開(kāi)發(fā)進(jìn)度,反而可以為開(kāi)發(fā)過(guò)程提供很好的控制,為軟件質(zhì)量、系統(tǒng)重構(gòu)等提供有力的保障,并且,當(dāng)后續(xù)系統(tǒng)需求發(fā)生變更、Bug Fix 或功能擴(kuò)展時(shí),能很好地保證已有實(shí)現(xiàn)不會(huì)遭到破壞,從而使得程序更易于維護(hù)和修改。 Martin Fowler、Kent Beck、Robert Martin 等軟件設(shè)計(jì)領(lǐng)域泰斗更是極力倡導(dǎo)測(cè)試先行的測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(Test Driven Development,TDD)的開(kāi)發(fā)方式。

單元測(cè)試主要用于測(cè)試細(xì)粒度的程序單元,如類的某個(gè)復(fù)雜方法的正確性,也可以根據(jù)需要綜合測(cè)試某個(gè)操作所涉及的多個(gè)相互聯(lián)系的類的正確性。在很多情況下,相互聯(lián)系的多個(gè)類中有些類比較簡(jiǎn)單,為這些簡(jiǎn)單類單獨(dú)編寫(xiě)單元測(cè)試用例往往不如將它們與使用它們的類一起進(jìn)行測(cè)試有意義。

模擬對(duì)象(Mock Objects)是為模擬被測(cè)試單元所使用的外圍對(duì)象、設(shè)備(后文統(tǒng)一簡(jiǎn)稱為外部對(duì)象)而設(shè)計(jì)的一種特殊對(duì)象,它們具有與外部對(duì)象相同的接口,但實(shí)現(xiàn)往往比較簡(jiǎn)單,可以根據(jù)測(cè)試的場(chǎng)景進(jìn)行定制。由于單元測(cè)試不是系統(tǒng)測(cè)試,方便、快速地被執(zhí)行是單元測(cè)試的一個(gè)基本要求,直接使用外部對(duì)象往往需要經(jīng)過(guò)復(fù)雜的系統(tǒng)配置,并且容易出現(xiàn)與欲測(cè)試功能無(wú)關(guān)的問(wèn)題;對(duì)于一些異常的場(chǎng)景,直接使用外部對(duì)象可能難以構(gòu)造,而通過(guò)設(shè)計(jì)合適的 Mock Objects,則可以方便地模擬需要的場(chǎng)景,從而為單元測(cè)試的順利執(zhí)行提供有效的支持。

本文根據(jù)筆者經(jīng)驗(yàn),介紹了幾種典型的設(shè)計(jì)模式在系統(tǒng)設(shè)計(jì)中的應(yīng)用,及由此為編寫(xiě)單元測(cè)試帶來(lái)的方便。

從對(duì)象創(chuàng)建開(kāi)始

由于需要使用 Mock Objects 來(lái)模擬外部對(duì)象的功能,因此必須修改正常的程序流程,使得被測(cè)試功能模塊與 Mock Objects,而不是外部對(duì)象進(jìn)行交互。要做到這一點(diǎn),首先要解決的問(wèn)題就是對(duì)象創(chuàng)建,即在原本創(chuàng)建外部對(duì)象的地方創(chuàng)建 Mock Objects,因此在設(shè)計(jì)、實(shí)現(xiàn)業(yè)務(wù)邏輯時(shí)需要注意從業(yè)務(wù)邏輯中分離出對(duì)象創(chuàng)建邏輯。

關(guān)于 setUp

setUp 是 JUnit 基礎(chǔ)類 TestCase 的一個(gè)重要方法,每個(gè)單元測(cè)試在被執(zhí)行前會(huì)調(diào)用 setUp 方法做一些必要的預(yù)處理,如準(zhǔn)備好一些公共的基本輸入或創(chuàng)建所需的外部對(duì)象。

Factory Method 是一種被普遍運(yùn)用的創(chuàng)建型模式,用于將對(duì)象創(chuàng)建的職責(zé)分離到獨(dú)立的方法中,并通過(guò)子類" title="子類">子類化來(lái)實(shí)現(xiàn)創(chuàng)建不同對(duì)象的目的。如果被測(cè)試單元所使用的外部對(duì)象是通過(guò) Factory Method 創(chuàng)建的,則可以通過(guò)從已有被測(cè)試的 Factory 類派生出一個(gè)新的 MockFactory,以創(chuàng)建 Mock Objects,并在 setUp 測(cè)試中創(chuàng)建 MockFactory,從而間接達(dá)到對(duì)被測(cè)試類進(jìn)行測(cè)試的目的。

面的" title="面的">面的代碼片段展示了具體的做法:

// BaseObjects.java
package com.factorymethod.demo;
public interface BaseObjects {
    voidfunc(); 
} 

// OuterObjects.java
package com.factorymethod.demo;
public class OuterObjects implements BaseObjects {
    public void func() { 
        System.out.println('OuterObjects.func'); 
    } 
} 

// LogicToBeTested.java, code to be tested
package com.factorymethod.demo;
public class LogicToBeTested {
    public void doSomething() { 
        BaseObjects b = createBase(); 
        b.func(); 
    }
    
    public BaseObjects createBase() {
        return newOuterObjects(); 
    } 
}

以下則是對(duì)應(yīng)的 MockOuterObjects、MockFactory 以及單元測(cè)試的實(shí)現(xiàn):

// MockOuterObjects.java
package com.factorymethod.demo;
public class MockOuterObjects implements BaseObjects {
    public void func() { 
        System.out.println('MockOuterObjects.func'); 
    } 
} 

// MockLogicToBeTested.java
package com.factorymethod.demo;
public class MockLogicToBeTested extends LogicToBeTested {
    public BaseObjects createBase() {
        return new MockOutterObjects(); 
    } 
} 

// LogicTest.java
package com.factorymethod.demo;
import junit.framework.TestCase;
 
public class  LogicTest extends TestCase { 
    LogicToBeTested c;
    protected void setUp() { 
        c =new MockLogicToBeTested(); 
    }
    public void testDoSomething() { 
        c.doSomething(); 
    } 
}
			

Abstract Factory 是另一種被普遍運(yùn)用的創(chuàng)建型模式,Abstract Factory 通過(guò)專門(mén)的 Factory Class 來(lái)封裝對(duì)象創(chuàng)建的職責(zé),并通過(guò)實(shí)現(xiàn) Abstract Factory 來(lái)完成不同的創(chuàng)建邏輯。如果被測(cè)試單元所使用的外部對(duì)象是通過(guò) Abstract Factory 創(chuàng)建的,則實(shí)現(xiàn)一個(gè)新的 Concrete Factory,并在此 Factory 中創(chuàng)建 Mock Objects 是一個(gè)比較好的解決辦法。對(duì)于 Factory 本身,則可以在 setUp 測(cè)試的時(shí)候指定新的 Concrete Factory ;此外,借助依賴注入框架(如 Spring 的 BeanFactory),通過(guò)依賴注入的方式將 Factory 注入也是一種不錯(cuò)的解決方案。對(duì)于簡(jiǎn)單的依賴注入需求,可以考慮實(shí)現(xiàn)一個(gè)應(yīng)用專有的依賴注入模塊,或者實(shí)現(xiàn)一個(gè)簡(jiǎn)單的實(shí)現(xiàn)加載器,即根據(jù)配置文件載入相應(yīng)的實(shí)現(xiàn),從而無(wú)需修改應(yīng)用代碼,僅通過(guò)修改配置文件即可載入不同的實(shí)現(xiàn),進(jìn)而方便地修改程序的運(yùn)行路徑,執(zhí)行單元測(cè)試。

下面的代碼實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的 InstanceFactory:

// refer to http://www.opensc-project.org/opensc-java/export/100/trunk/
// pkcs15/src/main/java/org/opensc/pkcs15/asn1/InstanceFactory.java
packagecom.instancefactory.demo;

importjava.lang.reflect.InvocationTargetException;
importjava.lang.reflect.Method;
importjava.lang.reflect.Modifier;

public class InstanceFactory {
    private final Method getInstanceMethod;
    
    public InstanceFactory(String type) { 
        Class clazz =null;
        try { 
            clazz = Class.forName(type);
            this.getInstanceMethod = clazz.getMethod('getInstance');
            if(!Modifier.isStatic(this.getInstanceMethod.getModifiers()) 
            || !Modifier.isPublic(this.getInstanceMethod.getModifiers()))
                throw new IllegalArgumentException(
                    'Method [' + clazz.getName() 
                    + '.getInstance(Object)] is not static public.'); 
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException(
                'Class [' + clazz.getName() 
                + '] has no static getInstance(Object) method.', e); 
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException('Class [' + type + '] is not found'); 
        } 
    }

    public Object getInstance() {
        try{
            return this.getInstanceMethod.invoke(null); 
        } catch (InvocationTargetException e) {
            if( e.getCause() instanceof RuntimeException )
                throw (RuntimeException)e.getCause();
            throw new IllegalArgumentException(
                    'Method [' +this.getInstanceMethod 
                    + '] has thrown an checked exception.', e); 
        } catch( IllegalAccessException e) {
            throw new IllegalArgumentException(
                    'Illegal access to method [' 
                    +this.getInstanceMethod + '].', e); 
        } 
    }
    
    public Method getGetInstanceMethod() {
        return this.getInstanceMethod; 
    } 
}

以下代碼演示了 InstanceFactory 的簡(jiǎn)單使用:

// BaseObjects.java
package com.instancefactory.demo;

public interface BaseObjects {
    voidfunc(); 
} 

 // OuterObjects.java

package com.instancefactory.demo;

public class OuterObjects implements BaseObjects {
    public static BaseObjects getInstance() {
        return new OuterObjects(); 
    }
    
    public void func() { 
        System.out.println('OuterObjects.func'); 
    } 
} 

// MockOuterObjects.java
package com.instancefactory.demo;
public class MockOuterObjects implements BaseObjects {
    public static BaseObjects getInstance() {
        return new MockOuterObjects(); 
    }
    
    public void func() { 
        System.out.println('MockOuterObjects.func'); 
    } 
 } 

// LogicToBeTested.java
packagecom.instancefactory.demo;
public class LogicToBeTested {
    public static final String PROPERTY_KEY= 'BaseObjects';
    public void doSomething() { 
        // load configuration file and read the implementation class name of BaseObjects 
        // read it from properties to simplify the demo 
        // actually, the property file reader can be implemented by InstanceFactory 
        String impl = System.getProperty(PROPERTY_KEY); 
        InstanceFactory factory = new InstanceFactory(impl); 
        BaseObjects b = (BaseObjects)factory.getInstance(); 
        b.doSomething(); 
    } 
 } 

// LogicTest.java
packagecom.instancefactory.demo;
importjunit.framework.TestCase;
public class LogicTest extends TestCase { 
    LogicToBeTested c;
    protected void setUp() { 
        // set the property file of class map to a file for MockObjects, omitted 
        // use System.setProperty to simplify the demo 
        System.setProperty(LogicToBeTested.PROPERTY_KEY, 
                'com.instancefactory.demo.MockOuterObjects'); 
        c = new LogicToBeTested(); 
    }
    
    public void testDoSomething() { 
        c.doSomething(); 
    } 
 }

替換實(shí)現(xiàn)

通過(guò) Factory Method 替換被創(chuàng)建對(duì)象可以滿足一些修改程序運(yùn)行路徑的需求,但是,這種方法以子類化為前提,具有很強(qiáng)的侵入性,并且在編寫(xiě)單元測(cè)試時(shí),開(kāi)發(fā)人員需要同時(shí)負(fù)責(zé) Mock Objects 的開(kāi)發(fā),供 Factory Method 調(diào)用,因此,編碼量往往會(huì)比較大,單元測(cè)試開(kāi)發(fā)人員也需對(duì)所使用的公共模塊的內(nèi)部結(jié)構(gòu)有十分清楚的認(rèn)識(shí)。即使可以使用公共的 Mock Objects 實(shí)現(xiàn)避免代碼重復(fù),往往也需要修改業(yè)務(wù)邏輯中公共服務(wù)相關(guān)對(duì)象的創(chuàng)建代碼,這一點(diǎn)對(duì)于應(yīng)用公共模塊的業(yè)務(wù)邏輯的單元測(cè)試可能不太適合。

在筆者曾參與設(shè)計(jì)、開(kāi)發(fā)的某應(yīng)用系統(tǒng)中,有一個(gè)專門(mén)的數(shù)據(jù)庫(kù)緩沖(Cache)公共服務(wù),該 Cache 負(fù)責(zé)完成與數(shù)據(jù)庫(kù)交互,實(shí)現(xiàn)數(shù)據(jù)的存取,并緩存數(shù)據(jù)以提高后續(xù)訪問(wèn)的效率。對(duì)于涉及數(shù)據(jù)庫(kù)緩沖的業(yè)務(wù)邏輯的單元測(cè)試,需要一個(gè)替代方案來(lái)替代已有的數(shù)據(jù)庫(kù)緩沖,以避免直接訪問(wèn)實(shí)際數(shù)據(jù)庫(kù),但又要保證這個(gè)替換不會(huì)影響到被測(cè)試單元的實(shí)現(xiàn)。

為了解決這個(gè)問(wèn)題,我們并沒(méi)有直接替換 Cache 創(chuàng)建處的代碼,因?yàn)檫@些代碼遍布在業(yè)務(wù)代碼中,直接替換 Cache 創(chuàng)建代碼無(wú)疑會(huì)侵入業(yè)務(wù)邏輯,并需要大量使用子類化。為了盡可能降低對(duì)業(yè)務(wù)邏輯的影響,我們維持了原有 CacheFactory 的接口,但是將 CacheFactory 的實(shí)現(xiàn)委托(Delegate)給另一個(gè)實(shí)現(xiàn)類完成,以下是 CacheFactory 實(shí)現(xiàn)的偽代碼:

package com.cachefactory.demo;
public abstract class CacheFactory {
    private static CacheFactoryinstance = new DelegateCacheFactory();
    private static CacheFactorydelegate;
    protected CacheFactory() { 
    } 
  
    // CacheFactory is a singletonpublic
    static CacheFactory getInstance() {
        return instance; 
    } 
  
    // the implementation can be changedprotected
    static void setDelegate(CacheFactory instance) {
        delegate= instance; 
    }
        
    public abstract Cache getCache(Object... args); 
 
    // redirect all request to delegateeprivate
    static class DelegateCacheFactoryextendsCacheFactory {
        private DelegateCacheFactory() { 
        }
            
        public Cache getCache(Object... args) {
            return delegate.getCache(args); 
        } 
    } 
 }

與 CacheFactoryImpl 類似地,我們實(shí)現(xiàn)了一個(gè) MockCacheFactory,但與 CacheFactoryImpl 不同的是,這個(gè) MockCacheFactory 所創(chuàng)建的 MockCache 對(duì)象雖然與真正的 Cache 實(shí)現(xiàn)了相同的接口,但是,它的內(nèi)部實(shí)現(xiàn)卻是基于 HashMap 的,因此,可以很好地滿足單元測(cè)試快速、方便地運(yùn)行的需要。

單元測(cè)試時(shí),只需要在 setUp 時(shí)調(diào)用執(zhí)行如下操作:

setDelegate(new MockCacheFactory());

將 CacheFactory 的實(shí)現(xiàn)委托給 MockCacheFactory 即可,所有業(yè)務(wù)邏輯都無(wú)需作任何修改,因此,這種替換實(shí)現(xiàn)的方式幾乎是沒(méi)有侵入性的。

這種通過(guò)將實(shí)現(xiàn)分離到專門(mén)的實(shí)現(xiàn)類中的做法其實(shí)是 Bridge 模式的一個(gè)應(yīng)用,通過(guò)使用 Bridge 模式,為替換實(shí)現(xiàn)保留了接口,從而使得在不對(duì)業(yè)務(wù)邏輯作任何修改的情況下可以輕松替換公共服務(wù)的實(shí)現(xiàn)。

除此之外,Strategy 模式也是一種替換實(shí)現(xiàn)的有效途徑,這種方式與 Factory Method 類似,通過(guò)子類化實(shí)現(xiàn)新的 Strategy 以替換業(yè)務(wù)邏輯使用的舊的 Strategy,通過(guò)與 Factory Method 或 Bridge 等模式聯(lián)合使用,在編寫(xiě)應(yīng)用公共服務(wù)的業(yè)務(wù)邏輯的單元測(cè)試時(shí)也十分有用。

繞過(guò)部分實(shí)現(xiàn)

繞過(guò)部分實(shí)現(xiàn)進(jìn)行單元測(cè)試在大多數(shù)情況下是不可取的,因?yàn)檫@種做法極有可能會(huì)影響單元測(cè)試的質(zhì)量。但是對(duì)于一些特殊的情況,我們可以“冒險(xiǎn)”使用這種方式,比如有這樣的一個(gè)場(chǎng)景:所有請(qǐng)求需經(jīng)過(guò)多級(jí)認(rèn)證,且部分認(rèn)證處理需要訪問(wèn)數(shù)據(jù)庫(kù),認(rèn)證結(jié)束后為請(qǐng)求分配相應(yīng)的 sessionId,請(qǐng)求在獲得 sessionId 后繼續(xù)進(jìn)行進(jìn)一步的業(yè)務(wù)邏輯處理。

在保證多級(jí)認(rèn)證模塊已被專門(mén)的單元測(cè)試覆蓋的情況下,我們?cè)跒闃I(yè)務(wù)邏輯編寫(xiě)單元測(cè)試的過(guò)程中可以考慮跳過(guò)多級(jí)認(rèn)證授權(quán)模塊(對(duì)于部分特權(quán)用戶,也應(yīng)跳過(guò)部分檢查),直接為其分配一個(gè) Mock 的 sessionId,以進(jìn)行后續(xù)處理。

對(duì)于多級(jí)認(rèn)證問(wèn)題本身,我們可以考慮采用 Chain of Responsibility 模式將不同的認(rèn)證邏輯封裝到不同的 RequestHandler 中,并通過(guò)編碼或者根據(jù)配置,將所有的 Handler 串聯(lián)成 Responsibility Chain ;而在單元測(cè)試過(guò)程中,可以修改 Handler 的串聯(lián)方式,繞過(guò)部分不希望在單元測(cè)試中經(jīng)過(guò)的 Handler,從而簡(jiǎn)化單元測(cè)試的運(yùn)行。

對(duì)于這個(gè)問(wèn)題,筆者并不同意為了單元測(cè)試的需要去采用 Chain of Responsibility 模式,實(shí)際上,上面所闡述的多級(jí)認(rèn)證問(wèn)題本身比較適合采用這種模式來(lái)解決,能夠根據(jù)需要繞過(guò)部分實(shí)現(xiàn),只是應(yīng)用這種模式的情況下進(jìn)行單元測(cè)試的一種可以考慮的測(cè)試途徑。

總結(jié)

單元測(cè)試是軟件開(kāi)發(fā)的重要組成部分,而應(yīng)用 Mock Object 是進(jìn)行單元測(cè)試一種普遍而有效的方式,通過(guò)在軟件設(shè)計(jì)、開(kāi)發(fā)的過(guò)程中合理地運(yùn)用設(shè)計(jì)模式,不但為系統(tǒng)重構(gòu)、功能擴(kuò)展及代碼維護(hù)提供了方便,同時(shí)也為單元測(cè)試的實(shí)施提供了極大的靈活性,可以有效降低單元測(cè)試編碼的難度,方便地在單元測(cè)試中引入 Mock Objects,達(dá)到對(duì)被測(cè)試目標(biāo)進(jìn)行單元測(cè)試的目的,從而更好地保證軟件開(kāi)發(fā)的質(zhì)量。



參考資料



關(guān)于作者

Photo of 熊偉(Wayne Xiong)

熊偉(Wayne Xiong),華中科技大學(xué)碩士,曾用網(wǎng)名 Bill David、大衛(wèi)、大笨熊等。精于 C++,后轉(zhuǎn)入 JAVA 陣營(yíng),曾就職于 Lucent、BEA(Oracle)等公司,從事電信及 J2EE 應(yīng)用平臺(tái)的設(shè)計(jì)開(kāi)發(fā);現(xiàn)為 Adobe 公司高級(jí)軟件工程師,主要從事 Flash Media Server 及 RIA 相關(guān)應(yīng)用的設(shè)計(jì)開(kāi)發(fā)。可以通過(guò) [email protected] 或博客 http://blog.csdn.net/billdavid 與他聯(lián)系。

本站內(nèi)容除特別聲明的原創(chuàng)文章之外,轉(zhuǎn)載內(nèi)容只為傳遞更多信息,并不代表本網(wǎng)站贊同其觀點(diǎn)。轉(zhuǎn)載的所有的文章、圖片、音/視頻文件等資料的版權(quán)歸版權(quán)所有權(quán)人所有。本站采用的非本站原創(chuàng)文章及圖片等內(nèi)容無(wú)法一一聯(lián)系確認(rèn)版權(quán)者。如涉及作品內(nèi)容、版權(quán)和其它問(wèn)題,請(qǐng)及時(shí)通過(guò)電子郵件或電話通知我們,以便迅速采取適當(dāng)措施,避免給雙方造成不必要的經(jīng)濟(jì)損失。聯(lián)系電話:010-82306118;郵箱:[email protected]
主站蜘蛛池模板: 欧美日韩在线播一区二区三区 | 九九黄色影院 | 三级三级三级网站网址 | 国产精品一区二区手机在线观看 | 男人天堂网在线视频 | 伊人久久国产免费观看视频 | 91欧洲在线视精品在亚洲 | 日韩中文在线观看 | 久久久精品久久 | 美女扒开腿让男人桶个爽 | 久久综合精品国产一区二区三区 | 91视频免费播放 | 亚洲人的天堂男人爽爽爽 | 男人女人做刺激视频免费 | 免费观看情趣v视频网站 | 国产成人免费全部网站 | 欧美特黄一级高清免费的香蕉 | 18免费网站 | 久久不见久久见免费影院www日本 | a毛片全部播放免费视频完整18 | 午夜dj视频完整社区 | 男人天堂网址 | 最新亚洲精品国自产在线观看 | 一区二区三区四区视频 | 欧美成人影院 在线播放 | 免费人成在线观看网站 | 成人永久免费视频网站在线观看 | 亚洲精国产一区二区三区 | 日本一区二区高清不卡 | 日本加勒比视频 | 久久黄色影片 | 日日a.v拍夜夜添久久免费 | 在线成人97观看 | 成人在线一区二区三区 | 久久毛片网 | 精品一区二区三区视频在线观看免 | 国产真实一区二区三区 | avtt天堂网永久资源手机版 | 欧美一级毛片特黄黄 | 久草视频国产 | www黄网站|