欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python單元測試的9個(gè)技巧技巧

 更新時(shí)間:2021年09月29日 10:04:34   作者:肖恩  
這篇文章主要給大家分享的是Python單元測試常見的幾個(gè)技巧,文章會(huì)講解requests的一些細(xì)節(jié)實(shí)現(xiàn)以及pytest的使用等,感興趣的小伙伴不妨和小編一起閱讀下面文章 的具體內(nèi)容吧

前言:

requestspython知名的http爬蟲庫,同樣簡單易用,是python開源項(xiàng)目的TOP10。

pytestpython的單元測試框架,簡單易用,在很多知名項(xiàng)目中應(yīng)用。requestspython知名的http爬蟲庫,同樣簡單易用,是python開源項(xiàng)目的TOP10。關(guān)于這2個(gè)項(xiàng)目,之前都有過介紹,本文主要介紹requests項(xiàng)目如何使用pytest進(jìn)行單元測試,會(huì)達(dá)到下面3個(gè)目標(biāo):

  • 熟練pytest的使用
  • 學(xué)習(xí)如何對(duì)項(xiàng)目進(jìn)行單元測試
  • 深入requests的一些實(shí)現(xiàn)細(xì)節(jié)

本文分如下幾個(gè)部分:

  • requests項(xiàng)目單元測試狀況
  • 簡單工具類如何測試
  • request-api如何測試
  • 底層API測試

1、requests項(xiàng)目單元測試狀況

requests的單元測試代碼全部在 tests 目錄,使用 pytest.ini 進(jìn)行配置。測試除pytest外,還需要安裝:

庫名 描述
httpbin 一個(gè)使用flask實(shí)現(xiàn)的http服務(wù),可以客戶端定義http響應(yīng),主要用于測試http協(xié)議
pytest-httpbin pytest的插件,封裝httpbin的實(shí)現(xiàn)
pytest-mock pytest的插件,提供mock
pytest-cov pytest的插件,提供覆蓋率

上述依賴 master 版本在requirement-dev文件中定義;2.24.0版本會(huì)在pipenv中定義。

測試用例使用make命令,子命令在Makefile中定義, 使用make ci運(yùn)行所有單元測試結(jié)果如下:

$ 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個(gè)測試用例,效果還是不錯(cuò)。使用 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 


結(jié)果顯示requests項(xiàng)目總體覆蓋率61%,每個(gè)模塊的覆蓋率也清晰可見。

單元測試覆蓋率使用代碼行數(shù)進(jìn)行判斷,Stmts顯示模塊的有效行數(shù),Miss顯示未執(zhí)行到的行。如果生成html的報(bào)告,還可以定位到具體未覆蓋到的行;pycharmcoverage也有類似功能。

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 模擬服務(wù)
test_testserver 模擬服務(wù)測試
test_lowlevel 使用模擬服務(wù)測試模擬網(wǎng)絡(luò)測試

2、簡單工具類如何測試

2.1 test_help 實(shí)現(xiàn)分析

先從最簡單的test_help上手,測試類和被測試對(duì)象命名是對(duì)應(yīng)的。先看看被測試的模塊help.py。這個(gè)模塊主要是2個(gè)函數(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)部實(shí)現(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'} 


首先從頭部的導(dǎo)入信息可以看到,僅僅對(duì)info函數(shù)進(jìn)行測試,這個(gè)容易理解。info測試通過,自然覆蓋到_implementation這個(gè)內(nèi)部函數(shù)。這里可以得到單元測試的第1個(gè)技巧:僅對(duì)public的接口進(jìn)行測試

test_idna_without_version_attributetest_idna_with_version_attribute均有一個(gè)mocker參數(shù),這是pytest-mock提供的功能,會(huì)自動(dòng)注入一個(gè)mock實(shí)現(xiàn)。使用這個(gè)mock對(duì)idna模塊進(jìn)行模擬

# 模擬空實(shí)現(xiàn) 
mocker.patch('requests.help.idna', new=None) 
# 模擬版本2.6 
mocker.patch('requests.help.idna', new=VersionedPackage('2.6')) 


可能大家會(huì)比較奇怪,這里patch模擬的是 requests.help.idna , 而我們在help中導(dǎo)入的是 inda 模塊。這是因?yàn)樵?code>requests.packages中對(duì)inda進(jìn)行了模塊名重定向:

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__信息就可以進(jìn)行控制,這樣info中的idna結(jié)果也就可以預(yù)期。那么可以得到第2個(gè)技巧:使用mock輔助單元測試

