Pytest Testrail Module - Post Test Results for Test Runs - python

I am trying to use the pytest testrail module and started with this demo script:
import pytest
from pytest_testrail.plugin import testrail
#testrail('C165')
def test_run():
print "T165:pass"
It does create a test run but does not post any results to the corresponding test cases.

Try adding an assertion as that is what the pytest hook is looking for:
import pytest
from pytest_testrail.plugin import testrail
#testrail('C165')
def test_run():
assert False

Here is add_result function. The testrail plugin executes it when your test (test_run) is finished.
You can notice the parameter status in the function, it requires your test need to return the result from assertion (ex. assert False is good example).
In your case, just print a string is not good to testrail know the status of the test.
def add_result(self, test_ids, status, comment='', duration=0):
"""
Add a new result to results dict to be submitted at the end.
:param list test_ids: list of test_ids.
:param int status: status code of test (pass or fail).
:param comment: None or a failure representation.
:param duration: Time it took to run just the test.
"""

Related

Mocking Azure BlobServiceClient in Python

I am trying to write a unit test that will test azure.storage.blob.BlobServiceClient class and its methods. Below is my code
A fixture in the conftest.py
#pytest.fixture
def mock_BlobServiceClient(mocker):
azure_ContainerClient = mocker.patch("azure.storage.blob.ContainerClient", mocker.MagicMock())
azure_BlobServiceClient= mocker.patch("azure_module.BlobServiceClient", mocker.MagicMock())
azure_BlobServiceClient.from_connection_string.return_value
azure_BlobServiceClient.get_container_client.return_value = azure_ContainerClient
azure_ContainerClient.list_blob_names.return_value = "test"
azure_ContainerClient.get_container_client.list_blobs.return_value = ["test"]
yield azure_BlobServiceClient
Contents of the test file
from azure_module import AzureBlob
def test_AzureBlob(mock_BlobServiceClient):
azure_blob = AzureBlob()
# This assertion passes
mock_BlobServiceClient.from_connection_string.assert_called_once_with("testconnectionstring")
# This assertion fails
mock_BlobServiceClient.get_container_client.assert_called()
Contents of the azure_module.py
from azure.storage.blob import BlobServiceClient
import os
class AzureBlob:
def __init__(self) -> None:
"""Initialize the azure blob"""
self.azure_blob_obj = BlobServiceClient.from_connection_string(os.environ["AZURE_STORAGE_CONNECTION_STRING"])
self.azure_container = self.azure_blob_obj.get_container_client(os.environ["AZURE_CONTAINER_NAME"])
My test fails when I execute it with below error message
> mock_BlobServiceClient.get_container_client.assert_called()
E AssertionError: Expected 'get_container_client' to have been called.
I am not sure why it says that the get_container_client wasn't called when it was called during the AzureBlob's initialization.
Any help is very much appreciated.
Update 1
I believe this is a bug in the unittest's MagicMock itself. Per
Michael Delgado suggested that I dialed the code to a bare minimum to test and identify the issue, and I concluded that the MagicMock was causing the problem. Below are my findings:
conftest.py
#pytest.fixture
def mock_Blob(mocker):
yield mocker.patch("module.BlobServiceClient")
test_azureblob.py
def test_AzureBlob(mock_Blob):
azure_blob = AzureBlob()
print(mock_Blob)
print(mock_Blob.mock_calls)
print(mock_Blob.from_connection_string.mock_calls)
print(mock_Blob.from_connection_string.get_container_client.mock_calls)
assert False # <- Intentional fail
After running the test, I got the following results.
$ pytest -vv
.
.
.
------------------------------------------------------------------------------------------- Captured stdout call -------------------------------------------------------------------------------------------
<MagicMock name='BlobServiceClient' id='140704187870944'>
[call.from_connection_string('AZURE_STORAGE_CONNECTION_STRING'),
call.from_connection_string().get_container_client('AZURE_CONTAINER_NAME')]
[call('AZURE_STORAGE_CONNECTION_STRING'),
call().get_container_client('AZURE_CONTAINER_NAME')]
[]
.
.
.
The prints clearly show that the get_container_client was seen being called, but the mocked method did not register it at its level. That led me to conclude that the MagicMock has a bug which I will report to the developers for further investigation.

