I want to test a class method with py.test and I've followed this post Testing class methods with pytest.
I've got a file with my method (calgo.py) and another file with my test (test_calgo.py), and both files are in the same folder (learning_ut).
When I run pytest I get an error message saying fixture 'self' not found.
Where am I going wrong?
Thanks!
# calgo.py
class MyClass():
def func(self, x):
return x+1
# test_calgo.py
import calgo
def test_func(self):
mc = MyClass()
assert mc.func(3) == 4
# Command line
(pyenv) C:\Users\Jimmy\Desktop\learning_ut>pytest
# Error message
(pyenv) C:\Users\Jimmy\Desktop\learning_ut>pytest
================================================= test session starts =================================================
platform win32 -- Python 3.6.13, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: C:\Users\Jimmy\Desktop\learning_ut
collected 1 item
test_calgo.py E [100%]
======================================================= ERRORS ========================================================
_____________________________________________ ERROR at setup of test_func _____________________________________________
file C:\Users\Jimmy\Desktop\learning_ut\test_calgo.py, line 11
def test_func(self):
E fixture 'self' not found
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary,
doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property,
record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
> use 'pytest --fixtures [testpath]' for help on them.
C:\Users\Jimmy\Desktop\learning_ut\test_calgo.py:11
=============================================== short test summary info ===============================================
ERROR test_calgo.py::test_func
I don't know about pytest, in unittest you create a test class, which would have method belonging to this class and have self as parameter. However, your error seems to be causes by the function in calgo.py, which does not belong to a class, thus does not have the self reference. Try the following:
import calgo
def test_func():
mc = MyClass()
assert mc.func(3) == 4
Related
The answer to how to override a fixture with parametrization is already given here:
pytest test parameterization override
My question is what is the correct way to run the same test, both with the original fixture and the overridden values.
I'm already using the pytest-lazy-fixture library to override one fixture with another, but when I run the following pytest:
import pytest
from pytest_lazyfixture import lazy_fixture
#pytest.fixture
def fixture_value():
return 0
def test_foo(fixture_value):
...
#pytest.mark.parametrize('fixture_value', (lazy_fixture('fixture_value'), 1, 2, 3))
def test_bar(fixture_value):
...
I get this error:
E recursive dependency involving fixture 'fixture_value' detected
The error is because fixture_value name of the fixture is same as the paramter passed to the test.
import pytest
from pytest_lazyfixture import lazy_fixture
#pytest.fixture
def fixture_value():
return 0
def test_foo(fixture_value):
assert True
#pytest.mark.parametrize('fixtures_value'[(pytest.lazy_fixture('fixture_value'))]) # Square brackets used here.
def test_bar(fixtures_value): # Note the name here is changed.
assert True
Output:
Platform win32 -- Python 3.9.2, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
plugins: lazy-fixture-0.6.3
collected 2 items
myso_test.py::test_foo PASSED
myso_test.py::test_bar[fixtures_value0] PASSED
I am writing tests in pytest and am using fixtures as variables.
Originally, this is how the fixtures looked:
#pytest.fixture(scope="class")
def user(request):
u = "Matt"
request.cls.u = u
return u
And then, there was another fixture to delete the user from the database once I finished with it.
In the tests, I used both fixtures like so #pytest.mark.usefixtures("user", "teardown fixture")
The teardown fixture was class scoped, until I decided to change it to session scoped, since I want to delete the user only after running all the tests.
The problem was that suddenly the teardown fixture couldn't access user, since user is class scoped.
I changed the user to session scoped, however, I am not sure how to access, or export it now.
#pytest.fixture(scope="session")
def user(request):
u = "Matt"
# request.cls.user = u -> WHAT GOES HERE INSTEAD OF THIS?
return u
User is no longer recognized in the test functions. The test is located inside of a class. The current function is something like this:
Class TestUser(OtherClassWhichInheritsFromBaseCase):
def test_user1(self, user1):
self.open("www.google.com")
print(user1)
When I try to run the code in pycharm I get the following error:
def _callTestMethod(self, method):
> method()
E TypeError: TestUser.test_user1() missing 1 required positional argument: 'user1'
Any advice?
I think you're approaching this from the wrong direction. If you need to clean up a fixture, you don't write a second fixture; you write your fixture as a context manager.
For example, you might write:
#pytest.fixture(scope="session")
def user():
u = User(name="Matt")
yield u
# cleanup goes here
And in your test code:
def test_something(user):
assert user.name == "Matt"
Here's a complete example. We start with this dummy user.py, which simply creates files to demonstrate which methods were called:
from dataclasses import dataclass
#dataclass
class User:
name: str
def commit(self):
open("commit_was_called", "w")
def delete(self):
open("delete_was_called", "w")
Then here's our test:
import pytest
import user
#pytest.fixture(scope="session")
def a_user():
u = user.User(name="testuser")
u.commit()
yield u
u.delete()
class TestUserStuff:
def test_user(self, a_user):
assert a_user.name == "testuser"
We run it like this:
$ pytest
=============================================================================================== test session starts ===============================================================================================
platform linux -- Python 3.10.1, pytest-6.2.4, py-1.11.0, pluggy-0.13.1
rootdir: /home/lars/tmp/python
plugins: testinfra-6.5.0
collected 1 item
test_user.py . [100%]
================================================================================================ 1 passed in 0.00s ================================================================================================
After which we can confirm that both the commit and delete methods were called:
$ ls
commit_was_called
delete_was_called
test_user.py
user.py
I'm trying to use pytest to test if my function is logging the expected text, such as addressed this question (the pyunit equivalent would be assertLogs). Following the pytest logging documentation, I am passing the caplog fixture to the tester. The documentation states:
Lastly all the logs sent to the logger during the test run are made available on the fixture in the form of both the logging.LogRecord instances and the final log text.
The module I'm testing is:
import logging
logger = logging.getLogger(__name__)
def foo():
logger.info("Quinoa")
The tester is:
def test_foo(caplog):
from mwe16 import foo
foo()
assert "Quinoa" in caplog.text
I would expect this test to pass. However, running the test with pytest test_mwe16.py shows a test failure due to caplog.text being empty:
============================= test session starts ==============================
platform linux -- Python 3.7.3, pytest-5.3.0, py-1.8.0, pluggy-0.13.0
rootdir: /tmp
plugins: mock-1.12.1, cov-2.8.1
collected 1 item
test_mwe16.py F [100%]
=================================== FAILURES ===================================
___________________________________ test_foo ___________________________________
caplog = <_pytest.logging.LogCaptureFixture object at 0x7fa86853e8d0>
def test_foo(caplog):
from mwe16 import foo
foo()
> assert "Quinoa" in caplog.text
E AssertionError: assert 'Quinoa' in ''
E + where '' = <_pytest.logging.LogCaptureFixture object at 0x7fa86853e8d0>.text
test_mwe16.py:4: AssertionError
============================== 1 failed in 0.06s ===============================
Why is caplog.text empty despite foo() sending text to a logger? How do I use pytest such that caplog.text does capture the logged text, or otherwise verify that the text is being logged?
The documentation is unclear here. From trial and error, and notwithstanding the "all the logs sent to the logger during the test run are made available" text, it still only captures logs with certain log levels. To actually capture all logs, one needs to set the log level for captured log messages using caplog.set_level or the caplog.at_level context manager, so that the test module becomes:
import logging
def test_foo(caplog):
from mwe16 import foo
with caplog.at_level(logging.DEBUG):
foo()
assert "Quinoa" in caplog.text
Now, the test passes:
============================= test session starts ==============================
platform linux -- Python 3.7.3, pytest-5.3.0, py-1.8.0, pluggy-0.13.0
rootdir: /tmp
plugins: mock-1.12.1, cov-2.8.1
collected 1 item
test_mwe16.py . [100%]
============================== 1 passed in 0.04s ===============================
In the logger set up, please set logger.propagate=True
This is a limitation of Pytest, cf. the feature request. I've resorted to creating a fixture based on _pytest.logging.LogCaptureFixture with a context manager and using it instead of caplog.
Some code:
from _pytest.logging import LogCaptureHandler, _remove_ansi_escape_sequences
class CatchLogFixture:
"""Fixture to capture logs regardless of the Propagate flag. See
https://github.com/pytest-dev/pytest/issues/3697 for details.
"""
#property
def text(self) -> str:
return _remove_ansi_escape_sequences(self.handler.stream.getvalue())
#contextmanager
def catch_logs(self, level: int, logger: logging.Logger) -> LogCaptureHandler:
"""Set the level for capturing of logs. After the end of the 'with' statement,
the level is restored to its original value.
"""
self.handler = LogCaptureHandler()
orig_level = logger.level
logger.setLevel(level)
logger.addHandler(self.handler)
try:
yield self
finally:
logger.setLevel(orig_level)
logger.removeHandler(self.handler)
#pytest.fixture
def capture_log():
return CatchLogFixture().catch_logs
I am getting following error while running pytest using following code im unable to figure out whats wrong please find below code snippets.
Console ouput :
================================================= test session starts =================================================
platform win32 -- Python 3.7.2, pytest-4.2.0, py-1.7.0, pluggy-0.8.1
rootdir: D:\Workspace\AutomationProject, inifile:
plugins: cov-2.6.1, allure-pytest-2.5.5
collected 1 item
tests\pages\test.py E [100%]
======================================================= ERRORS ========================================================
__________________________________________ ERROR at setup of test.test_test ___________________________________________
file D:\Workspace\AutomationProject\tests\pages\test.py, line 5
def test_test(self):
E fixture 'web_driver' not found
> available fixtures: _UnitTestCase__pytest_class_setup, cache, capfd, capfdbinary, caplog, capsys, capsysbinary, cov, doctest_namespace, monkeypatch, no_cover, pytestconfig, record_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
> use 'pytest --fixtures [testpath]' for help on them.
D:\Workspace\AutomationProject\tests\pages\test.py:5
=============================================== 1 error in 0.12 seconds ===============================================
My Base class contains following code:
from selenium import webdriver
import pytest
import unittest
#pytest.fixture(scope="class")
def web_driver(request):
driver = webdriver.Chrome("C:/chromedriver.exe")
request.cls.driver = driver
yield
web_driver.close()
#pytest.mark.usefixtures("web_driver")
class Base(unittest.TestCase):
'''
This fixture contains the set up and tear down code for each test.
'''
pass
and test class contains following code:
from core.web.Base import Base
class test(Base):
def test_test(self):
self.driver.get("http://google.com")
The test fixture is web_driver still getting error not found !
web_driver() is defined outside Base class scope, so it's invisible to the usefixtures as it is part of test class scope. You could move it to conftest file, but IMHO a better solution is to move web_driver inside Base
#pytest.mark.usefixtures("web_driver")
class Base(unittest.TestCase):
#pytest.fixture(scope="class")
def web_driver(self, request):
driver = webdriver.Chrome("C:/chromedriver.exe")
request.cls.driver = driver
yield
driver.close()
As a side note, it should be driver.close(), not web_driver.close()
To reduce boiler plate code i came upon the idea to generate test cases in the class Tester for all parameter-less methods.
On running py.test, it only recognizes the statically written test cases (test_a, test_b) but not the dynamically created test cases using setattr(Tester,'test_' + name, member)
Perhaps py.test has already inspected the class Tester for methods with 'test_*' before setUpClass is called? Any hints how to get this running?
import inspect
import unittest
class Testee:
def a(self):
print('a')
def b(self):
print('b')
#...
#...
def z(self):
print('z')
class Tester(unittest.TestCase):
#classmethod
def setUpClass(cls):
testee = Testee()
for name, member in inspect.getmembers(object=testee, predicate=inspect.ismethod or inspect.iscoroutine):
if len(inspect.signature(member).parameters):
print(str(inspect.signature(member).parameters))
setattr(Tester,'test_' + name, member)
if inspect.isfunction(member) or inspect.ismethod(member):
setattr(Tester,'test_' + name, member)
elif inspect.iscoroutinefunction(member):
setattr(Tester,'test_' + name, functools.partialmethod(TestInstrument.run_coro, member))
else:
print(member)
return super().setUpClass()
def test_a(self):
Tester.testee.a()
def test_b(self):
Tester.testee.b()
============================= test session starts ============================= platform win32 -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- c:\program
files\python35\python.exe cachedir: .cache rootdir: C:\tests, inifile:
collected 2 items
sandbox.py::Tester::test_a PASSED sandbox.py::Tester::test_b PASSED
========================== 2 passed in 0.03 seconds ===========================
EDIT: If i move the code in setupClass to the global scope (outside the class), then py.test detects and runs the auto-generated test cases.
To elaborate on my 'EDIT', one option would be so. I'm unhappy with the solution because global code execution would be open to side-effects and other subtle bugs. any suggestions on how to get this into class Tester scope?
import inspect
import unittest
import functools
def auto_generate_test_cases(Tester, Testee):
def run(self, fn):
fn(Tester._testee)
for name, member in inspect.getmembers(
object=Testee, predicate=inspect.isfunction or inspect.iscoroutine):
if len(inspect.signature(member).parameters) == 1:
setattr(Tester,'test_' + name, functools.partialmethod(run, member))
class Testee:
def __init__(self):
self._a = 'a'
def a(self):
print(self._a)
def ab(self, a, b):
print('a')
def b(self):
print('b')
def h(self):
print('h')
async def q(self):
print('async q')
#...
#...
def z(self):
print('z')
class Tester(unittest.TestCase):
_testee = Testee()
auto_generate_test_cases(Tester, Testee)
py.test. output:
C:\tests>py.test sandbox.py --verbose
============================= test session starts =============================
platform win32 -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- c:\program files\python35\python.exe
cachedir: .cache
rootdir: C:\\tests, inifile:
collected 5 items
sandbox.py::Tester::test_a PASSED
sandbox.py::Tester::test_b PASSED
sandbox.py::Tester::test_h PASSED
sandbox.py::Tester::test_q PASSED
sandbox.py::Tester::test_z PASSED
========================== 5 passed in 0.07 seconds ===========================