2.2 test_hooks 實(shí)現(xiàn)分析

我們繼續(xù)查看hooks如何進(jìn)行測試:

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個(gè)接口default_hooksdispatch_hook都進(jìn)行了測試。其中default_hooks是純函數(shù),無參數(shù)有返回值,這種函數(shù)最容易測試,僅僅檢查返回值是否符合預(yù)期即可。dispatch_hook會(huì)復(fù)雜一些,還涉及對(duì)回調(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ù)進(jìn)行測試。第一組參數(shù)hook和ata很簡單,hook是一個(gè)函數(shù),會(huì)對(duì)參數(shù)裁剪,去掉首位,ata是期望的返回值。test_hooksresponse的參數(shù)是Data,所以結(jié)果應(yīng)該是ata。第二組參數(shù)中的第一個(gè)參數(shù)會(huì)復(fù)雜一些,變成了一個(gè)數(shù)組,首位還是hook函數(shù),中間使用一個(gè)匿名函數(shù),匿名函數(shù)沒有返回值,這樣覆蓋到 if _hook_data is not None: 的旁路分支。執(zhí)行過程如下:

  • hook函數(shù)裁剪Data首位,剩余ata
  • 匿名函數(shù)不對(duì)結(jié)果修改,剩余ata
  • hook函數(shù)繼續(xù)裁剪ata首位,剩余ta

經(jīng)過測試可以發(fā)現(xiàn)dispatch_hook的設(shè)計(jì)十分巧妙,使用pipeline模式,將所有的鉤子串起來,這是和事件機(jī)制不一樣的地方。細(xì)心的話,我們可以發(fā)現(xiàn) if hooks: 并未進(jìn)行旁路測試,這個(gè)不夠嚴(yán)謹(jǐn),有違我們的第3個(gè)技巧:

測試盡可能覆蓋目標(biāo)函數(shù)的所有分支

2.3 test_structures 實(shí)現(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,給所有測試用例初始化了一個(gè)lookup_dict對(duì)象;同時(shí)pytest.mark.parametrize可以在不同的測試用例之間復(fù)用的,我們可以得到第4個(gè)技巧:

使用pytest.fixture復(fù)用被測試對(duì)象,使用pytest.mark.parametriz復(fù)用測試參數(shù)

通過TestLookupDicttest_getitemtest_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_structurestest_requests中都有測試,前者主要是基礎(chǔ)測試,后者偏向業(yè)務(wù)使用層面,我們可以看到這兩種差異:

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個(gè)技巧:

可以從不同的層面對(duì)同一個(gè)對(duì)象進(jìn)行單元測試

后面的test_lowleveltest_requests也應(yīng)用了這種技巧

2.4 utils.py

utils中構(gòu)建了一個(gè)可以寫入env的生成器(由yield關(guān)鍵字提供),可以當(dāng)上下文裝飾器使用:

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個(gè)技巧:涉及環(huán)境變量的地方,可以使用上下文裝飾器進(jìn)行模擬多種環(huán)境變量

2.5 utils測試用例

utils的測試用例較多,我們選擇部分進(jìn)行分析。先看to_key_val_list函數(shù):

# 對(duì)象轉(zhuǎn)列表

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)

對(duì)應(yīng)的測試用例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')

重點(diǎn)是test_invalid中使用pytest.raise對(duì)異常的處理:

第7個(gè)技巧:使用pytest.raises對(duì)異常進(jìn)行捕獲處理

TestSuperLen介紹了幾種進(jìn)行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的測試。當(dāng)然也可以使用BytesIO/cStringIO, 不過單元測試用例一般不關(guān)注性能,StringIO簡單夠用。

