單元測試自動化
所謂的單元測試(Unit Test)是根據(jù)特定的輸入數(shù)據(jù),針對程序代碼中的最小實體單元的輸入輸出的正確性進(jìn)行驗證測試的過程。所謂的最小實體單元就是組織項目代碼的最基本代碼結(jié)構(gòu):函數(shù),類,模塊等。在Python中比較知名的單元測試模塊:
-
unittest
-
pytest
-
doctest
-
nose
測試用例
所謂的測試用例(Test Case),就是執(zhí)行測試的依據(jù)和記錄,把測試應(yīng)用程序的操作步驟用文檔的形式描述出來的一份文檔。文檔的格式可以是Excel、markdown、html、xmind網(wǎng)頁。
一份合格的測試用例有利于測試人員理清測試思路,確保需要測試的功能周全沒有遺漏,方便測試工作的開展和評估測試工作量,同時還可以便于測試人員記錄測試數(shù)據(jù)和測試工作進(jìn)度,為后續(xù)的回歸測試提供樣本參考,提升測試效率以及后續(xù)測試工作的交接。
那么一份合格的測試用例長什么樣子或有什么內(nèi)容呢?
一份合格的測試用例,應(yīng)該包含測試時間、測試人員、測試模塊名、功能點名稱、用例ID、用例說明(測試目的)、前置條件、輸入數(shù)據(jù)、預(yù)期結(jié)果、測試結(jié)果(輸出結(jié)果、實際結(jié)果)等。注意:加粗內(nèi)容為必備的測試用例八要素。
參考文檔:
在實際工作中,因為缺陷報告與測試用例作用相似,因此有時候會合并一起或只選擇其中一種。
設(shè)計方法
那么在工作中,我們一般都應(yīng)該編寫測試用例或者應(yīng)該怎么設(shè)計測試用例來完成我們的測試工作呢?實際上在工作中,測試人員都是基于測試用例的7種基本設(shè)計方法來設(shè)計與編寫測試用例的:
-
等價類劃分法:根據(jù)輸入數(shù)據(jù)的有效性與無效性設(shè)計測試用例。
-
邊界值分析法:對等價類劃分法的一個補(bǔ)充,從等價類的邊緣值(臨界點)去尋找錯誤,基于這些錯誤來設(shè)計測試用例。
-
判定表法:把輸入數(shù)據(jù)的各種可能情況進(jìn)行組合羅列成一個判斷表,以判斷表來設(shè)計測試用例。
-
因果圖法:用圖解的方式表示輸入數(shù)據(jù)的各種組合關(guān)系,以此寫出判定表,從而設(shè)計相應(yīng)的測試用例。
-
正交表法:基于正交表來設(shè)計測試用例。
-
場景法:基于流程圖展示業(yè)務(wù)流程或功能的調(diào)用流程,對流程圖的走向路徑設(shè)計測試用例。
-
錯誤推測法:基于經(jīng)驗和直覺,找出程序中認(rèn)為可能出現(xiàn)的錯誤來設(shè)計測試用例。
一般在工作中,我們比較常用的是等價類劃分法與判定表法。
等價類劃分法
等價類劃分法就是按照測試要求,把具有共同特征的測試數(shù)據(jù)劃分為2類:有效等價類和無效等價類,把測試數(shù)據(jù)進(jìn)行分類以后設(shè)計測試用例。
-
有效等價類,就是符合程序使用要求或調(diào)用代碼要求的,能正確使用程序或調(diào)用代碼的一類數(shù)據(jù)。
-
無效等價類,就是不符合程序使用要求或調(diào)用代碼要求的,會導(dǎo)致程序出現(xiàn)異?;蚪Y(jié)果不正確的一類數(shù)據(jù)。
使用等價類劃分法,可以讓我們設(shè)計的測試工作更加科學(xué)有依據(jù),避免出現(xiàn)窮舉測試的情況,減少測試用例的數(shù)量。
例如,注冊功能中用戶名的測試用例,如果功能需求中,要求用戶名必須長度為3-11個長度的字符。
系統(tǒng)模塊 | 功能點 | 用例ID | 測試目的 | 前置條件 | 輸入 | 預(yù)期 | 結(jié)果 |
---|---|---|---|---|---|---|---|
會員模塊 | 用戶注冊 | 01 | 驗證用戶名 | 打開用戶注冊頁面 | "abc" | 正確 | |
會員模塊 | 用戶注冊 | 02 | 驗證用戶名 | 打開用戶注冊頁面 | "abdefgthssaaaaa" | 錯誤 |
判定表法
判定表是分析和表達(dá)多邏輯條件下執(zhí)行不同操作的情況的工具。而軟件測試中的判定表法,就是把輸入數(shù)據(jù)的各種可能情況進(jìn)行組合羅列成一個判斷表格,以判斷表來設(shè)計測試用例。
判定表的表結(jié)構(gòu)一般有如下2種:橫向判斷表與縱向判定表。
橫向判斷表:
條件樁 | 條件項 |
---|---|
動作樁 | 動作項 |
縱向判定表:
條件樁 | 動作樁 |
---|---|
條件項 | 動作項 |
例子,測試一個功能是否能修改文件。
如果使用縱向判定表: | |||
---|---|---|---|
條件1:是否有權(quán)限 | 條件2:是否存在 | 結(jié)果1:可以修改 | 結(jié)果2:不能修改 |
√ | √ | √ | ? |
√ | ? | ? | √ |
? | √ | ? | √ |
? | ? | ? | √ |
如果使用橫向判斷表: | ||||
---|---|---|---|---|
條件樁:是否有權(quán)限 | ? | ? | ? | ? |
條件樁:是否存在 | ? | ? | ? | ? |
動作樁:可以修改 | ? | ? | ? | ? |
動作樁:不能修改 | ? | ? | ? | ? |
單元測試框架-Unittest
Unittest是Python開發(fā)中常用于單元測試的內(nèi)置框架,免安裝使用簡單方便,其設(shè)計的靈感來源于Java的單元測試框架-Junit。
Unittest具備完整的測試結(jié)構(gòu),支持自動化測試的執(zhí)行,對測試用例進(jìn)行組織,并且提供了豐富的斷言方法,還提供生成測試報告。
官方文檔:unittest --- 單元測試框架 — Python 3.11.5 文檔
import unittest print(dir(unittest))
上面的代碼中,我們就引入了Unittest模塊, 同時可以通過打印發(fā)現(xiàn)Unittest框架中內(nèi)置了大量的工具成員。這些工具成員中除了以下5個以外,其他的都不怎么常用。
-
TestCase(測試用例)
是unittest中最重要的一個類,用于編寫測試用例類,是所有測試用例類的父類,實現(xiàn)了測試用例的基本代碼。
-
TestSuite(測試套件、測試集)
可以把多個TestCase組織、打包集成到一個測試集中一起執(zhí)行,TestSuite可以實現(xiàn)多個測試用例的執(zhí)行。
-
TextTestRunner(測試運行器)
TestSuite本身不具備執(zhí)行的功能,所以使用TextTestRunner執(zhí)行測試套件和輸出測試結(jié)果。
-
TestLoader(測試加載器)
用于加載測試用例TestCase,并生成測試套件TestSuite,實現(xiàn)自動從代碼中加載大量測試用例到測試套件中。
-
TestFixture(測試腳手架)
所謂的測試腳手架就是為了開展一項或多項測試所需要進(jìn)行的準(zhǔn)備工作,以及所有相關(guān)的清理操作。測試腳手架實際上會在執(zhí)行一些測試代碼之前與之后,讓我們編寫一些初始化和銷毀的代碼。
快速入門
測試用例-TestCase
前面講到TestCase就是提供給我們編寫測試用例的測試代碼的,那么怎么編寫一個測試用例?需要4個步驟即可。
-
導(dǎo)入unittest模塊
import unittest
2.定義測試用例類
import unittest
class 測試用例類名(unittest.TestCase): # 所有的測試用例類都必須直接或者間接繼承unittest.TestCase.
"""測試用例"""
pass
3.定義測試用例方法(此處的測試用例方法,就是上面所說的測試用例設(shè)計方法中的一行信息的測試代碼)
import unittest
class 測試用例類名(unittest.TestCase):
"""測試用例"""
# ....
def test_測試方法名(參數(shù)): # 測試方法必須以test開頭或test_開頭
pass
# ....
4.執(zhí)行測試用例
unittest.main()
在實際工作中,我們肯定是在項目中進(jìn)行測試代碼的編寫或單獨編寫一個測試項目,但是我們現(xiàn)在剛開始學(xué)習(xí),所以我們可以先編寫一個例子代碼,對其進(jìn)行測試,以達(dá)到學(xué)習(xí)的目的。
unittest_01_測試用例的編寫.py,代碼:
import unittest
# 被測試的代碼單元
def add(x,y):
return x+y
class FuncTest(unittest.TestCase):
"""測試用例"""
def test_01(self):
print(add(10, 20))
def test_02(self):
print(add("hello", "world"))
# def test_03(self):
# print(add("hello", 20))
# 因為pycharm本身內(nèi)置了執(zhí)行unittest的功能,所以不適用以下代碼也能執(zhí)行,但是終端下或者使用其他的代碼編輯器時,則需要加上。
if __name__ == '__main__':
unittest.main()
運行結(jié)果:
測試套件-TestSuite
前面我們將到測試套件,主要用于把多個測試用例類打包集成到一個測試集中一起執(zhí)行。工作中,一個項目往往需要編寫非常多的測試用例,而那么多的測試用例也不可能只編寫在一個文件中,此時就需要使用測試套件了。2個步驟:
-
通過unittest.TestSuite實例化測試套件對象
suite = unittest.TestSuite()
-
通過addTest方法添加測試用例
-
添加測試用例方法
# 添加測試用例方法 suite.addtest(測試用例類名("測試用例方法名")) ? # 批量添加測試用例方法 test_data = (測試用例類名("測試用例方法名1"), 測試用例類名("測試用例方法名2")) suite.addtests(test_data)
-
添加測試用例類(一次性添加測試用例的所有test_方法)
# 添加測試用例類 suite.addtest(unittest.makeSuite(測試用例類名)) ? # 批量添加測試用例類 test_data = (unittest.makeSuite(測試用例類名1), ?unittest.makeSuite(測試用例類名2)) suite.addTests(test_data)
-
unittest_02_測試套件的基本使用.py,代碼:
import unittest
?
import unittest_01_測試用例的編寫 as unittest_01
?
suite = unittest.TestSuite()
?
# # 添加測試用例方法
# suite.addTest(unittest_01.FuncTest("test_01"))
# suite.addTest(unittest_01.FuncTest("test_02"))
?
# # 批量添加測試用例方法
# test_data = (unittest_01.FuncTest("test_01"), unittest_01.FuncTest("test_02"))
# suite.addTests(test_data)
?
?
# # 添加測試用例類
# suite.addTest(unittest.makeSuite(unittest_01.FuncTest))
?
?
# 批量添加測試用例類
test_data = (unittest.makeSuite(unittest_01.FuncTest), ?unittest.makeSuite(unittest_01.FuncTest))
suite.addTests(test_data)
TestSuite的作用僅僅是把多個測試用例打包集成到一塊,但是并沒有提供批量執(zhí)行測試用例的方法,所以我們需要使用TextTestRunner了。
測試運行器-TextTestRunner
前面說過,TextTestRunner是用于執(zhí)行測試用例、測試套件和輸出測試結(jié)果的。2個步驟:
-
實例化運行器對象
-
通過run方法執(zhí)行測試
unittest_03_測試運行器基本使用.py,代碼:
import unittest
import unittest_01_測試用例的編寫 as unittest_01
suite = unittest.TestSuite()
# # 添加測試用例方法
# suite.addTest(unittest_01.FuncTest("test_01"))
# suite.addTest(unittest_01.FuncTest("test_02"))
# # 批量添加測試用例方法
# test_data = (unittest_01.FuncTest("test_01"), unittest_01.FuncTest("test_02"))
# suite.addTests(test_data)
# # 添加測試用例類
# suite.addTest(unittest.makeSuite(unittest_01.FuncTest))
# 批量添加測試用例類
test_data = (unittest.makeSuite(unittest_01.FuncTest), unittest.makeSuite(unittest_01.FuncTest))
suite.addTests(test_data)
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite)
運行結(jié)果:
測試加載器-TestLoader
前面說過,用于加載測試用例TestCase,并生成測試套件TestSuite,實現(xiàn)自動從代碼中加載大量測試用例到測試套件中。2個步驟:
-
實例化unittest.TestLoader對象
loader = unittest.TestLoader()
-
使用discover方法自動搜索指定目錄下指定文件格式的python模塊,并把查找到的測試用例組裝打包集成到測試組件作為返回值。
loader.discover(目錄路徑, pattern="文件名格式")
注意:pattern支持
*
號表示0到多個字符。
unittest_04_測試加載器基本使用.py,代碼:
import unittest
loader = unittest.TestLoader()
# 在當(dāng)前目錄下,搜索以unittest開頭作為文件名的所有python文件,并把文件中的測試用例類打包集成到測試套件中
suite =loader.discover("./", pattern="unittest*.py")
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite)
運行結(jié)果:
測試腳手架-TestFixture
前面提到,測試腳手架會在執(zhí)行一些測試代碼之前與之后,讓我們編寫一些初始化和銷毀的代碼,主要分三個級別:
-
方法級別:在方法執(zhí)行前與執(zhí)行后都提供自動調(diào)用的實例方法
setUp和tearDown
-
類級別:在類執(zhí)行前與執(zhí)行后都提供自動調(diào)用的類方法,不管類中有多少方法,只執(zhí)行一次。
setUpClass和tearDownClass
-
模塊級別:在模塊執(zhí)行前與執(zhí)行后都提供自動調(diào)用的函數(shù),不管模塊中有多少類或方法,只執(zhí)行一次。
setUpModule和tearDownModule
方法級別的腳手架
在測試用例類中提供了2個固定名字的實例方法(setUp與tearDown),用于完成方法執(zhí)行前與執(zhí)行后的操作。
unittest_05測試腳手架方法級別的腳手架.py,代碼:
import unittest
# 被測試的代碼單元
def add(x,y):
return x+y
class AddTest(unittest.TestCase):
"""測試用例"""
def setUp(self):
print("每個方法執(zhí)行前都會執(zhí)行一遍setUp實例方法,用于完成通用的前置操作或初始化工作")
def tearDown(self):
print("每個方法執(zhí)行后都會執(zhí)行一遍tearDown實例方法,用于完成通用的后置操作或銷毀工作")
def test_01(self):
print(add(10, 20))
def test_03(self):
print(add("hello", 20))
# 因為pycharm本身內(nèi)置了執(zhí)行unittest的功能,所以不適用以下代碼也能執(zhí)行,但是終端下或者使用其他的代碼編輯器時,則需要加上。
if __name__ == '__main__':
unittest.main()
運行嘗試:
類級別的腳手架
在測試用例類中提供了2個固定名字的類方法(setUpClass與tearDownClass),用于完成類執(zhí)行前與執(zhí)行后的操作。
unittest_06測試腳手架類級別的腳手架.py,代碼:
import unittest
# 被測試的代碼單元
def add(x,y):
return x+y
class AddTest(unittest.TestCase):
"""測試用例"""
@classmethod
def setUpClass(cls):
print("當(dāng)前類執(zhí)行前都會執(zhí)行一遍setUpClass類方法,用于完成通用的前置操作或初始化工作")
@classmethod
def tearDownClass(cls):
print("當(dāng)前類執(zhí)行后都會執(zhí)行一遍tearDownClass類方法,用于完成通用的后置操作或銷毀工作")
def test_01(self):
print(add(10, 20))
def test_03(self):
print(add("hello", 20))
# 因為pycharm本身內(nèi)置了執(zhí)行unittest的功能,所以不適用以下代碼也能執(zhí)行,但是終端下或者使用其他的代碼編輯器時,則需要加上。
if __name__ == '__main__':
unittest.main()
模塊級別的腳手架
在測試用例類中提供了2個固定名字的函數(shù)(setUpModule與tearDownModule),用于完成類執(zhí)行前與執(zhí)行后的操作。
unittest_07測試腳手架模塊級別的腳手架.py,代碼:
import unittest
def setUpModule():
print("當(dāng)前模塊執(zhí)行前都會執(zhí)行一遍setUpModule函數(shù),用于完成通用的前置操作或初始化工作")
def tearDownModule():
print("當(dāng)前模塊執(zhí)行前都會執(zhí)行一遍tearDownModule函數(shù),用于完成通用的前置操作或初始化工作")
# 被測試的代碼單元
def add(x, y):
return x + y
class AddTest1(unittest.TestCase):
"""測試用例"""
@classmethod
def setUpClass(cls):
print("當(dāng)前類執(zhí)行前都會執(zhí)行一遍setUpClass類方法,用于完成通用的前置操作或初始化工作")
@classmethod
def tearDownClass(cls):
print("當(dāng)前類執(zhí)行后都會執(zhí)行一遍tearDownClass類方法,用于完成通用的后置操作或銷毀工作")
def test_01(self):
print(add(10, 20))
class AddTest2(unittest.TestCase):
"""測試用例"""
@classmethod
def setUpClass(cls):
print("當(dāng)前類執(zhí)行前都會執(zhí)行一遍setUp方法,用于完成通用的前置操作或初始化工作")
@classmethod
def tearDownClass(cls):
print("當(dāng)前類執(zhí)行后都會執(zhí)行一遍tearDown方法,用于完成通用的后置操作或銷毀工作")
def test_03(self):
print(add("hello", 20))
# 因為pycharm本身內(nèi)置了執(zhí)行unittest的功能,所以不適用以下代碼也能執(zhí)行,但是終端下或者使用其他的代碼編輯器時,則需要加上。
if __name__ == '__main__':
unittest.main()
生成HTML格式測試報告(很少使用,基本不使用)
使用HTMLTestRunner模塊可以直接生成HTML格式的報告。HTMLTestRunner是一個不再維護(hù)的第三方的模塊,通過pip工具安裝不了,只能下載后手動導(dǎo)入。
HTMLTestRunner官網(wǎng):HTMLTestRunner - tungwaiyip's software
HTMLTestRunner下載:http://tungwaiyip.info/software/HTMLTestRunner_0_8_2/HTMLTestRunner.py
(我就先跳過)
斷言
斷言(assertion)是一種在程序中的判斷測試用例執(zhí)行結(jié)果是否符合預(yù)期結(jié)果的方式,所以斷言也被稱之為“期望”。當(dāng)程序執(zhí)行到斷言的位置時,對應(yīng)的斷言應(yīng)該為真。若斷言不為真時,程序會中止執(zhí)行,并給出錯誤信息。
unittest中常用的斷言方法(加粗為重要方法):
斷言方法 | 斷言描述 |
---|---|
assertEqual(arg1, arg2, msg=None) | 驗證arg1=arg2,不等則fail |
assertNotEqual(arg1, arg2, msg=None) | 驗證arg1 != arg2, 相等則fail |
assertTrue(expr, msg=None) | 驗證expr是true,如果為false,則fail |
assertFalse(expr,msg=None) | 驗證expr是false,如果為true,則fail |
assertIs(arg1, arg2, msg=None) | 驗證arg1、arg2是同一個對象,不是則fail |
assertIsNot(arg1, arg2, msg=None) | 驗證arg1、arg2不是同一個對象,是則fail |
assertIsNone(expr, msg=None) | 驗證expr是None,不是則fail |
assertIsNotNone(expr, msg=None) | 驗證expr不是None,是則fail |
assertIn(arg1, arg2, msg=None) | 驗證arg1是arg2的子串,不是則fail |
assertNotIn(arg1, arg2, msg=None) | 驗證arg1不是arg2的子串,是則fail |
assertIsInstance(obj, cls, msg=None) | 驗證obj是cls的實例,不是則fail |
assertNotIsInstance(obj, cls, msg=None) | 驗證obj不是cls的實例,是則fail |
unittest_09_斷言.py,代碼:
import unittest
def add(x ,y):
return x + y
class AddTest(unittest.TestCase):
def test_01(self):
res = add(1,2)
# 斷言結(jié)果是否與預(yù)期內(nèi)容相同
# self.assertEqual(res, 3, msg="斷言失敗!一般會錯誤的結(jié)果與原因")
# self.assertEqual(res, 2, msg="斷言失?。∫话銜e誤的結(jié)果與原因")
self.assertIn(res, [1, 2], msg="斷言失?。∫话銜e誤的結(jié)果與原因")
if __name__ == '__main__':
unittest.main()
跳過
針對開發(fā)中有時候針對不同環(huán)境或者不同的時間段,不同的代碼版本,有時候部分測試用例不希望被執(zhí)行,則可以使用跳過。
@unittest.skipIf(判斷條件表達(dá)式, 跳過原因)
unittest_10_跳過.py,代碼:
import unittest
def add(x, y):
return x + y
version = (2, 7, 0)
class AddTest(unittest.TestCase):
def setUp(self):
print("setUP執(zhí)行....")
@unittest.skipIf(version <= (3, 5, 0), "版本低于3.5,所以不測試test_01")
def test_01(self):
res = add(1, 2)
self.assertIn(res, [1, 3], msg="斷言失敗!一般會錯誤的結(jié)果與原因")
def test_02(self):
res = add("a", "B")
self.assertEqual(res, "aB", msg="斷言失??!一般會錯誤的結(jié)果與原因")
if __name__ == '__main__':
unittest.main()
參數(shù)化
當(dāng)需要使用多組不同的測試數(shù)據(jù)測試同一個方法時,可以使用unittest參數(shù)化來解決。常用的參數(shù)化方法有ddt、paramunittes
pip install parameterized
unittest_11_參數(shù)化.py,代碼:
import unittest
from parameterized import parameterized
def add(x, y):
return x + y
version = (2, 7, 0)
class AddTest(unittest.TestCase):
def setUp(self):
print("setUP執(zhí)行....")
@parameterized.expand([(10,20), ("a","B"), (50, 20)])
def test_00(self, x, y):
res = add(x, y)
self.assertIn(res, [1, 30, "aB", 70], msg="斷言失??!一般會錯誤的結(jié)果與原因")
# def test_01(self):
# res = add(1, 2)
# self.assertIn(res, [1, 3], msg="斷言失?。∫话銜e誤的結(jié)果與原因")
#
# def test_02(self):
# res = add("a", "B")
# self.assertEqual(res, "aB", msg="斷言失??!一般會錯誤的結(jié)果與原因")
#
# def test_03(self):
# print(add("a", 20))
if __name__ == '__main__':
unittest.main()
數(shù)據(jù)驅(qū)動測試
Data-Driven Tests(DDT)即數(shù)據(jù)驅(qū)動測試,可以實現(xiàn)多個數(shù)據(jù)對同一個方法進(jìn)行測試,達(dá)到數(shù)據(jù)和測試代碼分離,目的是為了減少測試用例的數(shù)量。
基本安裝
pip install ddt
直接傳遞單個數(shù)據(jù)
unittest_12參數(shù)化基于ddt直接傳遞數(shù)據(jù).py,代碼:
import unittest
from ddt import ddt, data
def add(a,b):
return a+b
@ddt
class AddTest(unittest.TestCase):
# # 單次傳遞一個數(shù)據(jù)到測試用例方法中
# @data(100)
# @data([1,2,3,4])
# @data({"a":1,"b":2})
# @data((1,2,3))
# # 多次傳遞一個數(shù)據(jù)到測試用例方法中
# @data(*["a","b","c"]) # 字符串
# @data(*[{"a":1}, {"a":2}, {"a":3}]) # 字典
# @data(*[[1, 1, 1], [1, 1, 2], [1, 1, 3]])
@data([1, 1, 1], [1, 1, 2], [1, 1, 3])
def test_01(self, a):
print(a)
if __name__ == '__main__':
unittest.main()
unittest_13_參數(shù)化-基于ddt解包傳遞多個數(shù)據(jù).py,使用unpack裝飾器解包數(shù)據(jù)
import unittest
from ddt import ddt, data, unpack
def add(a, b, c):
return a + b + c
@ddt
class AddTest(unittest.TestCase):
@data((1,2,3),(1,2,1),(1,3,1),(1,1,3))
@unpack
def test_01(self,a,b,c):
add(a,b,c)
if __name__ == '__main__':
unittest.main()
文章來源:http://www.zghlxwxcb.cn/news/detail-772806.html
最后:
我感覺還是測試崗位相對機(jī)會多一點,對于我這種普通人,開發(fā)的話大家需要認(rèn)真考量自己的實力,希望大家也能在秋招之際找到一個好工作!文章來源地址http://www.zghlxwxcb.cn/news/detail-772806.html
到了這里,關(guān)于軟件測試之單元測試自動化入門基礎(chǔ)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!