Mocking a function returning an object results in AssertionError: Expected '...' to have been called once. Called 0 times

I'm writing a simple example to help me understand how mocking works in unittest. I have a module with two functions:
# model.animals.py
def get_animals(animal_type):
db = connect_to_db()
result = db.query_all_data()
return list(filter(lambda x: x['animal_type'] == animal_type, result))
def connect_to_db():
pass # That would normally return a DB connection instance
I want to test the get_animals() function which uses a DB connection to retrieve information about all animals and then filters returned data based on animal type. Since I don't want to set up the whole database, I just want to mock the connect_to_db() function which returns a DB connection instance.
This is my test class:
# test_mock.py
from unittest import TestCase, main
from unittest.mock import Mock, patch
from model.animals import get_animals
class GetDataTest(TestCase):
#patch('model.animals.connect_to_db')
def test_get_animals(self, mock_db: Mock):
mock_db.return_value.query_all_data.return_value = [
{
'animal_type': 'meerkat',
'age': 5
},
{
'animal_type': 'meerkat',
'age': 11
},
{
'animal_type': 'cow',
'age': 3
}
]
result = get_animals('meerkat') # Run the function under test
mock_db.assert_called_once() # OK
mock_db.query_all_data.assert_called_once() # AssertionError
self.assertEqual(len(result), 2) # OK
self.assertEqual(result[0]['age'], 5) # OK
if __name__ == "__main__":
main()
As part of the test I wanted to not only check the filtering of animals based on their type but also whether all the methods inside get_animals() are called.
The test generally works as expected but I get an error when checking whether the query_all_data() function has been called:
AssertionError: Expected 'query_all_data' to have been called once. Called 0 times.
When I add spec=True to my patch I get another error:
AttributeError: Mock object has no attribute 'query_all_data'
Clearly, the function query_all_data is not visible inside the mock even though I set its return value in the test with mock_db.return_value.query_all_data.return_value = ....
What am I missing?
The reason that mock_db.query_all_data.assert_called_once() failed is that it should be mock_db.return_value.query_all_data.assert_called_once().
I have created a helper library to help me generate asserts for mocks so that I won't stumble on such issues as often.
To use it do: pip install mock-generator
Then, in your test place these lines after result = get_animals('meerkat'):
from mock_autogen import generate_asserts
generate_asserts(mock_db)
When you run the test, it would generate the asserts for you (printed to the console and copied to the clipboard):
assert 1 == mock_db.call_count
mock_db.assert_called_once_with()
mock_db.return_value.query_all_data.assert_called_once_with()
You can then edit the generated asserts and use whichever fits your test.

How do I run a fixture only when the test fails?

I have the following example:
conftest.py:
#pytest.fixture:
def my_fixture_1(main_device)
yield
if FAILED:
-- code lines --
else:
pass
main.py:
def my_test(my_fixture_1):
main_device = ...
-- code lines --
assert 0
-- code lines --
assert 1
When assert 0, for example, the test should fail and execute my_fixture_1. If the test pass, the fixture must not execute. I tried using hookimpl but didn't found a solution, the fixture is always executing even if the test pass.
Note that main_device is the device connected where my test is running.
You could use request as an argument to your fixture. From that, you can check the status of the corresponding tests, i.e. if it has failed or not. In case it failed, you can execute the code you want to get executed on failure. In code that reads as
#pytest.fixture
def my_fixture_1(request):
yield
if request.session.testsfailed:
print("Only print if failed")
Of course, the fixture will always run but the branch will only be executed if the corresponding test failed.
In Simon Hawe's answer, request.session.testsfailed denotes the number of test failures in that particular test run.
Here is an alternative solution that I can think of.
import os
#pytest.fixture(scope="module")
def main_device():
return None
#pytest.fixture(scope='function', autouse=True)
def my_fixture_1(main_device):
yield
if os.environ["test_result"] == "failed":
print("+++++++++ Test Failed ++++++++")
elif os.environ["test_result"] == "passed":
print("+++++++++ Test Passed ++++++++")
elif os.environ["test_result"] == "skipped":
print("+++++++++ Test Skipped ++++++++")
def pytest_runtest_logreport(report):
if report.when == 'call':
os.environ["test_result"] = report.outcome
You can do your implementations directly in the pytest_runtest_logreport hook itself. But the drawback is that you won't get access to the fixtures other than the report.
So, if you need main_device, you have to go with a custom fixture like as shown above.
Use #pytest.fixture(scope='function', autouse=True) which will automatically run it for every test case. you don't have to give main_device in all test functions as an argument.