pytest提供tmpdirfixture,可以進(jìn)行文件讀寫操作測試

可以使用__file__來進(jìn)行文件的只讀測試,__file__表示當(dāng)前文件,不會(huì)產(chǎn)生副作用。

第8個(gè)技巧:使用IO模擬配合進(jìn)行單元測試

2.6 request-api如何測試

requests的測試需要httpbinpytest-httpbin,前者會(huì)啟動(dòng)一個(gè)本地服務(wù),后者會(huì)安裝一個(gè)pytest插件,測試用例中可以得到httpbinfixture,用來操作這個(gè)服務(wù)的URL。

功能
TestRequests requests業(yè)務(wù)測試
TestCaseInsensitiveDict 大小寫不敏感的字典測試
TestMorselToCookieExpires cookie過期測試
TestMorselToCookieMaxAge cookie大小
TestTimeout 響應(yīng)超時(shí)的測試
TestPreparingURLs URL預(yù)處理
... 一些零碎的測試用例

坦率的講:這個(gè)測試用例內(nèi)容龐大,達(dá)到2500行??雌饋硎轻槍?duì)各種業(yè)務(wù)的零散case,我并沒有完全理順其組織邏輯。我選擇一些感興趣的業(yè)務(wù)進(jìn)行介紹, 先看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)建了一個(gè)延遲10s響應(yīng)的接口,然后請(qǐng)求本身設(shè)置成2s,這樣可以收到一個(gè)本地timeout的錯(cuò)誤。test_connect_timeout則是訪問一個(gè)不存在的服務(wù),捕獲連接超時(shí)的錯(cuò)誤。

TestRequests都是對(duì)requests的業(yè)務(wù)進(jìn)程測試,可以看到至少是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'

  • 對(duì)url進(jìn)行校驗(yàn),只需要對(duì)request進(jìn)行prepare,這種情況下,請(qǐng)求并未發(fā)送,少了網(wǎng)絡(luò)傳輸,測試用例會(huì)更迅速
  • 需要響應(yīng)數(shù)據(jù)的情況,需要使用httbin構(gòu)建真實(shí)的請(qǐng)求-響應(yīng)數(shù)據(jù)

3、底層API測試

