乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      微服務(wù)實戰(zhàn)之Mock

       liang1234_ 2019-04-16

      模擬對象

      一般都叫 Mock 或 Stub, 兩者差不多, 都是模擬被測組件對外依賴的模擬, 存根 stub 就在那里, 不需要檢查它和被測組件的交互, Mock 則可以用來檢查于被測對象的交互

      Mock

      Mock 是測試驅(qū)動開發(fā)必備之利器, 只要有狀態(tài), 有依賴, 做單元測試就不能沒有 Mock
      在 API 或 集成測試的時候, 如果依賴于第三方的 API, 也時常使用 mock server 或 mock proxy

      Mock 的原則

      Mockito 是廣泛使用的 Java Mock library, 它的 wiki 上有篇文章 - 如何寫出好的測試代碼, 其中提出了幾條使用 mock 的原則:

      • 不要 mock 非你所有的類型

      • 不要 mock 值對象

      • 不要 mock 所有的東西

      后兩點很好理解, 第一點有些語焉不詳, 什么叫非你所有的類型, 我的理解就是如果一個類型不是你與第三方約定的接口, 它屬于別人定義的, 你只是拿過來使用, 那么你最好不要去mock 它, 你可以寫一個中間層或適配器, 然后mock 這個中間層和適配器, 原因是第三方可以隨時更改它的定義和行為, 你把它mock掉了, 你也就不會發(fā)現(xiàn)由于別人更改了定義或行為導(dǎo)致的異常. 而你自己寫的中間層由你掌控,不必有此擔(dān)心。

      與第三方或其它服務(wù)集成測試屬于Consumer Test 消費者測試和End to End 端到端的測試的范圍

      Mock 使用步驟

      • 1.模擬外部依賴并將mock插入到測試代碼中

      • 2.定義指定的行為及響應(yīng)


        1. 執(zhí)行測試下的代碼

      • 4.驗證代碼是否正確執(zhí)行

      驗證什么呢, 除了你的程序的應(yīng)用邏輯, 還有對于所mock的對象的交互驗證

      • 對于mock 的對象的調(diào)用次數(shù)驗證

      • 對于mock 的對象的調(diào)用參數(shù)驗證

      • 對于mock 的對象所給出的不同輸出結(jié)果的反應(yīng)

      Mock 的問題

      mock的時候最煩人的是兩個問題

      1.無法mock

      就我熟悉的, 也是應(yīng)用最廣的兩門語言 C 和 Java 來看

      gmock 和 mockito 在大多數(shù)情況下都夠用了,一般情況下不需要也不應(yīng)該 mock 私有方法,靜態(tài)方法和全局方法,當然如果你的代碼可測試性及依賴反轉(zhuǎn)做得得沒那么好, 實在繞不過去,也有權(quán)變之法, C 可以直接改掉其在內(nèi)存中的函數(shù)地址, Java 可以利用反射或修改字節(jié)碼來搞定.

      2. 需要mock的太多了

      舉例來說, 我曾經(jīng)做過一個網(wǎng)絡(luò)電話控制系統(tǒng), 它會對呼入呼出的電話會做一些語音交互應(yīng)答(IVR), 并控制后續(xù)的電話會議流程, 系統(tǒng)比較復(fù)雜, 單元測試也很難做, 因為它用的是自己定義的一門領(lǐng)域特定語言 - Call Control XML, 并由自己的 Call Flow 引擎進行解析執(zhí)行, 端到端的測試由于環(huán)境及配置的復(fù)雜性做起來很麻煩, 我的一位同事提出把系統(tǒng)的網(wǎng)絡(luò)消息發(fā)送接收模塊 mock 掉, 也就是把對外交互的消息全部 mock 起來, 但是mock的消息數(shù)量巨大, 工作量驚人.

      我也寫了一個類似于 hub 的類, 所有消息會回調(diào)到一個 MessageReceiver, MessageReceiver 會直接調(diào)用注冊上來的各個 MessageHandler, 每個 Handler 只關(guān)注自己關(guān)心的消息, 具體來說, 每個 Handler 都可以設(shè)置一個正則表達式, 當消息頭或消息體匹配這個正則表達式, 則由這個 Handler 來處理回應(yīng)事先 mock好的消息, 回應(yīng)你自己指定的消息, 從而把這個系統(tǒng)對外的依賴全部 mock 掉, 并測試了所有的交互

      mock 的粒度

      根據(jù)你測試的對象大小,粒度自然有區(qū)別,根據(jù)測試三角形,小而美,越大越麻煩, 從小到大可以分為如下三個粒度

      1. mock一個函數(shù)

      與這個函數(shù)的交互全部mock 掉

      2. mock整個類或接口

      與這個類或接口的交互全部mock 掉,接口也可指某個API

      3. mock 整個系統(tǒng)

      與系統(tǒng)外部的交互全部mock 掉

      總之,模擬外部依賴要區(qū)分內(nèi)外的邊界,找到合適的切入點

      Mock 類庫和工具

      僅就我所熟悉的 Java 和 C 舉例如下, python, ruby, JavaScript 之類的腳本語言就更簡單了

      Mockito for Java

      http://site./

      Powermock for Java

      https://github.com/powermock/powermock 它通過自定義類加載器和修改字節(jié)碼來mock static methods, constructors, final classes and methods, private methods, removal of static initializers 等等

      GoogleMock for C

      https://github.com/google/googletest/tree/master/googlemock

      Mock Server

      MockServer 用來 mock 整個web service
      https://github.com/jamesdbloom/mockserver

      wiremock

      WireMock 和上面的 mock server差不多, 是一個 HTTP-based APIs的模擬器.

      http:///

      典型示例

      接下來, 讓我們寫幾個例子來說明 mock 和相關(guān)類庫的用法...

      Mock 依賴的類和方法

      基本步驟:

      1. mock 設(shè)置模擬行為

      2. call 調(diào)用被測試代碼

      3. verify 檢驗期望行為

      這里以 Guava Loading Cache 類為例, 測試它的基本行為是否符合預(yù)期

      package com.github.walterfan.hellotest;import com.google.common.cache.CacheBuilder;import com.google.common.cache.CacheLoader;import com.google.common.cache.LoadingCache;import com.google.common.cache.RemovalCause;import com.google.common.cache.RemovalListener;import com.google.common.cache.RemovalNotification;import com.google.common.util.concurrent.Uninterruptibles;import lombok.extern.slf4j.Slf4j;import org.mockito.ArgumentCaptor;import org.mockito.Captor;import org.mockito.Mock;import org.mockito.Mockito;import org.mockito.MockitoAnnotations;import org.mockito.invocation.InvocationOnMock;import org.mockito.stubbing.Answer;import org.testng.annotations.BeforeMethod;import org.testng.annotations.Test;import java.util.HashMap;import java.util.Map;import java.util.Optional;import java.util.concurrent.ExecutionException;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;import static org.mockito.Mockito.times;import static org.mockito.Mockito.verify;import static org.testng.Assert.assertEquals;import static org.testng.Assert.assertTrue;/** * Created by yafan on 23/1/2018. */@Slf4jpublic class LoadingCacheTest { private LoadingCache<String, String> internalCache; @Mock private CacheLoader<String, String> cacheLoader; @Mock private RemovalListener<String, String> cacheListener; @Captor private ArgumentCaptor<RemovalNotification<String, String>> argumentCaptor; private Answer<String> loaderAnswer; private AtomicInteger loadCounter = new AtomicInteger(0); @BeforeMethod public void setup() { MockitoAnnotations.initMocks(this); this.internalCache = CacheBuilder.newBuilder() .maximumSize(3) .expireAfterWrite(1, TimeUnit.SECONDS) .removalListener(this.cacheListener) .build(this.cacheLoader); this.loaderAnswer = new Answer<String>() { @Override public String answer(InvocationOnMock invocationOnMock) throws Throwable { String key = invocationOnMock.getArgumentAt(0, String.class); switch(loadCounter.getAndIncrement()) { case 0: return 'alice'; case 1: return 'bob'; case 2: return 'carl'; default: return 'unknown'; } } }; } @Test public void cacheTest() throws Exception { //Mock the return value of loader //Mockito.when(cacheLoader.load(Mockito.anyString())).thenReturn('alice'); Mockito.when(cacheLoader.load(Mockito.anyString())).thenAnswer(loaderAnswer); assertTrue('alice'.equals(internalCache.get('name'))); //sleep for 2 seconds Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS); assertTrue('bob'.equals(internalCache.get('name'))); verify(cacheLoader, times(2)).load('name'); verify(cacheListener).onRemoval(argumentCaptor.capture()); assertEquals(argumentCaptor.getValue().getKey(), 'name'); assertEquals(argumentCaptor.getValue().getValue(), 'alice'); assertEquals(argumentCaptor.getValue().getCause(), RemovalCause.EXPIRED); } }

      Mock 靜態(tài)方法

      這里使用 Powermock 和 testng , 如果有 junit 的話, 用法稍有不同
      testng 需要從 PowerMockTestCase 繼承
      junit4 需要加上一個注解 @RunWith(PowerMockRunner.class)

      • 靜態(tài)類和方法

      package com.github.walterfan.hellotest;import lombok.extern.slf4j.Slf4j;import org.apache.commons.io.IOUtils;import java.io.File;import java.io.FileFilter;import java.io.FilenameFilter;import java.io.IOException;import java.io.InputStream;import java.nio.charset.Charset;import java.util.ArrayList;import java.util.List;@Slf4jpublic class FileUtils {    public static final FileFilter javaFileFilter = new FileFilter() {        @Override
              public boolean accept(File file) {            if(file.isDirectory()) {                return true;
                  }            if(file.getName().endsWith('.java')) {                return true;
                  }            return false;
              }
          };    public static List<String> listFiles(File folder, FileFilter filter) {
              List<String> files = new ArrayList<>();
              listDir(new File('.'), files, filter);        return files;
          }    public static void listDir(File folder, List<String> fileNames, FileFilter filter) {
              File[] files = folder.listFiles(filter);        for (File file: files) {            if(file.isFile()) {
                      fileNames.add(file.getName());
                  } else if (file.isDirectory()) {
                      listDir(file, fileNames, filter);
                  }
              }
          }
      }
      • 測試類

      package com.github.walterfan.hellotest;import org.junit.runner.RunWith;import org.mockito.Mockito;import org.powermock.api.mockito.PowerMockito;import org.powermock.core.classloader.annotations.PrepareForTest;import org.powermock.modules.junit4.PowerMockRunner;import org.powermock.modules.testng.PowerMockObjectFactory;import org.powermock.modules.testng.PowerMockTestCase;import org.testng.IObjectFactory;import org.testng.annotations.Test;import java.io.File;import java.io.FileFilter;import java.util.Arrays;import java.util.List;import static org.mockito.Matchers.eq;import static org.testng.Assert.assertEquals;//@RunWith(PowerMockRunner.class) -- for junit4@PrepareForTest(FileUtils.class)public class FileUtilsTest extends PowerMockTestCase { public int howManyFiles(String path, FileFilter filter) { System.out.println('-----------'); List<String> files = FileUtils.listFiles(new File(path), filter); files.forEach(System.out::println); return files.size(); } @Test public void testHowManyFiles() { List<String> fileNames = Arrays.asList('a.java', 'b.java', 'c.java'); PowerMockito.mockStatic(FileUtils.class); PowerMockito.when(FileUtils.listFiles(Mockito.any(), Mockito.any())).thenReturn(fileNames); int count = howManyFiles('.', FileUtils.javaFileFilter); assertEquals(count, 3); } }

      在 pom.xml 中加上

      <dependency>
                  <groupId>org.powermock</groupId>
                  <artifactId>powermock-core</artifactId>
                  <version>1.7.1</version>
                  <scope>test</scope>
                  <scope>test</scope>
              </dependency>
      
              <dependency>
                  <groupId>org.powermock</groupId>
                  <artifactId>powermock-api-mockito</artifactId>
                  <version>1.7.1</version>
                  <scope>test</scope>
              </dependency>
      
              <dependency>
                  <groupId>org.powermock</groupId>
                  <artifactId>powermock-module-junit4</artifactId>
                  <version>1.7.1</version>
                  <scope>test</scope>
              </dependency>
      
              <dependency>
                  <groupId>org.powermock</groupId>
                  <artifactId>powermock-module-testng</artifactId>
                  <version>1.7.1</version>
                  <scope>test</scope>
              </dependency>

      Mock 第三方服務(wù)

      假設(shè)我們在服務(wù)啟動時需要調(diào)用第三方的服務(wù)來獲取訪問口令

      GET $third_service_url/oauth2/api/v1/access_token?client_id=$clientId&client_secret=$clientPass

      返回值是 json :

      { 'token': '$token'}

      我們在本地做測試時并沒有部署這個第三方服務(wù), 我們可以用如下方法 mock 掉整個第三方服務(wù)的所有 API 調(diào)用, 例子代碼如下, 這里用到了以上所說的 http://www.

      package com.github.walterfan.hellotest;import lombok.extern.slf4j.Slf4j;import okhttp3.Headers;import okhttp3.OkHttpClient;import okhttp3.Request;import okhttp3.Response;import org.apache.http.HttpHeaders;import org.mockserver.integration.ClientAndServer;import org.mockserver.matchers.Times;import org.mockserver.model.HttpRequest;import org.mockserver.model.HttpResponse;import org.testng.annotations.AfterSuite;import org.testng.annotations.BeforeSuite;import org.testng.annotations.Test;import java.io.IOException;import static org.assertj.core.api.Assertions.assertThat;import static org.junit.Assert.assertEquals;import static org.mockserver.model.HttpResponse.response;import static org.testng.Assert.assertTrue;@Slf4jpublic class MockServerTest { public static final String ACCESS_TOKEN_URL = '/oauth2/api/v1/access_token'; public static final String ACCESS_TOKEN_RESP = '{ \'token\': \'abcd1234\'}'; private int listenPort; private OkHttpClient httpClient; //mock server private ClientAndServer mocker; public MockServerTest() { listenPort = 10086; httpClient = new OkHttpClient(); } //啟動 mock server @BeforeSuite public void startup() { mocker = ClientAndServer.startClientAndServer(listenPort); } //關(guān)閉 mock server @AfterSuite public void shutdown() { mocker.stop(true); } @Test public void testCheckHealth() throws IOException { HttpRequest mockReq = new HttpRequest().withMethod('GET').withPath(ACCESS_TOKEN_URL); HttpResponse mockResp = new HttpResponse().withStatusCode(200).withBody(ACCESS_TOKEN_RESP).withHeader(HttpHeaders.CONTENT_TYPE, 'application/json;charset=UTF-8'); //mock API 的返回 mocker.when(mockReq, Times.exactly(1)) .respond(mockResp); String theUrl = String.format('http://localhost:%d%s?%s' , listenPort, ACCESS_TOKEN_URL, 'client_id=test&client_secret=pass'); Request request = new Request.Builder() .url(theUrl) .build(); Response response = httpClient.newCall(request).execute(); assertTrue(response.isSuccessful()); Headers responseHeaders = response.headers(); for (int i = 0; i < responseHeaders.size(); i ) { log.info(responseHeaders.name(i) ': ' responseHeaders.value(i)); } //mock server 返回了之前設(shè)定的結(jié)果 String strResult = response.body().string(); log.info(' strResult: {}', strResult); assertEquals(strResult, ACCESS_TOKEN_RESP); //驗證 mock 的交互 mocker.verify(mockReq); } }

      輸出如下

      22:09:05.000 [main] DEBUG org.mockserver.client.netty.NettyHttpClient - Sending to: localhost/127.0.0.1:10086 request: {  'method' : 'PUT',  'path' : '/expectation',  'headers' : {    'host' : [ 'localhost:10086' ]
        },  'body' : {    'type' : 'STRING',    'string' : '{\n  \'httpRequest\' : {\n    \'method\' : \'GET\',\n    \'path\' : \'/oauth2/api/v1/access_token\'\n  },\n  \'httpResponse\' : {\n    \'statusCode\' : 200,\n    \'headers\' : {\n      \'Content-Type\' : [ \'application/json;charset=UTF-8\' ]\n    },\n    \'body\' : \'{ \\\'token\\\': \\\'abcd1234\\\'}\'\n  },\n  \'times\' : {\n    \'remainingTimes\' : 1,\n    \'unlimited\' : false\n  },\n  \'timeToLive\' : {\n    \'unlimited\' : true\n  }\n}',    'contentType' : 'text/plain; charset=utf-8'
        }
      }22:09:05.059 [nioEventLoopGroup-4-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.bytebuf.checkAccessible: true22:09:05.060 [nioEventLoopGroup-4-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@6fc3e61922:09:05.106 [nioEventLoopGroup-4-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 3276822:09:05.106 [nioEventLoopGroup-4-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 222:09:05.106 [nioEventLoopGroup-4-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 1622:09:05.106 [nioEventLoopGroup-4-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 822:09:06.003 [nioEventLoopGroup-3-1] INFO org.mockserver.mock.HttpStateHandler - creating expectation:
      
          {      'httpRequest' : {        'method' : 'GET',        'path' : '/oauth2/api/v1/access_token'
            },      'times' : {        'remainingTimes' : 1,        'unlimited' : false
            },      'timeToLive' : {        'unlimited' : true
            },      'httpResponse' : {        'statusCode' : 200,        'headers' : {          'Content-Type' : [ 'application/json;charset=UTF-8' ]
              },        'body' : '{ \'token\': \'abcd1234\'}'
            }
          }22:09:06.035 [nioEventLoopGroup-4-1] DEBUG io.netty.buffer.PoolThreadCache - Freed 3 thread-local buffer(s) from thread: nioEventLoopGroup-4-1
      22:09:06.106 [nioEventLoopGroup-3-2] INFO org.mockserver.mock.HttpStateHandler - request:    {      'method' : 'GET',      'path' : '/oauth2/api/v1/access_token',      'queryStringParameters' : {        'client_secret' : [ 'pass' ],        'client_id' : [ 'test' ]
            },      'headers' : {        'content-length' : [ '0' ],        'Connection' : [ 'Keep-Alive' ],        'User-Agent' : [ 'okhttp/3.8.0' ],        'Host' : [ 'localhost:10086' ],        'Accept-Encoding' : [ 'gzip' ]
            },      'keepAlive' : true,      'secure' : false
          }
      
       matched expectation:
      
          {      'method' : 'GET',      'path' : '/oauth2/api/v1/access_token'
          }22:09:06.114 [nioEventLoopGroup-3-2] INFO org.mockserver.mock.HttpStateHandler - returning response:
      
          {      'statusCode' : 200,      'headers' : {        'Content-Type' : [ 'application/json;charset=UTF-8' ],        'connection' : [ 'keep-alive' ]
            },      'body' : '{ \'token\': \'abcd1234\'}'
          } for request:
      
          {      'method' : 'GET',      'path' : '/oauth2/api/v1/access_token',      'queryStringParameters' : {        'client_secret' : [ 'pass' ],        'client_id' : [ 'test' ]
            },      'headers' : {        'content-length' : [ '0' ],        'Connection' : [ 'Keep-Alive' ],        'User-Agent' : [ 'okhttp/3.8.0' ],        'Host' : [ 'localhost:10086' ],        'Accept-Encoding' : [ 'gzip' ]
            },      'keepAlive' : true,      'secure' : false
          } for response action:
      
          {      'statusCode' : 200,      'headers' : {        'Content-Type' : [ 'application/json;charset=UTF-8' ]
            },      'body' : '{ \'token\': \'abcd1234\'}'
          }22:09:06.122 [main] INFO com.github.walterfan.hellotest.MockServerTest - Content-Type: application/json;charset=UTF-822:09:06.123 [main] INFO com.github.walterfan.hellotest.MockServerTest - connection: keep-alive22:09:06.123 [main] INFO com.github.walterfan.hellotest.MockServerTest - content-length: 2222:09:06.124 [main] INFO com.github.walterfan.hellotest.MockServerTest -  strResult: { 'token': 'abcd1234'}

      pom.xml 如下

      <?xml version='1.0' encoding='UTF-8'?><project xmlns='http://maven./POM/4.0.0' xmlns:xsi='http://www./2001/XMLSchema-instance' xsi:schemaLocation='http://maven./POM/4.0.0 http://maven./xsd/maven-4.0.0.xsd'> <modelVersion>4.0.0</modelVersion> <groupId>com.github.walterfan</groupId> <artifactId>hellotest</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>hellotest</name> <description>Demo project for Mock Test</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.SR4</spring-cloud.version> <okhttp.version>3.8.0</okhttp.version> <mock-server-version>5.3.0</mock-server-version> <maven-shade-plugin-version>2.1</maven-shade-plugin-version> <metrics.version>3.1.5</metrics.version> </properties> <dependencies> <dependency> <groupId>io.dropwizard.metrics</groupId> <artifactId>metrics-core</artifactId> <version>${metrics.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-contract-stub-runner</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-contract-verifier</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-wiremock</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <version>2.4.0</version> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>6.11</version> </dependency> <dependency> <groupId>org.mock-server</groupId> <artifactId>mockserver-netty</artifactId> <version>${mock-server-version}</version> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>${okhttp.version}</version> </dependency> <dependency> <groupId>com.github.tomakehurst</groupId> <artifactId>wiremock</artifactId> <version>2.12.0</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>

      參考資料

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多