Python單元測試的9個技巧技巧
前言:
requests是python知名的http爬蟲庫,同樣簡單易用,是python開源項目的TOP10。
pytest是python的單元測試框架,簡單易用,在很多知名項目中應用。requests是python知名的http爬蟲庫,同樣簡單易用,是python開源項目的TOP10。關于這2個項目,之前都有過介紹,本文主要介紹requests項目如何使用pytest進行單元測試,會達到下面3個目標:
- 熟練
pytest的使用 - 學習如何對項目進行單元測試
- 深入
requests的一些實現(xiàn)細節(jié)
本文分如下幾個部分:
requests項目單元測試狀況- 簡單工具類如何測試
request-api如何測試- 底層API測試
1、requests項目單元測試狀況
requests的單元測試代碼全部在 tests 目錄,使用 pytest.ini 進行配置。測試除pytest外,還需要安裝:
| 庫名 | 描述 |
|---|---|
| httpbin | 一個使用flask實現(xiàn)的http服務,可以客戶端定義http響應,主要用于測試http協(xié)議 |
| pytest-httpbin | pytest的插件,封裝httpbin的實現(xiàn) |
| pytest-mock | pytest的插件,提供mock |
| pytest-cov | pytest的插件,提供覆蓋率 |
上述依賴 master 版本在requirement-dev文件中定義;2.24.0版本會在pipenv中定義。
測試用例使用make命令,子命令在Makefile中定義, 使用make ci運行所有單元測試結果如下:
$ make ci
pytest tests --junitxml=report.xml
======================================================================================================= test session starts =======================================================================================================
platform linux -- Python 3.6.8, pytest-3.10.1, py-1.10.0, pluggy-0.13.1
rootdir: /home/work6/project/requests, inifile: pytest.ini
plugins: mock-2.0.0, httpbin-1.0.0, cov-2.9.0
collected 552 items
tests/test_help.py ... [ 0%]
tests/test_hooks.py ... [ 1%]
tests/test_lowlevel.py ............... [ 3%]
tests/test_packages.py ... [ 4%]
tests/test_requests.py .................................................................................................................................................................................................... [ 39%]
127.0.0.1 - - [10/Aug/2021 08:41:53] "GET /stream/4 HTTP/1.1" 200 756
.127.0.0.1 - - [10/Aug/2021 08:41:53] "GET /stream/4 HTTP/1.1" 500 59
----------------------------------------
Exception happened during processing of request from ('127.0.0.1', 46048)
Traceback (most recent call last):
File "/usr/lib64/python3.6/wsgiref/handlers.py", line 138, in run
self.finish_response()
x......................................................................................... [ 56%]
tests/test_structures.py .................... [ 59%]
tests/test_testserver.py ......s.... [ 61%]
tests/test_utils.py ..s................................................................................................................................................................................................ssss [ 98%]
ssssss..... [100%]
----------------------------------------------------------------------------------- generated xml file: /home/work6/project/requests/report.xml -----------------------------------------------------------------------------------
======================================================================================= 539 passed, 12 skipped, 1 xfailed in 64.16 seconds ========================================================================================
可以看到requests在1分鐘內(nèi),總共通過了539個測試用例,效果還是不錯。使用 make coverage 查看單元測試覆蓋率:
$ make coverage ----------- coverage: platform linux, python 3.6.8-final-0 ----------- Name Stmts Miss Cover ------------------------------------------------- requests/__init__.py 71 71 0% requests/__version__.py 10 10 0% requests/_internal_utils.py 16 5 69% requests/adapters.py 222 67 70% requests/api.py 20 13 35% requests/auth.py 174 54 69% requests/certs.py 4 4 0% requests/compat.py 47 47 0% requests/cookies.py 238 115 52% requests/exceptions.py 35 29 17% requests/help.py 63 19 70% requests/hooks.py 15 4 73% requests/models.py 455 119 74% requests/packages.py 16 16 0% requests/sessions.py 283 67 76% requests/status_codes.py 15 15 0% requests/structures.py 40 19 52% requests/utils.py 465 170 63% ------------------------------------------------- TOTAL 2189 844 61% Coverage XML written to file coverage.xml
結果顯示requests項目總體覆蓋率61%,每個模塊的覆蓋率也清晰可見。
單元測試覆蓋率使用代碼行數(shù)進行判斷,Stmts顯示模塊的有效行數(shù),Miss顯示未執(zhí)行到的行。如果生成html的報告,還可以定位到具體未覆蓋到的行;pycharm的coverage也有類似功能。
tests下的文件及測試類如下表:
| 文件 | 描述 |
|---|---|
| compat | python2和python3兼容 |
| conftest | pytest配置 |
| test_help,test_packages,test_hooks,test_structures | 簡單測試類 |
| utils.py | 工具函數(shù) |
| test_utils | 測試工具函數(shù) |
| test_requests | 測試requests |
| testserver\server | 模擬服務 |
| test_testserver | 模擬服務測試 |
| test_lowlevel | 使用模擬服務測試模擬網(wǎng)絡測試 |
2、簡單工具類如何測試
2.1 test_help 實現(xiàn)分析
先從最簡單的test_help上手,測試類和被測試對象命名是對應的。先看看被測試的模塊help.py。這個模塊主要是2個函數(shù) info 和 _implementation:
import idna
def _implementation():
...
def info():
...
system_ssl = ssl.OPENSSL_VERSION_NUMBER
system_ssl_info = {
'version': '%x' % system_ssl if system_ssl is not None else ''
}
idna_info = {
'version': getattr(idna, '__version__', ''),
}
...
return {
'platform': platform_info,
'implementation': implementation_info,
'system_ssl': system_ssl_info,
'using_pyopenssl': pyopenssl is not None,
'pyOpenSSL': pyopenssl_info,
'urllib3': urllib3_info,
'chardet': chardet_info,
'cryptography': cryptography_info,
'idna': idna_info,
'requests': {
'version': requests_version,
},
}
info提供系統(tǒng)環(huán)境的信息, _implementation是其內(nèi)部實現(xiàn),以下劃線*_*開頭。再看測試類test_help:
from requests.help import info
def test_system_ssl():
"""Verify we're actually setting system_ssl when it should be available."""
assert info()['system_ssl']['version'] != ''
class VersionedPackage(object):
def __init__(self, version):
self.__version__ = version
def test_idna_without_version_attribute(mocker):
"""Older versions of IDNA don't provide a __version__ attribute, verify
that if we have such a package, we don't blow up.
"""
mocker.patch('requests.help.idna', new=None)
assert info()['idna'] == {'version': ''}
def test_idna_with_version_attribute(mocker):
"""Verify we're actually setting idna version when it should be available."""
mocker.patch('requests.help.idna', new=VersionedPackage('2.6'))
assert info()['idna'] == {'version': '2.6'}
首先從頭部的導入信息可以看到,僅僅對info函數(shù)進行測試,這個容易理解。info測試通過,自然覆蓋到_implementation這個內(nèi)部函數(shù)。這里可以得到單元測試的第1個技巧:僅對public的接口進行測試
test_idna_without_version_attribute和test_idna_with_version_attribute均有一個mocker參數(shù),這是pytest-mock提供的功能,會自動注入一個mock實現(xiàn)。使用這個mock對idna模塊進行模擬
# 模擬空實現(xiàn)
mocker.patch('requests.help.idna', new=None)
# 模擬版本2.6
mocker.patch('requests.help.idna', new=VersionedPackage('2.6'))
可能大家會比較奇怪,這里patch模擬的是 requests.help.idna , 而我們在help中導入的是 inda 模塊。這是因為在requests.packages中對inda進行了模塊名重定向:
for package in ('urllib3', 'idna', 'chardet'):
locals()[package] = __import__(package)
# This traversal is apparently necessary such that the identities are
# preserved (requests.packages.urllib3.* is urllib3.*)
for mod in list(sys.modules):
if mod == package or mod.startswith(package + '.'):
sys.modules['requests.packages.' + mod] = sys.modules[mod]
使用mocker后,idna的__version__信息就可以進行控制,這樣info中的idna結果也就可以預期。那么可以得到第2個技巧:使用mock輔助單元測試
2.2 test_hooks 實現(xiàn)分析
我們繼續(xù)查看hooks如何進行測試:
from requests import hooks
def hook(value):
return value[1:]
@pytest.mark.parametrize(
'hooks_list, result', (
(hook, 'ata'),
([hook, lambda x: None, hook], 'ta'),
)
)
def test_hooks(hooks_list, result):
assert hooks.dispatch_hook('response', {'response': hooks_list}, 'Data') == result
def test_default_hooks():
assert hooks.default_hooks() == {'response': []}
hooks模塊的2個接口default_hooks和dispatch_hook都進行了測試。其中default_hooks是純函數(shù),無參數(shù)有返回值,這種函數(shù)最容易測試,僅僅檢查返回值是否符合預期即可。dispatch_hook會復雜一些,還涉及對回調(diào)函數(shù)(hook函數(shù))的調(diào)用:
def dispatch_hook(key, hooks, hook_data, **kwargs):
"""Dispatches a hook dictionary on a given piece of data."""
hooks = hooks or {}
hooks = hooks.get(key)
if hooks:
# 判斷鉤子函數(shù)
if hasattr(hooks, '__call__'):
hooks = [hooks]
for hook in hooks:
_hook_data = hook(hook_data, **kwargs)
if _hook_data is not None:
hook_data = _hook_data
return hook_data
pytest.mark.parametrize提供了2組參數(shù)進行測試。第一組參數(shù)hook和ata很簡單,hook是一個函數(shù),會對參數(shù)裁剪,去掉首位,ata是期望的返回值。test_hooks的response的參數(shù)是Data,所以結果應該是ata。第二組參數(shù)中的第一個參數(shù)會復雜一些,變成了一個數(shù)組,首位還是hook函數(shù),中間使用一個匿名函數(shù),匿名函數(shù)沒有返回值,這樣覆蓋到 if _hook_data is not None: 的旁路分支。執(zhí)行過程如下:
hook函數(shù)裁剪Data首位,剩余ata- 匿名函數(shù)不對結果修改,剩余ata
hook函數(shù)繼續(xù)裁剪ata首位,剩余ta
經(jīng)過測試可以發(fā)現(xiàn)dispatch_hook的設計十分巧妙,使用pipeline模式,將所有的鉤子串起來,這是和事件機制不一樣的地方。細心的話,我們可以發(fā)現(xiàn) if hooks: 并未進行旁路測試,這個不夠嚴謹,有違我們的第3個技巧:
測試盡可能覆蓋目標函數(shù)的所有分支
2.3 test_structures 實現(xiàn)分析
LookupDict的測試用例如下:
class TestLookupDict:
@pytest.fixture(autouse=True)
def setup(self):
"""LookupDict instance with "bad_gateway" attribute."""
self.lookup_dict = LookupDict('test')
self.lookup_dict.bad_gateway = 502
def test_repr(self):
assert repr(self.lookup_dict) == "<lookup 'test'>"
get_item_parameters = pytest.mark.parametrize(
'key, value', (
('bad_gateway', 502),
('not_a_key', None)
)
)
@get_item_parameters
def test_getitem(self, key, value):
assert self.lookup_dict[key] == value
@get_item_parameters
def test_get(self, key, value):
assert self.lookup_dict.get(key) == value
可以發(fā)現(xiàn)使用setup方法配合@pytest.fixture,給所有測試用例初始化了一個lookup_dict對象;同時pytest.mark.parametrize可以在不同的測試用例之間復用的,我們可以得到第4個技巧:
使用pytest.fixture復用被測試對象,使用pytest.mark.parametriz復用測試參數(shù)
通過TestLookupDict的test_getitem和test_get可以更直觀的了解LookupDict的get和__getitem__方法的作用:
class LookupDict(dict):
...
def __getitem__(self, key):
# We allow fall-through here, so values default to None
return self.__dict__.get(key, None)
def get(self, key, default=None):
return self.__dict__.get(key, default)
- get自定義字典,使其可以使用 get 方法獲取值
- __getitem__自定義字典,使其可以使用 [] 符合獲取值
CaseInsensitiveDict的測試用例在test_structures和test_requests中都有測試,前者主要是基礎測試,后者偏向業(yè)務使用層面,我們可以看到這兩種差異:
class TestCaseInsensitiveDict:
# 類測試
def test_repr(self):
assert repr(self.case_insensitive_dict) == "{'Accept': 'application/json'}"
def test_copy(self):
copy = self.case_insensitive_dict.copy()
assert copy is not self.case_insensitive_dict
assert copy == self.case_insensitive_dict
class TestCaseInsensitiveDict:
# 使用方法測試
def test_delitem(self):
cid = CaseInsensitiveDict()
cid['Spam'] = 'someval'
del cid['sPam']
assert 'spam' not in cid
assert len(cid) == 0
def test_contains(self):
cid = CaseInsensitiveDict()
cid['Spam'] = 'someval'
assert 'Spam' in cid
assert 'spam' in cid
assert 'SPAM' in cid
assert 'sPam' in cid
assert 'notspam' not in cid
借鑒上面的測試方法,不難得出第5個技巧:
可以從不同的層面對同一個對象進行單元測試
后面的test_lowlevel和test_requests也應用了這種技巧
2.4 utils.py
utils中構建了一個可以寫入env的生成器(由yield關鍵字提供),可以當上下文裝飾器使用:
import contextlib import os @contextlib.contextmanager def override_environ(**kwargs): save_env = dict(os.environ) for key, value in kwargs.items(): if value is None: del os.environ[key] else: os.environ[key] = value try: yield finally: os.environ.clear() os.environ.update(save_env)
下面是使用方法示例:
# test_requests.py
kwargs = {
var: proxy
}
# 模擬控制proxy環(huán)境變量
with override_environ(**kwargs):
proxies = session.rebuild_proxies(prep, {})
def rebuild_proxies(self, prepared_request, proxies):
bypass_proxy = should_bypass_proxies(url, no_proxy=no_proxy)
def should_bypass_proxies(url, no_proxy):
...
get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper())
...
得出第6個技巧:涉及環(huán)境變量的地方,可以使用上下文裝飾器進行模擬多種環(huán)境變量
2.5 utils測試用例
utils的測試用例較多,我們選擇部分進行分析。先看to_key_val_list函數(shù):
# 對象轉列表
def to_key_val_list(value):
if value is None:
return None
if isinstance(value, (str, bytes, bool, int)):
raise ValueError('cannot encode objects that are not 2-tuples')
if isinstance(value, Mapping):
value = value.items()
return list(value)
對應的測試用例TestToKeyValList:
class TestToKeyValList:
@pytest.mark.parametrize(
'value, expected', (
([('key', 'val')], [('key', 'val')]),
((('key', 'val'), ), [('key', 'val')]),
({'key': 'val'}, [('key', 'val')]),
(None, None)
))
def test_valid(self, value, expected):
assert to_key_val_list(value) == expected
def test_invalid(self):
with pytest.raises(ValueError):
to_key_val_list('string')
重點是test_invalid中使用pytest.raise對異常的處理:
第7個技巧:使用pytest.raises對異常進行捕獲處理
TestSuperLen介紹了幾種進行IO模擬測試的方法:
class TestSuperLen:
@pytest.mark.parametrize(
'stream, value', (
(StringIO.StringIO, 'Test'),
(BytesIO, b'Test'),
pytest.param(cStringIO, 'Test',
marks=pytest.mark.skipif('cStringIO is None')),
))
def test_io_streams(self, stream, value):
"""Ensures that we properly deal with different kinds of IO streams."""
assert super_len(stream()) == 0
assert super_len(stream(value)) == 4
def test_super_len_correctly_calculates_len_of_partially_read_file(self):
"""Ensure that we handle partially consumed file like objects."""
s = StringIO.StringIO()
s.write('foobarbogus')
assert super_len(s) == 0
@pytest.mark.parametrize(
'mode, warnings_num', (
('r', 1),
('rb', 0),
))
def test_file(self, tmpdir, mode, warnings_num, recwarn):
file_obj = tmpdir.join('test.txt')
file_obj.write('Test')
with file_obj.open(mode) as fd:
assert super_len(fd) == 4
assert len(recwarn) == warnings_num
def test_super_len_with_tell(self):
foo = StringIO.StringIO('12345')
assert super_len(foo) == 5
foo.read(2)
assert super_len(foo) == 3
def test_super_len_with_fileno(self):
with open(__file__, 'rb') as f:
length = super_len(f)
file_data = f.read()
assert length == len(file_data)
使用StringIO來模擬IO操作,可以配置各種IO的測試。當然也可以使用BytesIO/cStringIO, 不過單元測試用例一般不關注性能,StringIO簡單夠用。
pytest提供tmpdir的fixture,可以進行文件讀寫操作測試
可以使用__file__來進行文件的只讀測試,__file__表示當前文件,不會產(chǎn)生副作用。
第8個技巧:使用IO模擬配合進行單元測試
2.6 request-api如何測試
requests的測試需要httpbin和pytest-httpbin,前者會啟動一個本地服務,后者會安裝一個pytest插件,測試用例中可以得到httpbin的fixture,用來操作這個服務的URL。
| 類 | 功能 |
|---|---|
| TestRequests | requests業(yè)務測試 |
| TestCaseInsensitiveDict | 大小寫不敏感的字典測試 |
| TestMorselToCookieExpires | cookie過期測試 |
| TestMorselToCookieMaxAge | cookie大小 |
| TestTimeout | 響應超時的測試 |
| TestPreparingURLs | URL預處理 |
| ... | 一些零碎的測試用例 |
坦率的講:這個測試用例內(nèi)容龐大,達到2500行??雌饋硎轻槍Ω鞣N業(yè)務的零散case,我并沒有完全理順其組織邏輯。我選擇一些感興趣的業(yè)務進行介紹, 先看TimeOut的測試:
TARPIT = 'http://10.255.255.1'
class TestTimeout:
def test_stream_timeout(self, httpbin):
try:
requests.get(httpbin('delay/10'), timeout=2.0)
except requests.exceptions.Timeout as e:
assert 'Read timed out' in e.args[0].args[0]
@pytest.mark.parametrize(
'timeout', (
(0.1, None),
Urllib3Timeout(connect=0.1, read=None)
))
def test_connect_timeout(self, timeout):
try:
requests.get(TARPIT, timeout=timeout)
pytest.fail('The connect() request should time out.')
except ConnectTimeout as e:
assert isinstance(e, ConnectionError)
assert isinstance(e, Timeout)
test_stream_timeout利用httpbin創(chuàng)建了一個延遲10s響應的接口,然后請求本身設置成2s,這樣可以收到一個本地timeout的錯誤。test_connect_timeout則是訪問一個不存在的服務,捕獲連接超時的錯誤。
TestRequests都是對requests的業(yè)務進程測試,可以看到至少是2種:
class TestRequests:
def test_basic_building(self):
req = requests.Request()
req.url = 'http://kennethreitz.org/'
req.data = {'life': '42'}
pr = req.prepare()
assert pr.url == req.url
assert pr.body == 'life=42'
def test_path_is_not_double_encoded(self):
request = requests.Request('GET', "http://0.0.0.0/get/test case").prepare()
assert request.path_url == '/get/test%20case
...
def test_HTTP_200_OK_GET_ALTERNATIVE(self, httpbin):
r = requests.Request('GET', httpbin('get'))
s = requests.Session()
s.proxies = getproxies()
r = s.send(r.prepare())
assert r.status_code == 200
ef test_set_cookie_on_301(self, httpbin):
s = requests.session()
url = httpbin('cookies/set?foo=bar')
s.get(url)
assert s.cookies['foo'] == 'bar'
- 對url進行校驗,只需要對
request進行prepare,這種情況下,請求并未發(fā)送,少了網(wǎng)絡傳輸,測試用例會更迅速 - 需要響應數(shù)據(jù)的情況,需要使用
httbin構建真實的請求-響應數(shù)據(jù)
3、底層API測試
testserver構建一個簡單的基于線程的tcp服務,這個tcp服務具有__enter__和__exit__方法,還可以當一個上下文環(huán)境使用。
class TestTestServer:
def test_basic(self):
"""messages are sent and received properly"""
question = b"success?"
answer = b"yeah, success"
def handler(sock):
text = sock.recv(1000)
assert text == question
sock.sendall(answer)
with Server(handler) as (host, port):
sock = socket.socket()
sock.connect((host, port))
sock.sendall(question)
text = sock.recv(1000)
assert text == answer
sock.close()
def test_text_response(self):
"""the text_response_server sends the given text"""
server = Server.text_response_server(
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 6\r\n" +
"\r\nroflol"
)
with server as (host, port):
r = requests.get('http://{}:{}'.format(host, port))
assert r.status_code == 200
assert r.text == u'roflol'
assert r.headers['Content-Length'] == '6'
test_basic方法對Server進行基礎校驗,確保收發(fā)雙方可以正確的發(fā)送和接收數(shù)據(jù)。先是客戶端的sock發(fā)送question,然后服務端在handler中判斷收到的數(shù)據(jù)是question,確認后返回answer,最后客戶端再確認可以正確收到answer響應。test_text_response方法則不完整的測試了http協(xié)議。按照http協(xié)議的規(guī)范發(fā)送了http請求,Server.text_response_server會回顯請求。下面是模擬瀏覽器的錨點定位不會經(jīng)過網(wǎng)絡傳輸?shù)膖estcase:
def test_fragment_not_sent_with_request():
"""Verify that the fragment portion of a URI isn't sent to the server."""
def response_handler(sock):
req = consume_socket_content(sock, timeout=0.5)
sock.send(
b'HTTP/1.1 200 OK\r\n'
b'Content-Length: '+bytes(len(req))+b'\r\n'
b'\r\n'+req
)
close_server = threading.Event()
server = Server(response_handler, wait_to_close_event=close_server)
with server as (host, port):
url = 'http://{}:{}/path/to/thing/#view=edit&token=hunter2'.format(host, port)
r = requests.get(url)
raw_request = r.content
assert r.status_code == 200
headers, body = raw_request.split(b'\r\n\r\n', 1)
status_line, headers = headers.split(b'\r\n', 1)
assert status_line == b'GET /path/to/thing/ HTTP/1.1'
for frag in (b'view', b'edit', b'token', b'hunter2'):
assert frag not in headers
assert frag not in body
close_server.set()
可以看到請求的path是 /path/to/thing/#view=edit&token=hunter2,其中 # 后面的部分是本地錨點,不應該進行網(wǎng)絡傳輸。上面測試用例中,對接收到的響應進行判斷,鑒別響應頭和響應body中不包含這些關鍵字。
結合requests的兩個層面的測試,我們可以得出第9個技巧:
構造模擬服務配合測試
小結:
簡單小結一下,從requests的單元測試實踐中,可以得到下面9個技巧:
- 僅對
public的接口進行測試 - 使用
mock輔助單元測試 - 測試盡可能覆蓋目標函數(shù)的所有分支
- 使用
pytest.fixture復用被測試對象,使用pytest.mark.parametriz復用測試參數(shù) - 可以從不同的層面對同一個對象進行單元測試
- 涉及環(huán)境變量的地方,可以使用上下文裝飾器進行模擬多種環(huán)境變量
- 使用
pytest.raises對異常進行捕獲處理 - 使用IO模擬配合進行單元測試
- 構造模擬服務配合測試
到此這篇關于Python單元測試常見技巧的文章就介紹到這了,更多相關Python單元測試技巧內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
python for循環(huán)內(nèi)輸出和外輸出方式
這篇文章主要介紹了python for循環(huán)內(nèi)輸出和外輸出方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03
Python+OpenCV實現(xiàn)基于顏色的目標識別
這篇文章主要介紹了利用OpenCV實現(xiàn)基于顏色的目標識別,即讓攝像頭識別到視野范圍內(nèi)的有顏色的氣球并返回每個氣球的中心點坐標,感興趣的可以跟隨小編學習一下2022-01-01
Python?OpenCV超詳細講解讀取圖像視頻和網(wǎng)絡攝像頭
OpenCV用C++語言編寫,它具有C?++,Python,Java和MATLAB接口,并支持Windows,Linux,Android和Mac?OS,OpenCV主要傾向于實時視覺應用,并在可用時利用MMX和SSE指令,本篇文章帶你了解OpenCV讀取圖像視頻與網(wǎng)絡攝像頭的方法2022-04-04
pandas map(),apply(),applymap()區(qū)別解析
這篇文章主要介紹了pandas map(),apply(),applymap()區(qū)別解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-02-02
python+Matplotlib?繪制帶置信區(qū)間的折線圖
這篇文章主要介紹了python繪制帶置信區(qū)間的折線圖,在本文中,我們將使用?numpy?模塊生成隨機數(shù)據(jù),并使用?matplotlib?庫實現(xiàn)數(shù)據(jù)可視化,需要的朋友可以參考下2023-05-05
OpenCV-DFT最優(yōu)尺寸cv::getOptimalDFTSize的設置
本文主要介紹了OpenCV-DFT最優(yōu)尺寸cv::getOptimalDFTSize的設置,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09

