<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一篇文章搞定前端單元測(cè)試框架 Jest

          共 9979字,需瀏覽 20分鐘

           ·

          2022-05-14 07:17

          前言

          雖然有很多前端團(tuán)隊(duì)壓根現(xiàn)在甚至未來(lái)都不太可能使用單元測(cè)試,包括我自己的團(tuán)隊(duì),原因無(wú)非是耽誤時(shí)間,開(kāi)發(fā)任務(wù)本身就比較重等等理由。

          但是我覺(jué)得一味的圖快,永遠(yuǎn)是飲鴆止渴,陷入惡性循環(huán),項(xiàng)目快 \--> 代碼爛 \--> 修改和加功能花費(fèi)更多的時(shí)間和精力 \--> 來(lái)不及做優(yōu)化必須更快 \--> 項(xiàng)目快 \--> 代碼爛 \--> ... 無(wú)限循環(huán)。

          這就是做單元測(cè)試我認(rèn)為最重要的原因就是,重構(gòu)代碼時(shí),確認(rèn)功能沒(méi)有問(wèn)題,不怕人員流動(dòng),功能遷移,最主要的是跟產(chǎn)品撕b,測(cè)試用例就是最好的證據(jù)??。

          業(yè)務(wù)項(xiàng)目用不到的話,如果你寫(xiě)庫(kù),不寫(xiě)單測(cè),可能用的同學(xué)都會(huì)有所顧忌,所以會(huì)寫(xiě)單測(cè)是對(duì)高級(jí)以上前端必備的技能。

          單元測(cè)試框架基本原理

          例如如下的一個(gè)測(cè)試用例,感受一下基本的樣子長(zhǎng)啥,我們后面會(huì)把其中用到的方法自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單版本

          //?意思是字符串hello是否包含ll
          test('測(cè)試字符串中是否包含?ll'),?()?=>?{
          ????expect(findStr('hello')).toMatch('ll')
          })

          function?findStr(str){
          ????return?`${str}?world`
          }
          復(fù)制代碼

          我們可以簡(jiǎn)單的實(shí)現(xiàn)一下上面測(cè)試用例用到的方法,test、expect、toMatch,這樣就算掌握了基本的測(cè)試框架原理

          test

          function?test(desc,?fn){
          ????try{
          ????????fn();
          ????????console.log(`???通過(guò)測(cè)試用例`)
          ????}catch{
          ????????console.log(`??沒(méi)有通過(guò)測(cè)試用例`)
          ????}
          }
          復(fù)制代碼

          expect、toMatch

          function?expect(ret){
          ????return?{
          ????????toMatch(expRet){
          ????????????if(typeof?ret?===?'string'){?throw?Error('')?}
          ????????????if(!ret.includes(expRet)){?throw?Error('')?}
          ????????}
          ????}
          }
          復(fù)制代碼

          jest基本配置

          必備工具:

          $?npm?i?-D?jest?babel-jest?ts-jest?@types/jest
          復(fù)制代碼

          參考配置jest.config.js,測(cè)試文件均放在tests目錄中:下面的testRegex表示匹配的tests文件夾下的以test或者spec結(jié)尾的jsx或者tsx文件

          module.exports?=?{
          ??transform:?{
          ????'^.+\\.tsx?$':?'ts-jest',
          ??},
          ??testRegex:?'(/tests/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
          ??moduleFileExtensions:?['tsx',?'ts',?'js',?'jsx',?'json',?'node'],
          };
          復(fù)制代碼

          最后在package.json的scripts中加入

          {
          ????test:?"jest"
          ????//?如果要測(cè)試覆蓋率,后面加上--coverage
          ????//?如果要監(jiān)聽(tīng)所有測(cè)試文件?--watchAll
          }
          復(fù)制代碼

          匹配器

          匹配器(Matchers)是Jest中非常重要的一個(gè)概念,它可以提供很多種方式來(lái)讓你去驗(yàn)證你所測(cè)試的返回值。舉個(gè)例子就明白什么是匹配器了。

          這里的匹配器掃一眼即可,大概知道有那么回事,用的時(shí)候查你想要的匹配器就行,不用刻意去記憶。

          相等匹配,這是我們最常用的匹配規(guī)則

          test('two?plus?two?is?four',?()?=>?{
          ??expect(2?+?2).toBe(4);
          });
          復(fù)制代碼

          在這段代碼中 expact(2 + 2) 將返回我們期望的結(jié)果,通常情況下我們只需要調(diào)用expect就可以,括號(hào)中的可以是一個(gè)具有返回值的函數(shù),也可以是表達(dá)式。后面的toBe 就是一匹配器。

          下面列舉一些常用的匹配器:

          普通匹配器

          • toBe:object.is 相當(dāng)于 ===
          test('測(cè)試加法?3?+?7',?()?=>?{
          ??//?toBe?匹配器?matchers?object.is?相當(dāng)于?===
          ??expect(10).toBe(10)
          })
          復(fù)制代碼
          • toEqual:內(nèi)容相等,匹配內(nèi)容,不匹配引用
          test('toEqual?匹配器',?()?=>?{
          ??//?toEqual?匹配器?只會(huì)匹配內(nèi)容,不會(huì)匹配引用
          ??const?a?=?{?one:?1?}
          ??expect(a).toEqual({?one:?1?})
          })
          復(fù)制代碼

          與真假有關(guān)的匹配器

          • 真假
          • toBeNull:只匹配 Null
          test('toBeNull?匹配器',?()?=>?{
          ??//?toBeNull
          ??const?a?=?null
          ??expect(a).toBeNull()
          })
          復(fù)制代碼

          toBeUndefined:只匹配 undefined

          test('toBeUndefined?匹配器',?()?=>?{
          ??const?a?=?undefined
          ??expect(a).toBeUndefined()
          })
          復(fù)制代碼

          toBeDefined:與 toBeUndefined 相反,這里匹配 null 是通過(guò)的

          test('toBeDefined?匹配器',?()?=>?{
          ??const?a?=?null
          ??expect(a).toBeDefined()
          })
          復(fù)制代碼

          toBeTruthy:匹配任何 if 語(yǔ)句為 true

          test('toBeTruthy?匹配器',?()?=>?{
          ??const?a?=?1
          ??expect(a).toBeTruthy()
          })
          復(fù)制代碼

          toBeFalsy:匹配任何 if 語(yǔ)句為 false

          test('toBeFalsy?匹配器',?()?=>?{
          ??const?a?=?0
          ??expect(a).toBeFalsy()
          })
          復(fù)制代碼

          not:取反

          test('not?匹配器',?()?=>?{
          ??const?a?=?1
          ??//?以下兩個(gè)匹配器是一樣的
          ??expect(a).not.toBeFalsy()
          ??expect(a).toBeTruthy()
          })
          復(fù)制代碼

          數(shù)字

          toBeGreaterThan:大于

          test('toBeGreaterThan',?()?=>?{
          ??const?count?=?10
          ??expect(count).toBeGreaterThan(9)
          })
          復(fù)制代碼

          toBeLessThan:小于

          test('toBeLessThan',?()?=>?{
          ??const?count?=?10
          ??expect(count).toBeLessThan(12)
          })
          復(fù)制代碼

          toBeGreaterThanOrEqual:大于等于

          test('toBeGreaterThanOrEqual',?()?=>?{
          ??const?count?=?10
          ??expect(count).toBeGreaterThanOrEqual(10)?//?大于等于?10
          })
          復(fù)制代碼

          toBeLessThanOrEqual:小于等于

          test('toBeLessThanOrEqual',?()?=>?{
          ??const?count?=?10
          ??expect(count).toBeLessThanOrEqual(10)?//?小于等于?10
          })
          復(fù)制代碼

          toBeCloseTo:計(jì)算浮點(diǎn)數(shù)

          test('toBeCloseTo',?()?=>?{
          ??const?firstNumber?=?0.1
          ??const?secondNumber?=?0.2
          ??expect(firstNumber?+?secondNumber).toBeCloseTo(0.3)?//?計(jì)算浮點(diǎn)數(shù)
          })
          復(fù)制代碼

          字符串

          toMatch:匹配某個(gè)特定項(xiàng)字符串,支持正則

          test('toMatch',?()?=>?{
          ??const?str?=?'http://www.zsh.com'
          ??expect(str).toMatch('zsh')
          ??expect(str).toMatch(/zsh/)
          })
          復(fù)制代碼

          數(shù)組

          toContain:匹配是否包含某個(gè)特定項(xiàng)

          test('toContain',?()?=>?{
          ??const?arr?=?['z',?'s',?'h']
          ??const?data?=?new?Set(arr)
          ??expect(data).toContain('z')
          })
          復(fù)制代碼

          異常

          toThrow

          const?throwNewErrorFunc?=?()?=>?{
          ??throw?new?Error('this?is?a?new?error')
          }
          test('toThrow',?()?=>?{
          ??//?拋出的異常也要一樣才可以通過(guò),也可以寫(xiě)正則表達(dá)式
          ??expect(throwNewErrorFunc).toThrow('this?is?a?new?error')
          })
          復(fù)制代碼

          測(cè)試異步代碼

          假設(shè)請(qǐng)求函數(shù)如下

          const fethUserInfo = fetch('http://xxxx')
          復(fù)制代碼

          測(cè)試異步代碼有好幾種方式,我就推薦一種我認(rèn)為比較常用的方式

          //?fetchData.test.js

          //?測(cè)試promise成功需要加.resolves方法
          test('the?data?is?peanut?butter',?async?()?=>?{
          ????await?expect(fethUserInfo()).resolves.toBe('peanut?butter');
          });

          //?測(cè)試promise成功需要加.rejects方法
          test('the?fetch?fails?with?an?error',?async?()?=>?{
          ????await?expect(fethUserInfo()).rejects.toMatch('error');
          });
          復(fù)制代碼

          作用域

          jest提供一個(gè)describle函數(shù)來(lái)分離各個(gè)test測(cè)試用例,就是把相關(guān)的代碼放到一類(lèi)分組中,這么簡(jiǎn)單,看個(gè)例子就懂了。

          //?分組一
          describe('Test?xxFunction',?()?=>?{
          ??test('Test?default?return?zero',?()?=>?{
          ??????expect(xxFunction()).toBe(0)
          ??})

          ??//?...其它test
          })

          //?分組二
          describe('Test?xxFunction2',?()?=>?{
          ??test('Pass?3?can?return?9',?()?=>?{
          ??????expect(xxFunction2(3)).toBe(9)
          ??})

          ??//?...其它test
          })
          復(fù)制代碼

          鉤子函數(shù)

          jest中有4個(gè)鉤子函數(shù)

          • beforeAll:所有測(cè)試之前執(zhí)行
          • afterAll:所有測(cè)試執(zhí)行完之后
          • beforeEach:每個(gè)測(cè)試實(shí)例之前執(zhí)行
          • afterEach:每個(gè)測(cè)試實(shí)例完成之后執(zhí)行

          我們舉例來(lái)說(shuō)明為什么需要他們。

          index.js 中寫(xiě)入一些待測(cè)試方法

          export?default?class?compute?{
          ??constructor()?{
          ????this.number?=?0
          ??}
          ??addOne()?{
          ????this.number?+=?1
          ??}
          ??addTwo()?{
          ????this.number?+=?2
          ??}
          ??minusOne()?{
          ????this.number?-=?1
          ??}
          ??minusTwo()?{
          ????this.number?-=?2
          ??}
          }
          復(fù)制代碼

          假如我們要 在 index.test.js 中寫(xiě)測(cè)試實(shí)例

          import?compute?from?'./index'

          const?Compute?=?new?compute()

          test('測(cè)試?addOne',?()?=>?{
          ??Compute.addOne()
          ??expect(Compute.number).toBe(1)
          })

          test('測(cè)試?minusOne',?()?=>?{
          ??Compute.minusOne()
          ??expect(Compute.number).toBe(0)
          })
          復(fù)制代碼
          • 這里兩個(gè)測(cè)試實(shí)例相互之間影響了,共用了一個(gè)computet實(shí)例,我們可以將const Compute = new compute()放在beforEach里面就可以解決了,每次測(cè)試實(shí)例之前先重新new compute

          • 同理,你想在每個(gè)test測(cè)試完畢后單獨(dú)運(yùn)行什么可以放入到afterEach

          我們接著看一下什么情況下使用beforeAll,假如我們測(cè)試數(shù)據(jù)庫(kù)數(shù)據(jù)是否保存正確

          • 我們?cè)跍y(cè)試最開(kāi)始,也就是?beforeAll生命周期里,?新增1條數(shù)據(jù)到數(shù)據(jù)庫(kù)里
          • 測(cè)試完后,也就是?afterAll周期里,?刪除之前添加的數(shù)據(jù)
          • 最后利用全局作用域?afterAll?確認(rèn)數(shù)據(jù)庫(kù)是否還原成初始狀態(tài)

          這里說(shuō)到

          //?模擬數(shù)據(jù)庫(kù)
          const?userDB?=?[
          ??{?id:?1,?name:?'小明'?},
          ??{?id:?2,?name:?'小花'?},
          ]

          //?新增數(shù)據(jù)
          const?insertTestData?=?data?=>?{
          ??//?userDB,push數(shù)據(jù)
          }

          //?刪除數(shù)據(jù)
          const?deleteTestData?=?id?=>?{
          ??//?userDB,delete數(shù)據(jù)
          }

          //?全部測(cè)試完
          afterAll(()?=>?{
          ??console.log(userDB)
          })

          describe('Test?about?user?data',?()?=>?{

          ??beforeAll(()?=>?{
          ??????insertTestData({?id:?99,?name:?'CS'?})
          ??})
          ??afterAll(()?=>?{
          ??????deleteTestData(99)
          ??})

          })
          復(fù)制代碼

          jest里的Mock

          為什么要使用Mock函數(shù)?

          在項(xiàng)目中,經(jīng)常會(huì)碰見(jiàn)A模塊掉B模塊的方法。并且,在單元測(cè)試中,我們可能并不需要關(guān)心內(nèi)部調(diào)用的方法的執(zhí)行過(guò)程和結(jié)果,只想知道它是否被正確調(diào)用即可,甚至?xí)付ㄔ摵瘮?shù)的返回值。此時(shí),就需要mock函數(shù)了。

          Mock函數(shù)提供的以下三種特性,在我們寫(xiě)測(cè)試代碼時(shí)十分有用:

          • 捕獲函數(shù)調(diào)用情況
          • 設(shè)置函數(shù)返回值
          • 改變函數(shù)的內(nèi)部實(shí)現(xiàn)

          jest.fn()

          jest.fn()是創(chuàng)建Mock函數(shù)最常用的方式。

          test('測(cè)試jest.fn()',?()?=>?{
          ??let?mockFn?=?jest.fn();
          ??let?result?=?mockFn(1);

          ??//?斷言mockFn被調(diào)用
          ??expect(mockFn).toBeCalled();
          ??//?斷言mockFn被調(diào)用了一次
          ??expect(mockFn).toBeCalledTimes(1);
          ??//?斷言mockFn傳入的參數(shù)為1
          ??expect(mockFn).toHaveBeenCalledWith(1);
          })
          復(fù)制代碼

          jest.fn()所創(chuàng)建的Mock函數(shù)還可以設(shè)置返回值定義內(nèi)部實(shí)現(xiàn)返回Promise對(duì)象

          test('測(cè)試jest.fn()返回固定值',?()?=>?{
          ??let?mockFn?=?jest.fn().mockReturnValue('default');
          ??//?斷言mockFn執(zhí)行后返回值為default
          ??expect(mockFn()).toBe('default');
          })

          test('測(cè)試jest.fn()內(nèi)部實(shí)現(xiàn)',?()?=>?{
          ??let?mockFn?=?jest.fn((num1,?num2)?=>?{
          ????return?num1?*?num2;
          ??})
          ??//?斷言mockFn執(zhí)行后返回100
          ??expect(mockFn(10,?10)).toBe(100);
          })

          test('測(cè)試jest.fn()返回Promise',?async?()?=>?{
          ??let?mockFn?=?jest.fn().mockResolvedValue('default');
          ??let?result?=?await?mockFn();
          ??//?斷言mockFn通過(guò)await關(guān)鍵字執(zhí)行后返回值為default
          ??expect(result).toBe('default');
          ??//?斷言mockFn調(diào)用后返回的是Promise對(duì)象
          ??expect(Object.prototype.toString.call(mockFn())).toBe("[object?Promise]");
          })
          復(fù)制代碼

          2. jest.mock()

          fetch.js文件夾中封裝的請(qǐng)求方法可能我們?cè)谄渌K被調(diào)用的時(shí)候,并不需要進(jìn)行實(shí)際的請(qǐng)求(請(qǐng)求方法已經(jīng)通過(guò)單測(cè)或需要該方法返回非真實(shí)數(shù)據(jù))。此時(shí),使用jest.mock()去mock整個(gè)模塊是十分有必要的。

          下面我們?cè)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">src/fetch.js的同級(jí)目錄下創(chuàng)建一個(gè)src/events.js

          import?fetch?from?'./fetch';

          export?default?{
          ??async?getPostList()?{
          ????return?fetch.fetchPostsList(data?=>?{
          ??????console.log('fetchPostsList?be?called!');
          ??????//?do?something
          ????});
          ??}
          }
          復(fù)制代碼
          import?events?from?'../src/events';
          import?fetch?from?'../src/fetch';

          jest.mock('../src/fetch.js');

          test('mock?整個(gè)?fetch.js模塊',?async?()?=>?{
          ??expect.assertions(2);
          ??await?events.getPostList();
          ??expect(fetch.fetchPostsList).toHaveBeenCalled();
          ??expect(fetch.fetchPostsList).toHaveBeenCalledTimes(1);
          });
          復(fù)制代碼

          在測(cè)試代碼中我們使用了jest.mock('../src/fetch.js')去mock整個(gè)fetch.js模塊。如果注釋掉這行代碼,執(zhí)行測(cè)試腳本時(shí)會(huì)出現(xiàn)以下報(bào)錯(cuò)信息

          從這個(gè)報(bào)錯(cuò)中,我們可以總結(jié)出一個(gè)重要的結(jié)論:

          在jest中如果想捕獲函數(shù)的調(diào)用情況,則該函數(shù)必須被mock或者spy!

          3. jest.spyOn()

          jest.spyOn()方法同樣創(chuàng)建一個(gè)mock函數(shù),但是該mock函數(shù)不僅能夠捕獲函數(shù)的調(diào)用情況,還可以正常的執(zhí)行被spy的函數(shù)。實(shí)際上,jest.spyOn()jest.fn()的語(yǔ)法糖,它創(chuàng)建了一個(gè)和被spy的函數(shù)具有相同內(nèi)部代碼的mock函數(shù)。

          上圖是之前jest.mock()的示例代碼中的正確執(zhí)行結(jié)果的截圖,從shell腳本中可以看到console.log('fetchPostsList be called!');這行代碼并沒(méi)有在shell中被打印,這是因?yàn)橥ㄟ^(guò)jest.mock()后,模塊內(nèi)的方法是不會(huì)被jest所實(shí)際執(zhí)行的。這時(shí)我們就需要使用jest.spyOn()

          //?functions.test.js

          import?events?from?'../src/events';
          import?fetch?from?'../src/fetch';

          test('使用jest.spyOn()監(jiān)控fetch.fetchPostsList被正常調(diào)用',?async()?=>?{
          ??expect.assertions(2);
          ??const?spyFn?=?jest.spyOn(fetch,?'fetchPostsList');
          ??await?events.getPostList();
          ??expect(spyFn).toHaveBeenCalled();
          ??expect(spyFn).toHaveBeenCalledTimes(1);
          })
          復(fù)制代碼

          執(zhí)行npm run test后,可以看到shell中的打印信息,說(shuō)明通過(guò)jest.spyOn()fetchPostsList被正常的執(zhí)行了。

          快照

          快照就是對(duì)你對(duì)比的數(shù)據(jù)會(huì)存一份副本,啥意思呢,我們舉個(gè)例子:

          這是index.js

          export?const?data2?=?()?=>?{
          ??return?{
          ????name:?'zhangsan',
          ????age:?26,
          ????time:?new?Date()
          ??}
          }
          復(fù)制代碼

          index.test.js 中寫(xiě)入一些測(cè)試實(shí)例

          import?{?data2?}?from?"./index"

          it('測(cè)試快照?data2',?()?=>?{
          ??expect(data2()).toMatchSnapshot({
          ????name:?'zhangsan',
          ????age:?26,
          ????time:?expect.any(Date)?//用于聲明是個(gè)時(shí)間類(lèi)型,否則時(shí)間會(huì)一直改變,快照不通過(guò)
          ??})
          })
          復(fù)制代碼
          • toMatchSnapshot會(huì)將參數(shù)將快照進(jìn)行匹配
          • expect.any(Date) 用于匹配一個(gè)時(shí)間類(lèi)型

          執(zhí)行npm run test會(huì)生成一個(gè)__snapshots__文件夾,里面是生成的快照,當(dāng)你修改一下測(cè)試代碼時(shí),會(huì)提示你,快照不匹配。如果你確定你需要修改,按 u 鍵,即可更新快照。這用于UI組件的測(cè)試非常有用。

          React的BDD單測(cè)

          接下來(lái)我們看下react代碼如何進(jìn)行測(cè)試,用一個(gè)很小的例子來(lái)說(shuō)明。

          案例中引入了enzyme。_Enzyme_?來(lái)自 airbnb 公司,是一個(gè)用于 React 的 JavaScript 測(cè)試工具,方便你判斷、操縱和歷遍 React Components 輸出。

          我們達(dá)成的目的是檢測(cè):

          • 用戶(hù)進(jìn)入首頁(yè),看到兩個(gè)按鈕,分別是counter1和counter2
          • 點(diǎn)擊counter1,就能看到兩個(gè)按鈕的文字部分分別是"counter1"和"counter2"

          react代碼如下

          import?React?from?'react';
          function?Counter(){
          ????return?(
          ????????<ul>
          ????????????<li>
          ????????????????<button?id='counter1'?className='button1'>counter1button>

          ????????????li>
          ????????????<li>
          ????????????????<button?id='counter2'?className='button2'>counter2button>
          ????????????li>
          ????????ul>
          ????)
          }
          復(fù)制代碼

          單測(cè)的文件:

          import?Counter?from?xx;
          import?{?mount?}?from?'enzyme';

          describle('測(cè)試APP',()?=>?{
          ????test('用戶(hù)進(jìn)入首頁(yè),看到兩個(gè)按鈕,分別是counter1和counter2,并且按鈕文字也是counter1和counter2',()=>{
          ????????const?wrapper?=?mount(<Counter?/>);
          ????????const?button?=?wrapper.find('button');
          ????????except(button).toHaveLength(2);
          ????????except(button.at(0).text()).toBe('counter1');
          ????????except(button.at(1).text()).toBe('counter2');
          ????})
          })
          復(fù)制代碼

          Jest | 測(cè)試設(shè)置分類(lèi)(describe)及作用域[1]

          jest入門(mén)單元測(cè)試[2]


          關(guān)于本文

          作者:孟祥_(kāi)成都

          https://juejin.cn/post/7092188990471667749


          最后



          歡迎關(guān)注【前端瓶子君】??ヽ(°▽°)ノ?
          回復(fù)「算法」,加入前端編程源碼算法群領(lǐng)取最新最熱的前端算法小書(shū)、面試小書(shū)以及海量簡(jiǎn)歷模板,期待與你共進(jìn)步!
          回復(fù)「交流」,吹吹水、聊聊技術(shù)、吐吐槽!
          回復(fù)「閱讀」,每日刷刷高質(zhì)量好文!
          如果這篇文章對(duì)你有幫助,在看」是最大的支持
          ?》》面試官也在看的算法資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持



          瀏覽 31
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  av电影天堂网 | 在线观看豆花 | 久久午夜无码鲁丝片午夜情品 | 日韩欧美东京热 | 日韩激情视频青青草 |