How can I provide a non-fixture pytest parameter via a custom decorator?

We have unit tests running via Pytest, which use a custom decorator to start up a context-managed mock echo server before each test, and provide its address to the test as an extra parameter. This works on Python 2.
However, if we try to run them on Python 3, then Pytest complains that it can't find a fixture matching the name of the extra parameter, and the tests fail.
Our tests look similar to this:
#with_mock_url('?status=404&content=test&content-type=csv')
def test_file_not_found(self, url):
res_id = self._test_resource(url)['id']
result = update_resource(None, res_id)
assert not result, result
self.assert_archival_error('Server reported status error: 404 Not Found', res_id)
With a decorator function like this:
from functools import wraps
def with_mock_url(url=''):
"""
Start a MockEchoTestServer and call the decorated function with the server's address prepended to ``url``.
"""
def decorator(func):
#wraps(func)
def decorated(*args, **kwargs):
with MockEchoTestServer().serve() as serveraddr:
return func(*(args + ('%s/%s' % (serveraddr, url),)), **kwargs)
return decorated
return decorator
On Python 2 this works; the mock server starts, the test gets a URL similar to "http://localhost:1234/?status=404&content=test&content-type=csv", and then the mock is shut down afterward.
On Python 3, however, we get an error, "fixture 'url' not found".
Is there perhaps a way to tell Python, "This parameter is supplied from elsewhere and doesn't need a fixture"? Or is there, perhaps, an easy way to turn this into a fixture?
You can use url as args parameter
#with_mock_url('?status=404&content=test&content-type=csv')
def test_file_not_found(self, *url):
url[0] # the test url
Looks like Pytest is content to ignore it if I add a default value for the injected parameter, to make it non-mandatory:
#with_mock_url('?status=404&content=test&content-type=csv')
def test_file_not_found(self, url=None):
The decorator can then inject the value as intended.
consider separating the address from the service of the url. Using marks and changing fixture behavior based on the presence of said marks is clear enough. Mock should not really involve any communication, but if you must start some service, then make it separate from
with_mock_url = pytest.mark.mock_url('http://www.darknet.go')
#pytest.fixture
def url(request):
marker = request.get_closest_marker('mock_url')
if marker:
earl = marker.args[0] if args else marker.kwargs['fake']
if earl:
return earl
try:
#
earl = request.param
except AttributeError:
earl = None
return earl
#fixture
def server(request):
marker = request.get_closest_marker('mock_url')
if marker:
# start fake_server
#with_mock_url
def test_resolve(url, server):
server.request(url)

Using pytest.fixture to opt out of function

I have a function that I don't want to run every time I run tests in my Flask-RESTFul API. This is an example of the setup:
class function(Resource):
def post(self):
print 'test'
do_action()
return {'success':True}
in my test I want to run this function, but ignore do_action(). How would I make this happen using pytest?
This seems like a good opportunity to mark the tests
#pytest.mark.foo_test
class function(Resource):
def post(self):
print 'test'
do_action()
return {'success':True}
Then if you call with
py.test -v -m foo_test
It will run only those tests marked "foo_test"
If you call with
py.test -v -m "not foo_test"
It will run all tests not marked "foo_test"
You can mock do_action in your test:
def test_post(resource, mocker):
m = mocker.patch.object(module_with_do_action, 'do_action')
resource.post()
assert m.call_count == 1
So the actual function will not be called in this test, with the added benefit
that you can check if the post implementation is actuall calling the function.
This requires pytest-mocker to
be installed (shameless plug).

Categories

Resources