溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

web前端單元測試之UI測試功能性代碼實例分析

發(fā)布時間:2022-08-05 14:09:34 來源:億速云 閱讀:178 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“web前端單元測試之UI測試功能性代碼實例分析”,在日常操作中,相信很多人在web前端單元測試之UI測試功能性代碼實例分析問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”web前端單元測試之UI測試功能性代碼實例分析”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

    UI測試:

    很容易理解,前端的項目終究還是會展示在瀏覽器上,那么所謂的UI測試也就是測試我們寫的代碼和預(yù)期渲染的UI是否一致而已。

    結(jié)合到我們現(xiàn)在實際項目,多數(shù)基于UI框架的情況(比如React),我們只需要跟隨官方提供的UI測試途徑即可,千萬別去直接搜索XX前端單元測試框架之類的。每個框架都有自己的特性,或多或少都會涉及到UI的渲染,比如虛擬DOM、生命周期之類。所以像React官方文檔提到的@testing-library/react(也有曾經(jīng)使用過比較優(yōu)秀的Enzyme),都會根據(jù)React框架特性提供相關(guān)的API例如:render、renderHook之類的。

    功能性代碼測試:

    簡單的說就是一段封裝的代碼,無論是一個函數(shù)或者是一個類。而這段代碼多數(shù)情況和UI無關(guān),我們只需要要關(guān)注功能實現(xiàn)的本身,最簡單的就是一個函數(shù),輸入什么參數(shù),返回什么值。

    功能性代碼的測試將是本文講述的重點,當(dāng)然我們需要一個測試框架提供支持,本文講述的Jest就是這種一個框架。

    在介紹Jest之前,還是先聊一聊何謂單元測試,進(jìn)行有效的單元測試。

    讓人聞風(fēng)喪膽的單元測試

    說起單元測試,尤其是對于前端行業(yè)的同學(xué)們來說,真的算的上是讓人聞風(fēng)喪膽,畢竟單測這個詞聽上去就是牛逼轟轟。

    也經(jīng)常有勇士會說,勞資React、antd一把梭哈,要個雞兒的單元測試。

    現(xiàn)實工作中,當(dāng)你接收了別人的代碼,大多數(shù)人可能會覺得在接手一座屎山,得想辦法怎么在屎山上進(jìn)行雕花,而且還得讓山不崩塌。阿西吧,勞資還不如重構(gòu)了。

    stop!又雙叒叕扯遠(yuǎn)了。

    好了,別管測試同學(xué)嘴里的什么黑盒、白盒、性能、功能。對我們大多數(shù)人而言,單元測試,不就是對代碼進(jìn)行測試么。怎么測試呢?

    代碼測試代碼

    可能你有點明白了,那么問題來了,挖掘機哪家強?sorry,問題是如何用代碼測試代碼?

    舉個簡單的例子,你寫個了函數(shù),判斷請求是否成功。因為不可抗拒的因素,你們公司團隊里面的后端同學(xué),每個人的接口對于成功返回的定義不一樣。有的是通過http 狀態(tài)碼200,有的根據(jù)返回值中的success是否為true,有的人返回了一個code字段,值可能為200也可能為"200"。

    現(xiàn)實就是這么殘酷,前端的小伙伴總是在寫這樣那樣的兼容方案。

    // utils.js
    /**
     * 判斷請求是否成功,成功返回,失敗提示失敗信息
     * @param {Object} response
     * @returns
     */
    export const requestSuccess = response => {
        if (
            response.data.success === true ||
            response.data.code === 'success' ||
            response.data.code === '1000' ||
            response.data.code === 200 ||
            response.data.code === '200'
        ) {
            return response;
        }
        message.error(response.data.message || '請求失敗');
    };

    對于這樣一個常見的函數(shù)而言,我們需要如何對其進(jìn)行單元測試呢?

    所謂測試,無非就是在特定場景下(我們可以先不用管這個場景是什么),輸入一些值,得到輸出值,然后跟期望輸出的值進(jìn)行比對,看是否一致,如果一致則表明這一條測試用例通過。

    看這個簡單的例子,我們不用管下面的testexpect方法具體是干什么的。

    // utils.test.js
    import { requestSuccess } from './utils.js'
    test('requestSuccess方法 請求正常測試用例', () => {
        const input = {
            data: {
                success: true,
                code: 200,
                message: '請求成功'
            }
        };
        const expectOutput = input;
        const output = requestSuccess(input);
        expect(output).toEqual(expectOutput);
    });

    在上面的案例中,我們來逐步分解:

    首先定義了一個輸入:input。

    然后將input作為參數(shù),調(diào)用了requestSuccess方法,本次調(diào)用得到另一個返回值output。

    最后就是判定,判定(也就是所謂的期望/斷言)得到的輸出值output等于期望的輸出值expectOutput。

    這是一段最基礎(chǔ)的,正常輸入值的單元測試代碼,我們可以總結(jié)出大概的步驟:

    • 1、定義輸入值

    • 2、將輸入值帶上,執(zhí)行代碼,得到輸出值

    • 3、對輸出值進(jìn)行斷言

    這個斷言就是說你覺得這個輸出值應(yīng)該是什么,也斷言這個輸出值和你期望輸出值匹配。當(dāng)然,實際輸出值和你的期望輸出可能不匹配,那就是表明你的這條用例執(zhí)行失敗。失敗的原因可能是你的源代碼有問題,也可能是你單元測試的用例代碼有問題。

    OK,我們了解了最基礎(chǔ)的單元測試。那么真正意義的單元測試應(yīng)該怎么寫呢?

    無非就是寫單元測試用例,定義各種輸入值,判斷和期望輸出是否一致,然后進(jìn)行分析修改。

    再回歸到上面的requestSuccess方法,上面的測試用例僅僅是驗證了正常情況下,當(dāng)然這種情況可能占大多數(shù),但是單元測試一般就是為了兼容小部分的異常場景。

    那么接下來,我們就來分析下一般意義上請求失敗場景的測試用例:

    // utils.test.js
    import { requestSuccess } from './utils';
    test('requestSuccess方法 請求失敗測試用例', () => {
        const input = {
            data: {
                success: false,
                message: '請求失敗'
            }
        };
        const output = requestSuccess(input); // 沒有返回值,output為undefine
        expect(output).toBeUndefined(); 
    });

    好了,到這里,有的同學(xué)說,請求正常、請求異常的情況都覆蓋了,單元測試完成,可以提交測試,然后進(jìn)行愉快的摸魚了。

    等等,事情沒有那么簡單。

    測試同學(xué)急急忙忙來找你了,說你的程序又崩了,頁面空白了。

    你讓測試同學(xué)給你復(fù)現(xiàn)了,一步一步debug。原來發(fā)現(xiàn),調(diào)用你requestSuccess方法的response參數(shù),盡然為一個空對象: {} 。

    你可能會直呼好家伙,后端不講武德?。ó?dāng)然可能性很多,可能并不是后端一個人的鍋),因為不可抗拒因素,你又得去改代碼,一邊改一邊罵。

    改完之后的源碼如下,然后你又得意的告訴測試同學(xué)已經(jīng)改完,沒有問題了。

    export const requestSuccess = response => {
        if (
            response.data?.success === true ||
            response.data?.code === 'success' ||
            response.data?.code === '1000' ||
            response.data?.code === 200 ||
            response.data?.code === '200'
        ) {
            return response;
        }
        message.error(response.data.message || '請求失敗');
    };

    結(jié)果不一會,測試同學(xué)說,你的程序又崩了,頁面空白了。

    你慌了,自言自語的說道,沒問題啊,勞資都寫了兼容了,讓測試同學(xué)給你復(fù)現(xiàn)了,一步一步debug。原來發(fā)現(xiàn),調(diào)用你requestSuccess方法的response參數(shù),盡然為undefined。你破口大罵,告訴測試是后端的鍋,是另一個前端瞎雞兒調(diào)用,和你無關(guān)。掰扯了一段時間,你又改了下你的代碼:

    // 當(dāng)然下面的代碼還是可以繼續(xù)優(yōu)化
    export const requestSuccess = response => {
        if (
            response?.data?.success === true ||
            response?.data?.code === 'success' ||
            response?.data?.code === '1000' ||
            response?.data?.code === 200 ||
            response?.data?.code === '200'
        ) {
            return response;
        }
        message.error(response.data.message || '請求失敗');
    };

    再回到單元測試的正題上,上面的那些異常情況,在實際項目運行中比比皆是。而除了配合測試同學(xué)發(fā)現(xiàn)bug然后修改之外,我們在單元測試的時候即可發(fā)現(xiàn),并優(yōu)化自己的代碼。

    例如requestSuccess針對這個方法而言,我們先不用去管實際調(diào)用時候什么請求成功、請求失敗,只去針對這個方法本身,調(diào)用requestSuccess方法的參數(shù)可能性是非常多的,各種類型的,所以我們可以以每一種類型的輸入值作為一條測試用例。

    // utils.test.js
    import { requestSuccess } from './utils';
    // 這個describe可以不用糾結(jié),理解成幾份用例的集合,只是統(tǒng)一為異常輸入的描述
    describe('requestSuccess方法異常輸入測試用例', () => {
        test('response為空對象測試', () => {
            const input = {};
            const output = requestSuccess(input);
            expect(output).toBeUndefined();
        });
        test('response為undefined測試', () => {
            const output = requestSuccess();
            expect(output).toBeUndefined();
        });
        test('response為Number類型測試', () => {
            const output = requestSuccess(123);
            expect(output).toBeUndefined();
        });
    });

    在寫了這么多的異常輸入的測試用例之后,你會發(fā)現(xiàn)你一開始寫的requestSuccess不夠強大,導(dǎo)致單元測試用例執(zhí)行失敗,所以你可以一遍又一遍的修改你的源碼,直至測試用例都通過。

    總結(jié): 如何進(jìn)行有效的單元測試,最簡單的做法就是考慮各種異常/邊界輸入值,編寫相應(yīng)的測試用例,通過單元測試的執(zhí)行,優(yōu)化你的代碼。

    當(dāng)然做好單元測試,并不僅僅只是說考慮各種異常輸入即可,實際還會涉及到開發(fā)時候 的考慮(比如常說的測試驅(qū)動開發(fā)之類的)以及非常多的實現(xiàn)細(xì)節(jié),這個可能就需要你慢慢的理解了。

    Jest介紹

    官方鏈接

    Jest is a delightful JavaScript Testing Framework with a focus on simplicity.

    It works with projects using: Babel, TypeScript, Node, React, Angular, Vue and more!

    官方的介紹就是上面2段話,就是說jest是一個讓人愉悅的js測試框架,專注于簡單性??梢耘浜蟗abel、ts、node、react、angular、vue等其他庫 一起使用。

    我們前文提及的什么describe、test、expect方法等等在Jest中都有相應(yīng)的api。

    一、基礎(chǔ)教程

    安裝

    可以使用yarn或者npm進(jìn)行安裝

    yarn add jest -D | npm i jest -D

    源碼開發(fā)

    這里舉了一個簡單的例子,實際組件開發(fā)需要使用ts以及其他UI測試框架。

    例如開發(fā)一個基礎(chǔ)方法,返回2個參數(shù)的和。文件名為sum.ts

    // sum.js
    function sum(a, b) {
      return a + b;
    }
    export default sum;

    測試用例編寫

    首先我們根據(jù)上面的目標(biāo)文件(sum.js)創(chuàng)建一個測試用例文件-- sum.test.js, 測試用例文件名稱統(tǒng)一為*.test.js(后綴根據(jù)實際場景區(qū)分為.js或者.ts或者.tsx)

    // sum.test.js
    import sum from './sum';
    test('adds 1 + 2 to equal 3', () => {
      expect(sum(1, 2)).toBe(3);
    });

    開始測試

    添加下面的部分到你的package.json中

    {
      "scripts": {
        "test": "jest"
      }
    }

    最后,執(zhí)行yarn testornpm run test命令,Jest將會打印如下信息:

    PASS  ./sum.test.js
    ? adds 1 + 2 to equal 3 (5ms)

    就這樣,基于jest的一個基礎(chǔ)單元測試流程走好了,Jest的單元測試核心就是在test方法的第二個參數(shù)里面,expect方法返回一個期望對象,通過匹配器(例如toBe)進(jìn)行斷言,期望是否和你預(yù)期的一致,和預(yù)期一致則單元測試通過,不一致則測試無法通過,需要排除問題然后繼續(xù)進(jìn)行單元測試。

    更多的配置以及命令行參數(shù)請參考官方文檔下面開始講解一些核心API。

    二、核心API

    全局方法

    在你的測試文件中,Jest將下面這些方法和對象放置全局環(huán)境,所以你無需再顯式的去require或者import。當(dāng)然,如果你更喜歡顯式的import,也可以使用例如import { describe, expect, it } from '@jest/globals'的方式。

    test(name, fn, timeout)

    test有別名it,兩個方法是一樣的。

    第一個參數(shù)是你想要描述的測試用例名稱; 第二個參數(shù)是包含測試期望的函數(shù),也是測試用例的核心。第三個參數(shù)(可選)是超時時間,也就是超過多久將會取消測試(默認(rèn)是5秒鐘)

    Note: 如果fn返回的是個promise,Jest在完成測試前將會等待Promise達(dá)到resolved狀態(tài)。具體情況本文下面也會講到如何對異步代碼進(jìn)行測試。

    匹配器

    Jest使用匹配器可以讓你使用各種方式測試你的代碼,Jest中的匹配器實際就是expect方法返回的期望對象中包含的相關(guān)方法。官方提供了非常多的匹配器,完善的學(xué)習(xí)請查看官方文檔。

    下面摘選了幾個最常見的匹配器方法。

    1、.toBe(value)

    toBe是最簡單最基礎(chǔ)的匹配器,就是判定是否精確匹配,toBe方法使用了Object.is方法來測試精確相等。

    Object.is方法的判定準(zhǔn)則可以參考這里

    test('two plus two is four', () => {
      expect(2 + 2).toBe(4);
    });

    在測試中,你有時需要區(qū)分 undefined、 null,和 false,但有時你又不需要區(qū)分。 Jest 讓你明確你想要什么。

    toBeNull 只匹配 null

    toBeUndefined 只匹配 undefined

    toBeDefinedtoBeUndefined 相反

    toBeTruthy 匹配任何 if 語句為真

    toBeFalsy 匹配任何 if 語句為假

    2、.not

    非常容易理解,一般就是反向測試

    test('two plus two is four', () => {
      expect(2 + 2).not.toBe(4);
    });

    3、.toEqual

    遞歸檢查對象或數(shù)組的每個字段

    和上面的toBe進(jìn)行對比,toBe對比倆對象對比的是內(nèi)存地址,toEqual比的是屬性值。

    test('object assignment', () => {
      const data1 = { one: 1, two: 2 };
      const data2 = { one: 1, two: 2 };
      expect(data1).toBe(data2); // 測試失敗
      expect(data1).toEqual(data2);// 測試通過
    });

    4、expect.assertions

    expect.assertions(number) 驗證一定數(shù)量的斷言在某個測試用例中被調(diào)用。通常在異步代碼測試中非常有用,目的就是為了確保所有的斷言被真實的調(diào)用了。

    比如下面這個例子,如果去掉了expect.assertions(2), 那么測試用例會通過測試,但實際的需求應(yīng)該是失敗的,因為我們最初的期望是catch中的斷言也會被調(diào)用。

    而有了expect.assertions(2),Jest會判斷斷言實際調(diào)用的數(shù)量和我們預(yù)期是否一致,如果不一致則說明測試失敗。

    test('doAsync calls both callbacks', () => {
      expect.assertions(2);
      return Promise.resolve(123).then((data: number) => {
        expect(data).toBe(123);
        return; // 例如手抖寫了return
        // 因為某些原因下面的代碼沒有執(zhí)行
        throw new Error('報錯了');
      }).catch(err => {
        expect(err).toBe('報錯了');
      });
    });

    異步代碼測試

    在JavaScript中執(zhí)行異步代碼是很常見的。 當(dāng)你有以異步方式運行的代碼時,Jest 需要知道當(dāng)前它測試的代碼是否已執(zhí)行完成,然后它可以轉(zhuǎn)移到另一個測試。 Jest有若干方法處理這種情況。

    回調(diào)

    最常見的異步模式就是回調(diào)函數(shù),例如下面的setTimeout方法,下面的測試用例無法通過,原因是Jest無法知道callback具體的調(diào)用時間,所以會造成測試已經(jīng)結(jié)束,但是setTimeout的callback還沒有執(zhí)行。

    test('the data is peanut butter', () => {
      function callback(data: string) {
        expect(data).toBe('peanut butter');
      }
      setTimeout(() => {
        callback('peanut butter');
      }, 2000);
    });

    想要解決上面的問題,非常簡單,很容易就會聯(lián)想到消息通知機制,也就是在callback調(diào)用的時候通知Jest,表示當(dāng)前測試用例通過,可以跑下一個測試。

    test方法的第二個參數(shù)fn,可以添加一個done參數(shù),done是一個方法,調(diào)用了done,就是通知Jest測試完成,當(dāng)然如果你的測試用例中的done方法始終沒有執(zhí)行,那么你的測試也會失?。ǔ瑫r),所以最好的方式就是加上try catch。

    test('the data is peanut butter', done => {
      function callback(data: string) {
        try {
          expect(data).toBe('peanut butter');
          done();
        } catch (err) {
          done(err);
        }
      }
      setTimeout(() => {
        callback('peanut butter');
      }, 2000);
    });

    Promise

    如果你的代碼使用了Promise, Jest提供了一種更加直接的方式去處理異步測試。在test第二個參數(shù)fn中直接返回一個promise,Jest就會等待這個promise達(dá)到resolved狀態(tài),如果達(dá)到了fulfilled狀態(tài),測試將會自動失敗。

    例如這個案例,此測試用例能夠正常的通過

    test('promise resolved', () => {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve('resolved');
        }, 2000);
      }).then((data: string) => {
        expect(data).toBe('resolved');
      });
    });

    如果promise fulfilled如下,則測試用例會跑失敗

    test('promise fulfilled', () => {
      return Promise.reject('fulfilled').then((data: string) => {
        expect(data).toBe('fulfilled');
      })
    });

    當(dāng)然我們也可以使用catch方法,例如下面這個例子,測試用例就能夠正常的通過。

    test('promise fulfilled', () => {
      expect.assertions(1);
      return Promise.reject('fulfilled').catch(err => {
        expect(err).toMatch('fulfilled');
      });
    });

    promise代碼可以配合匹配器.resolvesrejects一起使用,使用案例如下:

    test('promise resolved', () => {
      return expect(Promise.resolve('resolved')).resolves.toBe('resolved');
    });
    test('promise fulfilled', () => {
      return expect(Promise.reject('fulfilled')).rejects.toMatch('fulfilled');
    });

    Async/Await

    如果你的代碼使用了Promise, Jest提供了一種更加直接的方式去處理異步測試。在test第二個參數(shù)fn中直接返回一個promise,Jest就會等待這個promise達(dá)到resolved狀態(tài),如果達(dá)到了fulfilled狀態(tài),測試將會自動失敗。

    const TEN = 10;
    const BASE = 5;
    function fetchData () {
      return new Promise((resolve, reject) => {
        const random = Math.random() * TEN;
        random > BASE ? resolve(random) : reject(random);
      });
    }
    test('the random promise', async () => {
      expect.assertions(1);
      try {
        const random = await fetchData();
        expect(random).toBeGreaterThan(BASE);
      } catch (e) {
        expect(e).toBeLessThanOrEqual(BASE);
      }
    });

    Mock Functions

    Mock 函數(shù)簡單的說就是模擬一個函數(shù),這個功能很強大,例如nodejs中沒有DOM/BOM,及時是jsdom也會缺少一些api,那么我們可以使用mock函數(shù)來進(jìn)行一些測試,具體暫不詳細(xì)說明。

    有兩種方法可以模擬函數(shù):要么在測試代碼中創(chuàng)建一個 mock 函數(shù),要么編寫一個手動 mock來覆蓋模塊依賴。

    Mock Functions Doc

    使用 mock 函數(shù)

    假設(shè)我們要測試函數(shù) forEach 的內(nèi)部實現(xiàn),這個函數(shù)為傳入的數(shù)組中的每個元素調(diào)用一次回調(diào)函數(shù)。

    function forEach(items, callback) {
      for (let index = 0; index < items.length; index++) {
        callback(items[index]);
      }
    }

    為了測試此函數(shù),我們可以使用一個 mock 函數(shù),然后檢查 mock 函數(shù)的狀態(tài)來確?;卣{(diào)函數(shù)如期調(diào)用。

    const mockCallback = jest.fn(x => 42 + x);
    forEach([0, 1], mockCallback);
    // 此 mock 函數(shù)被調(diào)用了兩次
    expect(mockCallback.mock.calls.length).toBe(2);
    // 第一次調(diào)用函數(shù)時的第一個參數(shù)是 0
    expect(mockCallback.mock.calls[0][0]).toBe(0);
    // 第二次調(diào)用函數(shù)時的第一個參數(shù)是 1
    expect(mockCallback.mock.calls[1][0]).toBe(1);
    // 第一次函數(shù)調(diào)用的返回值是 42
    expect(mockCallback.mock.results[0].value).toBe(42);
    .mock 屬性

    所有的 mock 函數(shù)都有這個特殊的 .mock屬性,它保存了關(guān)于此函數(shù)如何被調(diào)用、調(diào)用時的返回值的信息。 .mock 屬性還追蹤每次調(diào)用時 this的值,所以我們同樣可以也檢視

    Mock 的返回值

    const filterTestFn = jest.fn();
    // Make the mock return `true` for the first call,
    // and `false` for the second call
    filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);
    const result = [11, 12].filter(num =&gt; filterTestFn(num));
    console.log(result);
    // &gt; [11]
    console.log(filterTestFn.mock.calls);
    // &gt; [ [11], [12] ]

    Mocking Modules

    在Jest單元測試的真實場景中,會有很多的數(shù)據(jù)來自接口,但是Jest并不推薦直接在測試代碼中去調(diào)用真實的接口,因為這可能會讓測試變得非常緩慢而且脆弱,所以jest.fn().mockResolvedValue提供了mock接口的方式,使用假數(shù)據(jù)進(jìn)行測試。

    test('async test', async () =&gt; {
      const asyncMock = jest.fn().mockResolvedValue(23);
      const result = await asyncMock(); // 43
      expect(result).toBe(23);
    });

    快照測試

    每當(dāng)你想要確保你的UI不會有意外的改變,快照測試是非常有用的工具。

    一點典型的快照測試案例就是一個移動端的app渲染一個UI組件,拍下快照,然后將其與之前測試存儲的參考快照文件進(jìn)行對比,如果2個快照不匹配的話測試就會失敗。簡單的來說就是對比前后2次組件渲染的照片,這個測試方法非常適合React這類UI框架。

    Jest快照測試第一次會生成一個快照文件,就像下面這樣

    // Jest Snapshot v1, https://goo.gl/fbAQLP
    exports[`component--Loading 單元測試start 1`] = `
    <div
      className="container"
    >
      <div
        className="inner"
      >
        <div
          className="boxLoading"
        />
        <div
          className="title"
        >
          努力加載中...
        </div>
      </div>
    </div>
    `;

    我們可以在Jest 命令中加入--updateSnapshot,這樣快照有跟新的話會跟新快照文件而不是直接讓整個快照測試失敗了。

    建議把快照測試文件提交到git,快照測試文件也可以進(jìn)行code-review。

    到此,關(guān)于“web前端單元測試之UI測試功能性代碼實例分析”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI