A guide to my Python testing style

In this article, I tried to collect a few of my Python testing techniques. Do not take them as dogma, because I think over time I will update my practices.





A bit of terminology

  • The target is what you are currently testing. Perhaps it is a function, method, or behavior generated by a collection of elements.





  • โ€“ , . , ( , ), โ€“ , , .





  • - . -. . , .





  • โ€“ , . -, ยซยป .





,

pytest. , . , transport.py test_transport.py.





, .





def refresh(...):
    ...

def test_refresh():
    ...
      
      



, , , :





def test_refresh_failure():
    ...

def test_refresh_with_timeout():
    ...
      
      



, , , , . , :





class Thing(object):
   ...

class TestThing(object):
    def test_something(self):
       ...
      
      



test_constructor



, , test_default_state







def test_default_state(self):
    credentials = self.make_credentials()
    # No access token, so these shouldn't be valid.
    assert not credentials.valid
    # Expiration hasn't been set yet
    assert not credentials.expired
    # Scopes aren't required for these credentials
    assert not credentials.requires_scopes
      
      



assert , ,

, . , : ( ) . . . , , . , .





, , , :





test_payload = {'test': 'value'}
encoded = jwt.encode(signer, test_payload)
expected_header = {'typ': 'JWT', 'alg': 'RS256', 'kid': signer.key_id}
expected_call = json.dumps(expected_header) + '.' + json.dumps(test_payload)
signer.sign.assert_called_once_with(expected_call)
      
      



, , , :





test_payload = {'test': 'value'}
encoded = jwt.encode(signer, test_payload)
header, payload, _, _ = jwt._unverified_decode(encoded)
assert payload == test_payload
assert header == {'typ': 'JWT', 'alg': 'RS256', 'kid': signer.key_id}
      
      



, assert_call



*, , , . , , , , , , . , ( ).





,

, - , , . , , .





, ( ):





signer = mock.create_autospec(crypt.Signer, instance=True)
signer.key_id = 1

test_payload = {'test': 'value'}
encoded = jwt.encode(signer, test_payload)

expected_header = {'typ': 'JWT', 'alg': 'RS256', 'kid': signer.key_id}
expected_call = json.dumps(expected_header) + '.' + json.dumps(test_payload)
signer.sign.assert_called_once_with(expected_call)
      
      



- , , :





signer = crypt.RSASigner.from_string(PRIVATE_KEY_BYTES, '1')
test_payload = {'test': 'value'}
encoded = jwt.encode(signer, test_payload)
header, payload, _, _ = jwt._unverified_decode(encoded)
assert payload == test_payload
assert header == {'typ': 'JWT', 'alg': 'RS256', 'kid': signer.key_id}
      
      



, , , , .





 

, Mock- . mock.create_autospec()



(https://docs.python.org/3/library/unittest.mock.html#unittest.mock.create_autospec) mock.patch(autospec=True)



(https://docs.python.org/3/library/unittest.mock.html#autospeccing), . , , , . , , , , , !





, , , :





signer = mock.Mock()

encoded = jwt.encode(signer, test_payload)
...
signer.sign.assert_called_once_with(expected_call)
      
      



, , . , , :





signer = mock.Mock(spec=['sign', 'key_id'])

encoded = jwt.encode(signer, test_payload)
...
signer.sign.assert_called_once_with(expected_call)
      
      



โ€“ mock.create_autospec()



mock.patch(..., autospec=True)



. , . , , :





signer = mock.create_autospec(crypt.Signer, instance=True)

encoded = jwt.encode(signer, test_payload)
...
signer.sign.assert_called_once_with(expected_call)
      
      



autospec, , , . , .





, , , , , , , (stub). , -, , , (, in-memory ).





, :





class CredentialsStub(google.auth.credentials.Credentials):
    def __init__(self, token='token'):
        super(CredentialsStub, self).__init__()
        self.token = token

    def apply(self, headers, token=None):
        headers['authorization'] = self.token

    def before_request(self, request, method, url, headers):
        self.apply(headers)

    def refresh(self, request):
        self.token += '1'
      
      



Memcache:





class MemcacheFake(object):
    def __init__(self):
        self._data = {}

    def set(self, key, value):
        self._data[key] = value

    def get(self, key):
        return self._data.get(key)
      
      



, . , pylint, , , .





ยซยป

, , , ยซยป. โ€“ , . Mock , wraps



:





credentials = mock.Mock(wraps=CredentialsStub())
...
assert credentials.refresh.called
      
      



//

, . mock_x, x_mock, mocked_x, fake_x ., x. , , , . , :





mock_signer = mock.create_autospec(crypt.Signer, instance=True)
      
      



signer:





signer = mock.create_autospec(crypt.Signer, instance=True)
      
      



patch , :





@mock.patch('google.auth._helpers.utcnow')
def test_refresh_success(mock_utcnow):
    mock_utcnow.return_value = datetime.datetime.min
    ...
      
      



utcnow



:





@mock.patch('google.auth._helpers.utcnow')
def test_refresh_success(utcnow):
    utcnow.return_value = datetime.datetime.min
    ...
      
      



patch



, x_patch







utcnow_patch = mock.patch('google.auth._helpers.utcnow')
with utcnow_patch as utcnow:
    utcnow.return_value = datetime.datetime.min
    ...
      
      



, utcnow_patch



utcnow



. , , , .





, patch



, unused_x



:





@mock.patch('google.auth._helpers.utcnow')
def test_refresh_success(unused_utcnow):
    ...
      
      



helper-

. , helper- . , http-, :





def make_http(data, status=http_client.OK, headers=None):
    response = mock.create_autospec(transport.Response, instance=True)
    response.status = status
    response.data = _helpers.to_bytes(data)
    response.headers = headers or {}

    http = mock.create_autospec(transport.Request)
    http.return_value = response

    return request
      
      



:





def test_refresh_success():
    http = make_http('OK')
    assert refresh(http)

def test_refresh_failure():
    http = make_http('Not Found', status=http_client.NOT_FOUND)
    with pytest.raises(exceptions.TransportError):
        refresh(http)
      
      



pytest (https://docs.pytest.org/en/latest/fixture.html) โ€“ . , helper- , helper



. , , . , , - :





@pytest.fixture()
def server():
    server = WSGIServer(application=TEST_APP)
    server.start()
    yield server
    server.stop()
      
      



, . , , :





@pytest.fixture()
def database():
    db = database.Client()
    yield db
    db.delete(db.list_all())
      
      



โ€“ , , . , . , , urllib3



transport



:





@pytest.fixture(params=['urllib3', 'requests'])
def http_request(request):
    if request.param == 'urllib3':
        yield google.auth.transport.urllib3.Request(URLLIB3_HTTP)
    elif request.param == 'requests':
        yield google.auth.transport.requests.Request(REQUESTS_SESSION)
      
      




2021 , Django . , ยซยป Django Docker, , , .   .





"Python Developer. Professional"








All Articles