testserver構(gòu)建一個(gè)簡單的基于線程的tcp服務(wù),這個(gè)tcp服務(wù)具有__enter____exit__方法,還可以當(dāng)一個(gè)上下文環(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方法對(duì)Server進(jìn)行基礎(chǔ)校驗(yàn),確保收發(fā)雙方可以正確的發(fā)送和接收數(shù)據(jù)。先是客戶端的sock發(fā)送question,然后服務(wù)端在handler中判斷收到的數(shù)據(jù)是question,確認(rèn)后返回answer,最后客戶端再確認(rèn)可以正確收到answer響應(yīng)。test_text_response方法則不完整的測試了http協(xié)議。按照http協(xié)議的規(guī)范發(fā)送了http請(qǐng)求,Server.text_response_server會(huì)回顯請(qǐng)求。下面是模擬瀏覽器的錨點(diǎn)定位不會(huì)經(jīng)過網(wǎng)絡(luò)傳輸?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()

可以看到請(qǐng)求的path /path/to/thing/#view=edit&token=hunter2,其中 # 后面的部分是本地錨點(diǎn),不應(yīng)該進(jìn)行網(wǎng)絡(luò)傳輸。上面測試用例中,對(duì)接收到的響應(yīng)進(jìn)行判斷,鑒別響應(yīng)頭和響應(yīng)body中不包含這些關(guān)鍵字。

結(jié)合requests的兩個(gè)層面的測試,們可以得出第9個(gè)技巧:

構(gòu)造模擬服務(wù)配合測試

小結(jié):

簡單小結(jié)一下,從requests的單元測試實(shí)踐中,可以得到下面9個(gè)技巧:

  1. 僅對(duì)public的接口進(jìn)行測試
  2. 使用mock輔助單元測試
  3. 測試盡可能覆蓋目標(biāo)函數(shù)的所有分支
  4. 使用pytest.fixture復(fù)用被測試對(duì)象,使用pytest.mark.parametriz復(fù)用測試參數(shù)
  5. 可以從不同的層面對(duì)同一個(gè)對(duì)象進(jìn)行單元測試
  6. 涉及環(huán)境變量的地方,可以使用上下文裝飾器進(jìn)行模擬多種環(huán)境變量
  7. 使用pytest.raises對(duì)異常進(jìn)行捕獲處理
  8. 使用IO模擬配合進(jìn)行單元測試
  9. 構(gòu)造模擬服務(wù)配合測試

到此這篇關(guān)于Python單元測試常見技巧的文章就介紹到這了,更多相關(guān)Python單元測試技巧內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 有趣的python小程序分享

    有趣的python小程序分享

    這篇文章主要介紹了有趣的python小程序分享,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-12-12
  • 詳解使用pymysql在python中對(duì)mysql的增刪改查操作(綜合)

    詳解使用pymysql在python中對(duì)mysql的增刪改查操作(綜合)

    本篇文章主要介紹了使用pymysql在python中對(duì)mysql的增刪改查操作,通過pymysql向數(shù)據(jù)庫進(jìn)行查刪增改,具有一定的參考價(jià)值,有興趣的可以了解一下。
    2017-01-01
  • 利用jupyter網(wǎng)頁版本進(jìn)行python函數(shù)查詢方式

    利用jupyter網(wǎng)頁版本進(jìn)行python函數(shù)查詢方式

    這篇文章主要介紹了利用jupyter網(wǎng)頁版本進(jìn)行python函數(shù)查詢方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-04-04
  • python zip()函數(shù)的使用示例

    python zip()函數(shù)的使用示例

    這篇文章主要介紹了python zip()的使用示例,幫助大家更好的理解和學(xué)習(xí)python,感興趣的朋友可以了解下
    2020-09-09
  • python操作日期和時(shí)間的方法

    python操作日期和時(shí)間的方法

    經(jīng)常獲得了一個(gè)用戶提交的當(dāng)前日期,我們需要以這個(gè)日期為依據(jù)返回它的前一天、后一天的日期或者轉(zhuǎn)換操作等。用Python可以非常簡單的解決這些關(guān)于日期計(jì)算的問題
    2014-03-03
  • Python+Matplotlib繪制高亮顯示餅圖的示例代碼

    Python+Matplotlib繪制高亮顯示餅圖的示例代碼

    餅圖 (Pie Chart) 是一種圓形統(tǒng)計(jì)圖,被分割成片用于表示數(shù)值間的比例關(guān)系,本文為大家介紹了Matplotlib繪制高亮顯示的餅圖的函數(shù)源碼,需要的可以參考一下
    2023-06-06
  • python打包pyinstall的實(shí)現(xiàn)步驟

    python打包pyinstall的實(shí)現(xiàn)步驟

    PyInstaller可將Python代碼打包成單個(gè)可執(zhí)行文件,本文主要介紹了python打包pyinstall的實(shí)現(xiàn)步驟,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-10-10
  • Python中rapidjson參數(shù)校驗(yàn)實(shí)現(xiàn)

    Python中rapidjson參數(shù)校驗(yàn)實(shí)現(xiàn)

    通常需要對(duì)前端傳遞過來的參數(shù)進(jìn)行校驗(yàn),校驗(yàn)的方式有多種,本文主要介紹了Python中rapidjson參數(shù)校驗(yàn)實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • Python 抓取數(shù)據(jù)存儲(chǔ)到Redis中的操作

    Python 抓取數(shù)據(jù)存儲(chǔ)到Redis中的操作

    這篇文章主要介紹了Python 抓取數(shù)據(jù)存儲(chǔ)到Redis中的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-07-07
  • python中os模塊和sys模塊的使用詳解

    python中os模塊和sys模塊的使用詳解

    本文主要介紹了python中os模塊和sys模塊的使用詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03

最新評(píng)論