淺談基于Pytest框架的自動(dòng)化測(cè)試開(kāi)發(fā)實(shí)踐
Pytest是Python的一種易用、高效和靈活的單元測(cè)試框架,可以支持單元測(cè)試和功能測(cè)試。本文不以介紹Pytest工具本身為目的,而是以一個(gè)實(shí)際的API測(cè)試項(xiàng)目為例,將Pytest的功能應(yīng)用到實(shí)際的測(cè)試工程實(shí)踐中,教大家將Pytest用起來(lái)。
在開(kāi)始本文之前,我想跟大家澄清兩個(gè)概念,一個(gè)是測(cè)試框架一個(gè)是測(cè)試工具。很多人容易把他們搞混了,測(cè)試框架是諸如Unittest、Pytest、TestNG這類,而測(cè)試工具指的則是Selenium、Appium、Jmeter這類。
測(cè)試框架的作用是,幫助我們管理測(cè)試用例、執(zhí)行測(cè)試用例、參數(shù)化、斷言、生成測(cè)試報(bào)告等基礎(chǔ)性工作,讓我們將精力用在測(cè)試用例的編寫(xiě)上。好的測(cè)試框架應(yīng)該具有很高的擴(kuò)展性,支持二次開(kāi)發(fā),并能夠支持多種類型的自動(dòng)化測(cè)試。
測(cè)試工具的作用是為了完成某一類型的測(cè)試,比如Selenium用于對(duì)WEB UI進(jìn)行自動(dòng)化測(cè)試,Appium用來(lái)對(duì)APP進(jìn)行自動(dòng)化測(cè)試,Jmeter可以用來(lái)進(jìn)行API自動(dòng)化測(cè)試和性能測(cè)試。另外,Java語(yǔ)言中OkHttp庫(kù),Python語(yǔ)言中的requests庫(kù),這些HTTP的client也可以看做是一種API測(cè)試工具。
澄清了這兩個(gè)概念,說(shuō)一下本文的目的。其實(shí)網(wǎng)上已經(jīng)有很多教程,包括官方文檔,都是以介紹Pytest的功能為出發(fā)點(diǎn),羅列了各種功能的使用方法,大家看完之后會(huì)感覺(jué)都明白了,但是還是不知道如何與實(shí)際項(xiàng)目相結(jié)合,真正落地用起來(lái)。本文不以介紹Pytest工具本身為目的,而是以一個(gè)實(shí)際的API測(cè)試項(xiàng)目為例,通過(guò)單元測(cè)試框架Pytest和Python的Requests庫(kù)相結(jié)合,將Pytest功能應(yīng)用到實(shí)際的測(cè)試工程實(shí)踐中,教大家將Pytest用起來(lái)。
請(qǐng)相信我,使用Pytest會(huì)讓你的測(cè)試工作非常高效。
01 — Pytest核心功能
在開(kāi)始使用Pytest之前,先來(lái)了解一下Pytest的核心功能,根據(jù)官方網(wǎng)站介紹,它具有如下功能和特點(diǎn):
- 非常容易上手,入門簡(jiǎn)單,文檔豐富,文檔中有很多實(shí)例可以參考。
- 能夠支持簡(jiǎn)單的單元測(cè)試和復(fù)雜的功能測(cè)試。
- 支持參數(shù)化。
- 能夠執(zhí)行全部測(cè)試用例,也可以挑選部分測(cè)試用例執(zhí)行,并能重復(fù)執(zhí)行失敗的用例。
- 支持并發(fā)執(zhí)行,還能運(yùn)行由nose, unittest編寫(xiě)的測(cè)試用例。
- 方便、簡(jiǎn)單的斷言方式。
- 能夠生成標(biāo)準(zhǔn)的Junit XML格式的測(cè)試結(jié)果。
- 具有很多第三方插件,并且可以自定義擴(kuò)展。
- 方便的和持續(xù)集成工具集成。
Pytest的安裝方法與安裝其他的python軟件無(wú)異,直接使用pip安裝即可。
$ pip install -U pytest
安裝完成后,可以通過(guò)下面方式驗(yàn)證是否安裝成功:
$ py.test --help
如果能夠輸出幫助信息,則表示安裝成功了。
接下來(lái),通過(guò)開(kāi)發(fā)一個(gè)API自動(dòng)化測(cè)試項(xiàng)目,詳細(xì)介紹以上這些功能是如何使用的。
02 — 創(chuàng)建測(cè)試項(xiàng)目
先創(chuàng)建一個(gè)測(cè)試項(xiàng)目目錄api_pytest,為這個(gè)項(xiàng)目創(chuàng)建虛擬環(huán)境。關(guān)于虛擬環(huán)境的創(chuàng)建,可以參考這篇文章《利用pyenv和pipenv管理多個(gè)相互獨(dú)立的Python虛擬開(kāi)發(fā)環(huán)境》。這里我們直接介紹如何使用,執(zhí)行下面兩條命令:
$ mkdir api_pytest $ pipenv --python 3.7.7
這樣,項(xiàng)目目錄和虛擬環(huán)境就創(chuàng)建完成了。
接著,安裝依賴包,第一個(gè)是要安裝pytest,另外本文是以API自動(dòng)化測(cè)試為例,因此還要安裝一下HTTP 的client包requests。
$ pipenv install pytest requests
現(xiàn)在我們創(chuàng)建一個(gè)data目錄,用來(lái)存放測(cè)試數(shù)據(jù),一個(gè)tests目錄,用來(lái)存放測(cè)試腳本,一個(gè)config目錄,用來(lái)存放配置文件,一個(gè)utils目錄從來(lái)存放工具。
$ mkdir data $ mkdir tests $ mkdir config $ mkdir utils
現(xiàn)在,項(xiàng)目的目錄結(jié)構(gòu)應(yīng)該是如下這樣:
$ tree . ├── Pipfile ├── Pipfile.lock ├── config ├── data ├── tests └── utils ? 4 directories, 2 files
至此測(cè)試項(xiàng)目就創(chuàng)建完成了。接著編寫(xiě)測(cè)試用例。
03 — 編寫(xiě)測(cè)試用例
在這部分,我們以測(cè)試豆瓣電影列表API和電影詳情API為例,編寫(xiě)測(cè)試用例。
這兩個(gè)API信息如下:
| 接口 | 示例 |
|---|---|
| 電影列表 | http://api.douban.com/v2/movie/in_theaters?apikey=0df993c66c0c636e29ecbb5344252a4a&start=0&count=10 |
| 電影詳情 | https://api.douban.com/v2/movie/subject/30261964?apikey=0df993c66c0c636e29ecbb5344252a4a |
我們先寫(xiě)電影列表API的自動(dòng)化測(cè)試用例,設(shè)置3個(gè)校驗(yàn)點(diǎn):
- 驗(yàn)證請(qǐng)求中的start與響應(yīng)中的start一致。
- 驗(yàn)證請(qǐng)求中的count與響應(yīng)中的count一致。
- 驗(yàn)證響應(yīng)中的title是"正在上映的電影-上海"。
在tests目錄里面,創(chuàng)建個(gè)test_in_theaters.py文件,里面編寫(xiě)測(cè)試用例,內(nèi)容如下:
import requests
class TestInTheaters(object):
def test_in_theaters(self):
host = "http://api.douban.com"
path = "/v2/movie/in_theaters"
params = {"apikey": "0df993c66c0c636e29ecbb5344252a4a",
"start": 0,
"count": 10
}
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"
}
r = requests.request("GET", url=host + path, headers=headers, params=params)
response = r.json()
assert response["count"] == params["count"]
assert response["start"] == params["start"]
assert response["title"] == "正在上映的電影-上海", "實(shí)際的標(biāo)題是:{}".format(response["title"])
你可能會(huì)問(wèn),這就是測(cè)試用例了?這就是基于Pytest的測(cè)試用例了嗎?答案是肯定的。基于Pytest編寫(xiě)自動(dòng)化測(cè)試用例,與編寫(xiě)平常的Python代碼沒(méi)有任何區(qū)別,唯一的區(qū)別在于文件名、函數(shù)名或者方法名要以test_開(kāi)頭或者_(dá)test結(jié)尾,類名以Test開(kāi)頭。
Pytest會(huì)在test_*.py 或者 *_test.py 文件中,尋找class外邊的test_開(kāi)頭的函數(shù),或者Test開(kāi)頭的class里面的test_開(kāi)頭的方法,將這些函數(shù)和方法作為測(cè)試用例來(lái)管理??梢酝ㄟ^(guò)下面的命令,查看Pytest收集到哪些測(cè)試用例:
$ py.test --collect-only
====================================================== test session starts =======================================================
platform darwin -- Python 3.7.7, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /Users/chunming.liu/learn/api_pytest
collected 1 item
<Module tests/test_in_theaters.py>
<Class TestInTheaters>
<Function test_in_theaters>
?
===================================================== no tests ran in 0.10s ======================================================
從結(jié)果中看到,一共有一條測(cè)試用例,測(cè)試用例位于tests/test_in_theaters.py這個(gè)module里面TestInTheaters這個(gè)類中的test_in_theaters這個(gè)方法。
在Pytest中斷言使用的是Python自帶的assert語(yǔ)句,非常簡(jiǎn)單。
04 — 執(zhí)行測(cè)試用例
下面來(lái)運(yùn)行這個(gè)測(cè)試:
$ py.test tests/
====================================================== test session starts =======================================================
platform darwin -- Python 3.7.7, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /Users/chunming.liu/learn/api_pytest
collected 1 item
?
tests/test_in_theaters.py . [100%]
?
======================================================= 1 passed in 0.61s ========================================================
(api_pytest) MBC02X21W4G8WN:api_pytest chunming.liu$ py.test tests/
====================================================== test session starts =======================================================
platform darwin -- Python 3.7.7, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /Users/chunming.liu/learn/api_pytest
collected 1 item
?
tests/test_in_theaters.py F [100%]
?
============================================================ FAILURES ============================================================
________________________________________________ TestInTheaters.test_in_theaters _________________________________________________
?
self = <test_in_theaters.TestInTheaters object at 0x110eee9d0>
?
def test_in_theaters(self):
host = "http://api.douban.com"
path = "/v2/movie/in_theaters"
params = {"apikey": "0df993c66c0c636e29ecbb5344252a4a",
"start": 0,
"count": 10
}
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36"
}
r = requests.request("GET", url=host + path, headers=headers, params=params)
response = r.json()
assert response["count"] == params["count"]
assert response["start"] == params["start"]
assert response["total"] == len(response["subjects"])
?
> assert response["title"] == "正在上映的電影-上海", "實(shí)際的標(biāo)題是:{}".format(response["title"])
E AssertionError: 實(shí)際的標(biāo)題是:正在上映的電影-北京
E assert '正在上映的電影-北京' == '正在上映的電影-上海'
E - 正在上映的電影-上海
E ? ^^
E + 正在上映的電影-北京
E ? ^^
?
tests/test_in_theaters.py:20: AssertionError
==================================================== short test summary info =====================================================
FAILED tests/test_in_theaters.py::TestInTheaters::test_in_theaters - AssertionError: 實(shí)際的標(biāo)題是正在上映的電影-北京
======================================================= 1 failed in 0.96s ========================================================
這個(gè)命令執(zhí)行時(shí),會(huì)在tests/目錄里面尋找測(cè)試用例。執(zhí)行測(cè)試的時(shí)候,如果不指定測(cè)試用例所在目錄,Pytest會(huì)在當(dāng)前的目錄下,按照前面介紹的規(guī)則尋找測(cè)試用例并執(zhí)行。
通過(guò)上面的測(cè)試輸出,我們可以看到該測(cè)試過(guò)程中,一共收集到了一個(gè)測(cè)試用例,測(cè)試結(jié)果是失敗的(標(biāo)記為F),并且在FAILURES部分輸出了詳細(xì)的錯(cuò)誤信息,通過(guò)這些信息,我們可以分析測(cè)試失敗的原因。上面測(cè)試用例的失敗原因是在斷言title的時(shí)候出錯(cuò)了,預(yù)期的title是“正在上映的電影-上海”,但是實(shí)際是“正在上映的電影-北京”,預(yù)期和實(shí)際的對(duì)比非常直觀。
執(zhí)行測(cè)試用例的方法還有很多種,都是在py.test后面添加不同的參數(shù)即可,我在下面羅列了一下:
$ py.test # run all tests below current dir
$ py.test test_module.py # run tests in module
$ py.test somepath # run all tests below somepath
$ py.test -k stringexpr # only run tests with names that match the
# the "string expression", e.g. "MyClass and not method"
# will select TestMyClass.test_something
# but not TestMyClass.test_method_simple
$ py.test test_module.py::test_func # only run tests that match the "node ID",
# e.g "test_mod.py::test_func" will select
# only test_func in test_mod.py
上面這些用法,通過(guò)注釋很容易理解。在測(cè)試執(zhí)行過(guò)程中,這些方法都有機(jī)會(huì)被用到,最好掌握一下。
05 — 數(shù)據(jù)與腳本分離
03小節(jié)的測(cè)試用例,將測(cè)試數(shù)據(jù)和測(cè)試代碼放到了同一個(gè)py文件中,而且是同一個(gè)測(cè)試方法中,產(chǎn)生了緊耦合,會(huì)導(dǎo)致修改測(cè)試數(shù)據(jù)或測(cè)試代碼時(shí),可能會(huì)相互影響,不利于測(cè)試數(shù)據(jù)和測(cè)試腳本的維護(hù)。比如,為測(cè)試用例添加幾組新的測(cè)試數(shù)據(jù),除了準(zhǔn)備測(cè)試數(shù)據(jù)外,還要修改測(cè)試代碼,降低了測(cè)試代碼的可維護(hù)性。
另外接口測(cè)試往往是數(shù)據(jù)驅(qū)動(dòng)的測(cè)試,測(cè)試數(shù)據(jù)和測(cè)試代碼放到一起也不方便借助Pytest做參數(shù)化。
將測(cè)試代碼和測(cè)試數(shù)據(jù)分離已經(jīng)是測(cè)試領(lǐng)域中的共識(shí)了。在data/目錄下創(chuàng)建一個(gè)用于存放測(cè)試數(shù)據(jù)的Yaml文件test_in_theaters.yaml,內(nèi)容如下:
---
tests:
- case: 驗(yàn)證響應(yīng)中start和count與請(qǐng)求中的參數(shù)一致
http:
method: GET
path: /v2/movie/in_theaters
headers:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
params:
apikey: 0df993c66c0c636e29ecbb5344252a4a
start: 0
count: 10
expected:
response:
title: 正在上映的電影-上海
count: 10
start: 0
熟悉Yaml格式的同學(xué),應(yīng)該很容易看懂上面測(cè)試數(shù)據(jù)文件的內(nèi)容。這個(gè)測(cè)試數(shù)據(jù)文件中,有一個(gè)數(shù)組tests,里面包含的是一條完整的測(cè)試數(shù)據(jù)。一個(gè)完整的測(cè)試數(shù)據(jù)由三部分組成:
- case,表示測(cè)試用例名稱。
- http,表示請(qǐng)求對(duì)象。
- expected,表示預(yù)期結(jié)果。
http這個(gè)請(qǐng)求對(duì)象包含了被測(cè)接口的所有參數(shù),包括請(qǐng)求方法、請(qǐng)求路徑、請(qǐng)求頭、請(qǐng)求參數(shù)。
expected表示預(yù)期結(jié)果,上面的測(cè)試數(shù)據(jù)中,只列出了對(duì)請(qǐng)求響應(yīng)的預(yù)期值,實(shí)際測(cè)試中,還可以列出對(duì)數(shù)據(jù)庫(kù)的預(yù)期值。
測(cè)試腳本也要做相應(yīng)的改造,需要讀取test_in_theaters.yaml文件獲取請(qǐng)求數(shù)據(jù)和預(yù)期結(jié)果,然后通過(guò)requests發(fā)出請(qǐng)求。修改后的測(cè)試代碼如下:
import requests
import yaml
?
?
def get_test_data(test_data_path):
case = [] # 存儲(chǔ)測(cè)試用例名稱
http = [] # 存儲(chǔ)請(qǐng)求對(duì)象
expected = [] # 存儲(chǔ)預(yù)期結(jié)果
with open(test_data_path) as f:
dat = yaml.load(f.read(), Loader=yaml.SafeLoader)
test = dat['tests']
for td in test:
case.append(td.get('case', ''))
http.append(td.get('http', {}))
expected.append(td.get('expected', {}))
parameters = zip(case, http, expected)
return case, parameters
?
?
cases, parameters = get_test_data("/Users/chunming.liu/learn/api_pytest/data/test_in_theaters.yaml")
list_params=list(parameters)
?
class TestInTheaters(object):
def test_in_theaters(self):
host = "http://api.douban.com"
r = requests.request(list_params[0][1]["method"],
url=host + list_params[0][1]["path"],
headers=list_params[0][1]["headers"],
params=list_params[0][1]["params"])
response = r.json()
assert response["count"] == list_params[0][2]['response']["count"]
assert response["start"] == list_params[0][2]['response']["start"]
assert response["total"] == len(response["subjects"])
assert response["title"] == list_params[0][2]['response']["title"], "實(shí)際的標(biāo)題是:{}".format(response["title"])
注意,讀取Yaml文件,需要安裝PyYAML包。
測(cè)試腳本中定義了一個(gè)讀取測(cè)試數(shù)據(jù)的函數(shù)get_test_data,通過(guò)這個(gè)函數(shù)從測(cè)試數(shù)據(jù)文件test_in_theaters.yaml中讀取到了測(cè)試用例名稱case,請(qǐng)求對(duì)象http和預(yù)期結(jié)果expected。這三部分分別是一個(gè)列表,通過(guò)zip將他們壓縮到一起。
測(cè)試方法test_in_theaters并沒(méi)有太大變化,只是發(fā)送請(qǐng)求所使用的測(cè)試數(shù)據(jù)不是寫(xiě)死的,而是來(lái)自于測(cè)試數(shù)據(jù)文件了。
通常情況下,讀取測(cè)試數(shù)據(jù)的函數(shù)不會(huì)定義在測(cè)試用例文件中,而是會(huì)放到utils包中,比如放到utils/commonlib.py中。至此,整個(gè)項(xiàng)目的目錄結(jié)構(gòu)應(yīng)該是如下所示:
$ tree
.
├── Pipfile
├── Pipfile.lock
├── config
├── data
│ └── test_in_theaters.yaml
├── tests
│ └── test_in_theaters.py
└── utils
└── commlib.py
這樣,我們修改測(cè)試腳本,就修改test_in_theaters.py,變更測(cè)試數(shù)據(jù),就修改test_in_theaters.yaml。但是目前看,感覺(jué)好像并沒(méi)有真正看到測(cè)試數(shù)據(jù)和腳本分離的厲害之處,或者更加有價(jià)值的地方,那么我們接著往下看。
06 — 參數(shù)化
上面我們將測(cè)試數(shù)據(jù)和測(cè)試腳本相分離,如果要為測(cè)試用例添加更多的測(cè)試數(shù)據(jù),往tests數(shù)組中添加更多的同樣格式的測(cè)試數(shù)據(jù)即可。這個(gè)過(guò)程叫作參數(shù)化。
參數(shù)化的意思是對(duì)同一個(gè)接口,使用多種不同的輸入對(duì)其進(jìn)行測(cè)試,以驗(yàn)證是否每一組輸入?yún)?shù)都能得到預(yù)期結(jié)果。Pytest提供了pytest.mark.paramtrize這種方式來(lái)進(jìn)行參數(shù)化,我們先看下官方網(wǎng)站提供的介紹pytest.mark.paramtrize用法的例子:
# content of tests/test_time.py
import pytest
?
from datetime import datetime, timedelta
?
testdata = [
(datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)),
(datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)),
]
?
?
@pytest.mark.parametrize("a,b,expected", testdata)
def test_timedistance_v0(a, b, expected):
diff = a - b
assert diff == expected
執(zhí)行上面的腳本將會(huì)得到下面的輸出,測(cè)試方法test_timedistance_v0被執(zhí)行了兩遍,第一遍執(zhí)行用的測(cè)試數(shù)據(jù)是testdata列表中的第一個(gè)元組,第二遍執(zhí)行時(shí)用的測(cè)試數(shù)據(jù)是testdata列表中的第二個(gè)元組。這就是參數(shù)化的效果,同一個(gè)腳本可以使用不同的輸入?yún)?shù)執(zhí)行測(cè)試。
============================= test session starts ============================== platform darwin -- Python 3.7.7, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 -- /Users/chunming.liu/.local/share/virtualenvs/api_pytest-wCozfXSU/bin/python cachedir: .pytest_cache rootdir: /Users/chunming.liu/learn/api_pytest/tests collecting ... collected 2 items ? test_time.py::test_timedistance_v0[a0-b0-expected0] PASSED [ 50%] test_time.py::test_timedistance_v0[a1-b1-expected1] PASSED [100%] ? ============================== 2 passed in 0.02s ===============================
照貓畫(huà)虎,對(duì)我們自己的測(cè)試項(xiàng)目中的測(cè)試腳本進(jìn)行如下修改。
import pytest
import requests
?
from utils.commlib import get_test_data
?
cases, list_params = get_test_data("/Users/chunming.liu/learn/api_pytest/data/test_in_theaters.yaml")
?
?
class TestInTheaters(object):
@pytest.mark.parametrize("case,http,expected", list(list_params), ids=cases)
def test_in_theaters(self, case, http, expected):
host = "http://api.douban.com"
r = requests.request(http["method"],
url=host + http["path"],
headers=http["headers"],
params=http["params"])
response = r.json()
assert response["count"] == expected['response']["count"]
assert response["start"] == expected['response']["start"]
assert response["title"] == expected['response']["title"], "實(shí)際的標(biāo)題是:{}".format(response["title"])
在測(cè)試方法上面添加了一個(gè)裝飾器@pytest.mark.parametrize,裝飾器會(huì)自動(dòng)對(duì)list(list_params)解包并賦值給裝飾器的第一參數(shù)。裝飾器的第一個(gè)參數(shù)中逗號(hào)分隔的變量可以作為測(cè)試方法的參數(shù),在測(cè)試方法內(nèi)就可以直接獲取這些變量的值,利用這些值發(fā)起請(qǐng)求和進(jìn)行斷言。裝飾器還有一個(gè)參數(shù)叫ids,這個(gè)值作為測(cè)試用例的名稱將打印到測(cè)試結(jié)果中。
在執(zhí)行修改后的測(cè)試腳本前,我們?cè)跍y(cè)試數(shù)據(jù)文件再增加一組測(cè)試數(shù)據(jù),現(xiàn)在測(cè)試數(shù)據(jù)文件中,包含了兩組測(cè)試數(shù)據(jù):
---
tests:
- case: 驗(yàn)證響應(yīng)中start和count與請(qǐng)求中的參數(shù)一致
http:
method: GET
path: /v2/movie/in_theaters
headers:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
params:
apikey: 0df993c66c0c636e29ecbb5344252a4a
start: 0
count: 10
expected:
response:
title: 正在上映的電影-上海
count: 10
start: 0
- case: 驗(yàn)證響應(yīng)中title是"正在上映的電影-北京"
http:
method: GET
path: /v2/movie/in_theaters
headers:
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36
params:
apikey: 0df993c66c0c636e29ecbb5344252a4a
start: 1
count: 5
expected:
response:
title: 正在上映的電影-北京
count: 5
start: 1
現(xiàn)在我們執(zhí)行一下測(cè)試腳本,看看效果:
$ export PYTHONPATH=/Users/chunming.liu/learn/api_pytest
$ py.test tests/test_in_theaters.py
====================================================== test session starts =======================================================
platform darwin -- Python 3.7.7, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /Users/chunming.liu/learn/api_pytest, inifile: pytest.ini
collected 2 items
?
tests/test_in_theaters.py F. [100%]
?
============================================================ FAILURES ============================================================
___________________________________ TestInTheaters.test_in_theaters[驗(yàn)證響應(yīng)中start和count與請(qǐng)求中的參數(shù)一致] ___________________________________
?
self = <test_in_theaters.TestInTheaters object at 0x102659510>, case = '驗(yàn)證響應(yīng)中start和count與請(qǐng)求中的參數(shù)一致'
http = {'headers': {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chr...T', 'params': {'apikey': '0df993c66c0c636e29ecbb5344252a4a', 'count': 10, 'start': 0}, 'path': '/v2/movie/in_theaters'}
expected = {'response': {'count': 10, 'start': 0, 'title': '正在上映的電影-上海'}}
?
@pytest.mark.parametrize("case,http,expected", list(list_params), ids=cases)
def test_in_theaters(self, case, http, expected):
host = "http://api.douban.com"
r = requests.request(http["method"],
url=host + http["path"],
headers=http["headers"],
params=http["params"])
response = r.json()
assert response["count"] == expected['response']["count"]
assert response["start"] == expected['response']["start"]
> assert response["title"] == expected['response']["title"], "實(shí)際的標(biāo)題是:{}".format(response["title"])
E AssertionError: 實(shí)際的標(biāo)題是:正在上映的電影-北京
E assert '正在上映的電影-北京' == '正在上映的電影-上海'
E - 正在上映的電影-上海
E ? ^^
E + 正在上映的電影-北京
E ? ^^
?
tests/test_in_theaters.py:20: AssertionError
==================================================== short test summary info =====================================================
FAILED tests/test_in_theaters.py::TestInTheaters::test_in_theaters[\u9a8c\u8bc1\u54cd\u5e94\u4e2dstart\u548ccount\u4e0e\u8bf7\u6c42\u4e2d\u7684\u53c2\u6570\u4e00\u81f4]
================================================== 1 failed, 1 passed in 0.69s ===================================================
從結(jié)果看,Pytest收集到了2個(gè)items,測(cè)試腳本執(zhí)行了兩遍,第一遍執(zhí)行用第一組測(cè)試數(shù)據(jù),結(jié)果是失敗(F),第二遍執(zhí)行用第二組測(cè)試數(shù)據(jù),結(jié)果是通過(guò)(.)。執(zhí)行完成后的summary info部分,看到了一些Unicode編碼,這里其實(shí)是ids的內(nèi)容,因?yàn)槭侵形?,所以默認(rèn)這里顯示Unicode編碼。為了顯示中文,需要在測(cè)試項(xiàng)目的根目錄下創(chuàng)建一個(gè)Pytest的配置文件pytest.ini,在其中添加如下代碼:
[pytest] disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
再次執(zhí)行測(cè)試腳本,在測(cè)試結(jié)果的summary_info部分,則會(huì)顯示正確中文內(nèi)容了。
FAILED tests/test_in_theaters.py::TestInTheaters::test_in_theaters[驗(yàn)證響應(yīng)中start和count與請(qǐng)求中的參數(shù)一致] - AssertionError: ...
按照這種參數(shù)化的方法,如果想修改或者添加測(cè)試數(shù)據(jù),只需要修改測(cè)試數(shù)據(jù)文件即可。
現(xiàn)在,自動(dòng)化測(cè)試項(xiàng)目的目錄結(jié)構(gòu)應(yīng)該是如下這樣:
$ tree
.
├── Pipfile
├── Pipfile.lock
├── config
├── data
│ └── test_in_theaters.yaml
├── pytest.ini
├── tests
│ ├── test_in_theaters.py
│ └── test_time.py
└── utils
└── commlib.py
?
4 directories, 7 files
07 — 測(cè)試配置管理
06小節(jié)的自動(dòng)化測(cè)試代碼中,host是寫(xiě)在測(cè)試腳本中的,這種硬編碼方式顯然是不合適的。這個(gè)host在不同的測(cè)試腳本都會(huì)用到,應(yīng)該放到一個(gè)公共的地方來(lái)維護(hù)。如果需要對(duì)其進(jìn)行修改,那么只需要修改一個(gè)地方就可以了。根據(jù)我的實(shí)踐經(jīng)驗(yàn),將其放到config文件夾中,是比較好的。
除了host外,其他與測(cè)試環(huán)境相關(guān)的配置信息也可以放到config文件夾中,比如數(shù)據(jù)庫(kù)信息、kafka連接信息等,以及與測(cè)試環(huán)境相關(guān)的基礎(chǔ)測(cè)試數(shù)據(jù),比如測(cè)試賬號(hào)。很多時(shí)候,我們會(huì)有不同的測(cè)試環(huán)境,比如dev環(huán)境、test環(huán)境、stg環(huán)境、prod環(huán)境等。我們可以在config文件夾下面創(chuàng)建子目錄來(lái)區(qū)分不同的測(cè)試環(huán)境。因此config文件夾,應(yīng)該是類似這樣的結(jié)構(gòu):
├── config │ ├── prod │ │ └── config.yaml │ └── test │ └── config.yaml
在config.yaml中存放不同環(huán)境的配置信息,以前面的例子為例,應(yīng)該是這樣:
host: douban: http://api.douban.com
將測(cè)試配置信息從腳本中拆分出來(lái),就需要有一種機(jī)制將其讀取到,才能在測(cè)試腳本中使用。Pytest提供了fixture機(jī)制,通過(guò)它可以在測(cè)試執(zhí)行前執(zhí)行一些操作,在這里我們利用fixture提前讀取到配置信息。我們先對(duì)官方文檔上的例子稍加修改,來(lái)介紹fixture的使用。請(qǐng)看下面的代碼:
import pytest
?
?
@pytest.fixture
def smtp_connection():
import smtplib
connection = smtplib.SMTP_SSL("smtp.163.com", 465, timeout=5)
yield connection
print("teardown smtp")
connection.close()
?
?
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert 0
這段代碼中,smtp_connection被裝飾器@pytest.fixture裝飾,表明它是一個(gè)fixture函數(shù)。這個(gè)函數(shù)的功能是連接163郵箱服務(wù)器,返回一個(gè)連接對(duì)象。當(dāng)test_ehlo的最后一次測(cè)試執(zhí)行完成后,執(zhí)行print(“teardown smtp”)和connection.close()斷開(kāi)smtp連接。
fixture函數(shù)名可以作為測(cè)試方法test_ehlo的參數(shù),在測(cè)試方法內(nèi)部,使用fixture函數(shù)名這個(gè)變量,就相當(dāng)于是在使用fixture函數(shù)的返回值。
回到我們讀取測(cè)試配置信息的需求上,在自動(dòng)化測(cè)試項(xiàng)目tests/目錄中創(chuàng)建一個(gè)文件conftest.py,定義一個(gè)fixture函數(shù)env:
@pytest.fixture(scope="session")
def env(request):
config_path = os.path.join(request.config.rootdir,
"config",
"test",
"config.yaml")
with open(config_path) as f:
env_config = yaml.load(f.read(), Loader=yaml.SafeLoader)
return env_config
conftest.py文件是一個(gè)plugin文件,里面可以實(shí)現(xiàn)Pytest提供的Hook函數(shù)或者自定義的fixture函數(shù),這些函數(shù)只在conftest.py所在目錄及其子目錄中生效。scope="session"表示這個(gè)fixture函數(shù)的作用域是session級(jí)別的,在整個(gè)測(cè)試活動(dòng)中開(kāi)始前執(zhí)行,并且只會(huì)被執(zhí)行一次。除了session級(jí)別的fixture函數(shù),還有function級(jí)別、class級(jí)別等。
env函數(shù)中有一個(gè)參數(shù)request,其實(shí)request也是一個(gè)fixture函數(shù)。在這里用到了它的request.config.rootdir屬性,這個(gè)屬性表示的是pytest.ini這個(gè)配置文件所在的目錄,因?yàn)槲覀兊臏y(cè)試項(xiàng)目中pytest.ini處于項(xiàng)目的根目錄,所以config_path的完整路徑就是:
/Users/chunming.liu/learn/api_pytest/config/test/config.yaml
將env作為參數(shù)傳入測(cè)試方法test_in_theaters,將測(cè)試方法內(nèi)的host改為env[“host”][“douban”]:
class TestInTheaters(object):
@pytest.mark.parametrize("case,http,expected", list(list_params), ids=cases)
def test_in_theaters(self, env, case, http, expected):
r = requests.request(http["method"],
url=env["host"]["douban"] + http["path"],
headers=http["headers"],
params=http["params"])
response = r.json()
這樣就達(dá)到了測(cè)試配置文件與測(cè)試腳本相互分離的效果,如果需要修改host,只需要修改配置文件即可,測(cè)試腳本文件就不用修改了。修改完成后執(zhí)行測(cè)試的方法不變。
上面的env函數(shù)實(shí)現(xiàn)中,有點(diǎn)點(diǎn)小缺憾,就是讀取的配置文件是固定的,讀取的都是test環(huán)境的配置信息,我們希望在執(zhí)行測(cè)試時(shí),通過(guò)命令行選項(xiàng),可指定讀取哪個(gè)環(huán)境的配置,以便在不同的測(cè)試環(huán)境下開(kāi)展測(cè)試。Pytest提供了一個(gè)叫作pytest_addoption的Hook函數(shù),可以接受命令行選項(xiàng)的參數(shù),寫(xiě)法如下:
def pytest_addoption(parser):
parser.addoption("--env",
action="store",
dest="environment",
default="test",
help="environment: test or prod")
pytest_addoption的含義是,接收命令行選項(xiàng)–env選項(xiàng)的值,存到environment變量中,如果不指定命令行選項(xiàng),environment變量默認(rèn)值是test。將上面代碼也放入conftest.py中,并修改env函數(shù),將os.path.join中的"test"替換為request.config.getoption(“environment”),這樣就可以通過(guò)命令行選項(xiàng)來(lái)控制讀取的配置文件了。比如執(zhí)行test環(huán)境的測(cè)試,可以指定–env test:
$ py.test --env test tests/test_in_theaters.py
如果不想每次都在命令行上指定–env,還可以將其放入pyest.ini中:
[pytest] addopts = --env prod
命令行上的參數(shù)會(huì)覆蓋pyest.ini里面的參數(shù)。
08 — 測(cè)試的準(zhǔn)備與收尾
很多時(shí)候,我們需要在測(cè)試用例執(zhí)行前做數(shù)據(jù)庫(kù)連接的準(zhǔn)備,做測(cè)試數(shù)據(jù)的準(zhǔn)備,測(cè)試執(zhí)行后斷開(kāi)數(shù)據(jù)庫(kù)連接,清理測(cè)試臟數(shù)據(jù)這些工作。通過(guò)07小節(jié)大家對(duì)于通過(guò)env這個(gè)fixture函數(shù),如何在測(cè)試開(kāi)始前的開(kāi)展準(zhǔn)備工作有所了解,本小節(jié)將介紹更多內(nèi)容。
@pytest.fixture函數(shù)的scope可能的取值有function,class,module,package 或 session。他們的具體含義如下:
- function,表示fixture函數(shù)在測(cè)試方法執(zhí)行前和執(zhí)行后執(zhí)行一次。
- class,表示fixture函數(shù)在測(cè)試類執(zhí)行前和執(zhí)行后執(zhí)行一次。
- module,表示fixture函數(shù)在測(cè)試腳本執(zhí)行前和執(zhí)行后執(zhí)行一次。
- package,表示fixture函數(shù)在測(cè)試包(文件夾)中第一個(gè)測(cè)試用例執(zhí)行前和最后一個(gè)測(cè)試用例執(zhí)行后執(zhí)行一次。
- session,表示所有測(cè)試的最開(kāi)始和測(cè)試結(jié)束后執(zhí)行一次。
通常,數(shù)據(jù)庫(kù)連接和斷開(kāi)、測(cè)試配置文件的讀取等工作,是需要放到session級(jí)別的fixture函數(shù)中,因?yàn)檫@些操作針對(duì)整個(gè)測(cè)試活動(dòng)只需要做一次。而針對(duì)測(cè)試數(shù)據(jù)的準(zhǔn)備,通常是function級(jí)別或者class級(jí)別的,因?yàn)闇y(cè)試數(shù)據(jù)針對(duì)不同的測(cè)試方法或者測(cè)試類往往都不相同。
在TestInTheaters測(cè)試類中,模擬一個(gè)準(zhǔn)備和清理測(cè)試數(shù)據(jù)的fixture函數(shù)preparation,scope設(shè)置為function:
@pytest.fixture(scope="function")
def preparation(self):
print("在數(shù)據(jù)庫(kù)中準(zhǔn)備測(cè)試數(shù)據(jù)")
test_data = "在數(shù)據(jù)庫(kù)中準(zhǔn)備測(cè)試數(shù)據(jù)"
yield test_data
print("清理測(cè)試數(shù)據(jù)")
在測(cè)試方法中,將preparation作為參數(shù),通過(guò)下面的命令執(zhí)行測(cè)試:
$ pipenv py.test -s -q --tb=no tests/test_in_theaters.py ====================================================== test session starts ======================================================= platform darwin -- Python 3.7.7, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 rootdir: /Users/chunming.liu/learn/api_pytest, inifile: pytest.ini collected 2 items tests/test_in_theaters.py 在數(shù)據(jù)庫(kù)中準(zhǔn)備測(cè)試數(shù)據(jù) F清理測(cè)試數(shù)據(jù) 在數(shù)據(jù)庫(kù)中準(zhǔn)備測(cè)試數(shù)據(jù) .清理測(cè)試數(shù)據(jù) ? ==================================================== short test summary info ===================================================== FAILED tests/test_in_theaters.py::TestInTheaters::test_in_theaters[驗(yàn)證響應(yīng)中start和count與請(qǐng)求中的參數(shù)一致] - AssertionError: ... ================================================== 1 failed, 1 passed in 0.81s ===================================================
通過(guò)輸出可以看到在每一條測(cè)試用例執(zhí)行前后,各執(zhí)行了一次“在數(shù)據(jù)庫(kù)中準(zhǔn)備測(cè)試數(shù)據(jù)”和“清理測(cè)試數(shù)據(jù)”。如果scope的值改為class,執(zhí)行測(cè)試用例的輸出信息將是下面這樣:
tests/test_in_theaters.py 在數(shù)據(jù)庫(kù)中準(zhǔn)備測(cè)試數(shù)據(jù)
F.清理測(cè)試數(shù)據(jù)
在測(cè)試類執(zhí)行前后各執(zhí)行一次“在數(shù)據(jù)庫(kù)中準(zhǔn)備測(cè)試數(shù)據(jù)”和“清理測(cè)試數(shù)據(jù)”。
09 — 標(biāo)記與分組
通過(guò)pytest.mark可以給測(cè)試用例打上標(biāo)記,常見(jiàn)的應(yīng)用場(chǎng)景是:針對(duì)某些還未實(shí)現(xiàn)的功能,將測(cè)試用例主動(dòng)跳過(guò)不執(zhí)行。或者在某些條件下,測(cè)試用例跳過(guò)不執(zhí)行。還有可以主動(dòng)將測(cè)試用例標(biāo)記為失敗等等。針對(duì)三個(gè)場(chǎng)景,pytest提供了內(nèi)置的標(biāo)簽,我們通過(guò)具體代碼來(lái)看一下:
import sys
?
import pytest
?
?
class TestMarks(object):
@pytest.mark.skip(reason="not implementation")
def test_the_unknown(self):
"""
跳過(guò)不執(zhí)行,因?yàn)楸粶y(cè)邏輯還沒(méi)有被實(shí)現(xiàn)
"""
assert 0
?
@pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher")
def test_skipif(self):
"""
低于python3.7版本不執(zhí)行這條測(cè)試用例
:return:
"""
assert 1
?
@pytest.mark.xfail
def test_xfail(self):
"""
Indicate that you expect it to fail
這條用例失敗時(shí),測(cè)試結(jié)果被標(biāo)記為xfail(expected to fail),并且不打印錯(cuò)誤信息。
這條用例執(zhí)行成功時(shí),測(cè)試結(jié)果被標(biāo)記為xpassed(unexpectedly passing)
"""
assert 0
?
@pytest.mark.xfail(run=False)
def test_xfail_not_run(self):
"""
run=False表示這條用例不用執(zhí)行
"""
assert 0
下面來(lái)運(yùn)行這個(gè)測(cè)試:
$ py.test -s -q --tb=no tests/test_marks.py ====================================================== test session starts ======================================================= platform darwin -- Python 3.7.7, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 rootdir: /Users/chunming.liu/learn/api_pytest, inifile: pytest.ini collected 4 items ? tests/test_marks.py s.xx ============================================ 1 passed, 1 skipped, 2 xfailed in 0.06s =============================================
從結(jié)果中可以看到,第一條測(cè)試用例skipped了,第二條測(cè)試用例passed了,第三條和第四條測(cè)試用例xfailed了。
除了內(nèi)置的標(biāo)簽,還可以自定義標(biāo)簽并加到測(cè)試方法上:
@pytest.mark.slow
def test_slow(self):
"""
自定義標(biāo)簽
"""
assert 0
這樣就可以通過(guò)-m過(guò)濾或者反過(guò)濾,比如只執(zhí)行被標(biāo)記為slow的測(cè)試用例:
$ py.test -s -q --tb=no -m "slow" tests/test_marks.py $ py.test -s -q --tb=no -m "not slow" tests/test_marks.py
對(duì)于自定義標(biāo)簽,為了避免出現(xiàn)PytestUnknownMarkWarning,最好在pytest.ini中注冊(cè)一下:
[pytest]
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
10 — 并發(fā)執(zhí)行
如果自動(dòng)化測(cè)試用例數(shù)量成千上萬(wàn),那么并發(fā)執(zhí)行它們是個(gè)很好的主意,可以加快整體測(cè)試用例的執(zhí)行時(shí)間。
pyest有一個(gè)插件pytest-xdist可以做到并發(fā)執(zhí)行,安裝之后,執(zhí)行測(cè)試用例通過(guò)執(zhí)行-n參數(shù)可以指定并發(fā)度,通過(guò)auto參數(shù)自動(dòng)匹配CPU數(shù)量作為并發(fā)度。并發(fā)執(zhí)行本文的所有測(cè)試用例:
$ py.test -s -q --tb=no -n auto tests/ ====================================================== test session starts ======================================================= platform darwin -- Python 3.7.7, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 rootdir: /Users/chunming.liu/learn/api_pytest, inifile: pytest.ini plugins: xdist-1.31.0, forked-1.1.3 gw0 [10] / gw1 [10] / gw2 [10] / gw3 [10] / gw4 [10] / gw5 [10] / gw6 [10] / gw7 [10] s.FxxF..F. ==================================================== short test summary info ===================================================== FAILED tests/test_marks.py::TestMarks::test_slow - assert 0 FAILED tests/test_smtpsimple.py::test_ehlo - assert 0 FAILED tests/test_in_theaters.py::TestInTheaters::test_in_theaters[驗(yàn)證響應(yīng)中start和count與請(qǐng)求中的參數(shù)一致] - AssertionError: ... ======================================= 3 failed, 4 passed, 1 skipped, 2 xfailed in 1.91s ========================================
可以非常直觀的感受到,并發(fā)執(zhí)行比順序執(zhí)行快得多。但是并發(fā)執(zhí)行需要注意的是,不同的測(cè)試用例之間不要有測(cè)試數(shù)據(jù)的相互干擾,最好不同的測(cè)試用例使用不同的測(cè)試數(shù)據(jù)。
這里提一下,pytest生態(tài)中,有很多第三方插件很好用,更多的插件可以在這里https://pypi.org/search/?q=pytest-查看和搜索,當(dāng)然我們也可以開(kāi)發(fā)自己的插件。
11 — 測(cè)試報(bào)告
Pytest可以方便的生成測(cè)試報(bào)告,通過(guò)指定–junitxml參數(shù)可以生成XML格式的測(cè)試報(bào)告,junitxml是一種非常通用的標(biāo)準(zhǔn)的測(cè)試報(bào)告格式,可以用來(lái)與持續(xù)集成工具等很多工具集成:
$ py.test -s -q --junitxml=./report.xml tests/
現(xiàn)在應(yīng)用更加廣泛的測(cè)試報(bào)告是Allure,可以方便的與Pytest集成,大家可以參考我的另外一篇公眾號(hào)文章《用Pytest+Allure生成漂亮的HTML圖形化測(cè)試報(bào)告》。
12 — 總結(jié)
本文章以實(shí)際項(xiàng)目出發(fā),介紹了如何編寫(xiě)測(cè)試用例、如何參數(shù)化、如何進(jìn)行測(cè)試配置管理、如何進(jìn)行測(cè)試的準(zhǔn)備和清理,如何進(jìn)行并發(fā)測(cè)試并生成報(bào)告。根據(jù)本文的介紹,你能夠逐步搭建起一套完整的測(cè)試項(xiàng)目。
本文并沒(méi)有對(duì)Pytest的細(xì)節(jié)和比較高階的內(nèi)容做充分介紹,以后再進(jìn)行專題介紹,這篇文章主要目的是讓大家能夠?qū)ytest用起來(lái)。更高階的內(nèi)容,公眾號(hào)后續(xù)文章還將繼續(xù)對(duì)其進(jìn)行介紹。至此,我們的自動(dòng)化測(cè)試項(xiàng)目完整目錄結(jié)構(gòu)如下:
$ tree
.
├── Pipfile
├── Pipfile.lock
├── config
│ ├── prod
│ │ └── config.yaml
│ └── test
│ └── config.yaml
├── data
│ └── test_in_theaters.yaml
├── pytest.ini
├── tests
│ ├── conftest.py
│ ├── test_in_theaters.py
│ ├── test_marks.py
│ ├── test_smtpsimple.py
│ └── test_time.py
└── utils
└── commlib.py
?
6 directories, 12 files
參考資料
[1] https://docs.pytest.org/en/latest/
[2] https://www.guru99.com/pytest-tutorial.html?
到此這篇關(guān)于淺談基于Pytest框架的自動(dòng)化測(cè)試開(kāi)發(fā)實(shí)踐的文章就介紹到這了,更多相關(guān)Pytest自動(dòng)化測(cè)試內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Pytest 自動(dòng)化測(cè)試框架的使用
- pytest接口自動(dòng)化測(cè)試框架搭建的全過(guò)程
- pytest自動(dòng)化測(cè)試數(shù)據(jù)驅(qū)動(dòng)yaml/excel/csv/json
- python+pytest自動(dòng)化測(cè)試函數(shù)測(cè)試類測(cè)試方法的封裝
- Pytest+Yaml+Excel?接口自動(dòng)化測(cè)試框架的實(shí)現(xiàn)示例
- Python自動(dòng)化測(cè)試框架pytest的詳解安裝與運(yùn)行
- 自動(dòng)化測(cè)試Pytest單元測(cè)試框架的基本介紹
- python使用pytest接口自動(dòng)化測(cè)試的使用
- Pytest接口自動(dòng)化測(cè)試框架搭建模板
- 詳解如何使用Pytest進(jìn)行自動(dòng)化測(cè)試
- Pytest自動(dòng)化測(cè)試的具體使用
相關(guān)文章
python字符串string的內(nèi)置方法實(shí)例詳解
這篇文章主要介紹了python字符串string的內(nèi)置方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2018-05-05
詳解python3中socket套接字的編碼問(wèn)題解決
本篇文章主要介紹了詳解python3中socket套接字的編碼問(wèn)題解決,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
django channels使用和配置及實(shí)現(xiàn)群聊
本文主要介紹了django channels使用和配置及實(shí)現(xiàn)群聊,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
Python SQLite3數(shù)據(jù)庫(kù)日期與時(shí)間常見(jiàn)函數(shù)用法分析
這篇文章主要介紹了Python SQLite3數(shù)據(jù)庫(kù)日期與時(shí)間常見(jiàn)函數(shù)用法,結(jié)合實(shí)例形式分析了Python連接、查詢SQLite3數(shù)據(jù)以及數(shù)據(jù)庫(kù)日期與時(shí)間常見(jiàn)操作方法,需要的朋友可以參考下2017-08-08
解讀FastAPI異步化為transformers模型打造高性能接口
這篇文章主要介紹了解讀FastAPI異步化為transformers模型打造高性能接口問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
Python密碼學(xué)概述雙倍強(qiáng)度加密教程
這篇文章主要為大家介紹了Python密碼學(xué)概述雙倍強(qiáng)度加密教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>2022-05-05

