Pytest-Variables: how to use them without funcarg? - unit-testing

I wanna pass a json file for testbed definition to pytest.
My testcases are implemented inside a unittest class and need to use the json file send via pytest cli.
I tried to use pytest-variable to pass a json to pytest. Then I want to use the json as a dictionary inside my tests.
To be clearer, my test is launched with this command
pytest -s --variables ../testbeds/testbed_SQA_252.json TC_1418.py
I know unittest cannot accept external arguments but will be very useful a technique to unlock this constraint.
CASE 1 -- test implemented as functions --->OK
def test_variables(variables):
print(variables)
in this case the ouput is correct and the json is printed in the CLI
CASE 2-- test implemented as Unittest Class--->KO
class TC_1418(unittest.TestCase):
def setUp(self, variables):
print (variables)
....other functions
I obtain the following error:
TypeError: setUp() missing 1 required positional argument: 'variables'
Any Idea?

Your issue comes from mixing up concepts of pytest (e.g. injection of fixtures like variables) with concepts of unittest.TestCase. While pytest supports running tests based on unittest, I'm afraid that injection of plugins' fixtures into test methods is not supported.
There is a workaround though that takes advantage of fixtures being injected into other fixtures and making custom fixtures available in unittest.TestCase with #pytest.mark.usefixtures decorator:
# TC_1418.py
import pytest
import unittest
#pytest.fixture(scope="class")
def variables_injector(request, variables):
request.cls.variables = variables
#pytest.mark.usefixtures("variables_injector")
class Test1418(unittest.TestCase):
def test_something(self):
print(self.variables)
Notice that the name of the class starts with Test so as to follow conventions for test discovery.
If you don't want to go into this travesty, I propose you rather fully embrace Pytest and make your life easier with simple test functions that you have already discovered or properly structured test classes:
# TC_1418.py
class Test1418:
def test_something(self, variables):
print(variables)

Related

Django DRF APITestCase chain test cases

For example I want to write several tests cases like this
class Test(APITestCase):
def setUp(self):
....some payloads
def test_create_user(self):
....create the object using payload from setUp
def test_update_user(self):
....update the object created in above test case
In the example above, the test_update_user failed because let's say cannot find the user object. Therefore, for that test case to work, I have to create the user instead test_update_user again.
One possible solution, I found is to run create user in setUp. However, I would like to know if there is a way to chain test cases to run one after another without deleting the object created from previous test case.
Rest framework tests include helper classes that extend Django's existing test framework and improve support for making API requests.
Therefore all tests for DRF calls are executed with Django's built in test framework.
An important principle of unit-testing is that each test should be independent of all others. If in your case the code in test_create_user must come before test_update_user, then you could combine both into one test:
def test_create_and_update_user(self):
....create and update user
Tests in Django are executed in a parallell manner to minimize the time it takes to run all tests.
As you said above if you want to share code between tests one has to set it up in the setUp method
def setUp(self):
pass

pytest.mark.parametrize with django.test.SimpleTestCase

I am using pytest 3.2.2 and Django 1.11.5 on Python 3.6.2 on Windows.
The following code
import django.test
import pytest
class ParametrizeTest:
#pytest.mark.parametrize("param", ["a", "b"])
def test_pytest(self, param):
print(param)
assert False
works as expected:
scratch_test.py::ParametrizeTest::test_pytest[a] FAILED
scratch_test.py::ParametrizeTest::test_pytest[b] FAILED
But as soon as I change it to use Django's SimpleTestCase,
like this:
class ParametrizeTest(django.test.SimpleTestCase):
...
it fails with
TypeError: test_pytest() missing 1 required positional argument: 'param'
Can anybody explain why? And what to do against it?
(I actually even need to use django.test.TestCase and access the database.)
I have the following pytest plugins installed:
plugins: random-0.2, mock-1.6.2, django-3.1.2, cov-2.5.1
but turning any one of them (or all of them) off via -p no:random etc. does not help.
The Django test class is a unittest.TestCase subclass.
Parametrization is unsupported and this is documented under the section pytest features in unittest.TestCase subclasses:
The following pytest features do not work, and probably never will due to different design philosophies:
Fixtures (except for autouse fixtures)
Parametrization
Custom hooks
If you need parametrized tests and pytest runner, your best bet is to abandon the unittest style - this means move the setup/teardown into fixtures (pytest-django plugin has already implemented the hard parts for you), and use module level functions for your tests.
Use #pytest.mark.django_db
Thanks, wim, for that helpful answer. RTFM, once again.
For clarity, here is the formulation that will work (equivalent to a test inheriting from TestCase, not just SimpleTestCase).
Make sure you have pytest-django installed and then do:
import pytest
#pytest.mark.django_db
class ParametrizeTest:
#pytest.mark.parametrize("param", ["a", "b"])
def test_pytest(self, param):
print(param)
assert False
(BTW: Funnily, one reason why I originally decided to use pytest was that
the idea of using plain test functions instead of test methods appealed to me;
I like lightweight approaches.
But now I almost exclusively use test classes and methods anyway,
because I prefer the explicit grouping of tests they provide.)

Testing backend code in Django

I am writing an authentication back-end in Django to log only a few users.
It is in a folder called restrictedauthentification/ which is at the root of my Django Project. (I am written it down for a specific project.)
It has two files in it : backend.py and tests.py
In the last file, I have written down some tests for it.
But I can't run them with command ./manage.py test because it isn't an installed app.
Any ideas how I could run them ?
Okay, I found a solution that keep me from turning my backend into a module.
Somthing that I didn't understand and that could help some beginners : In python, a test cannot run itself. It need to be executed by a TestRunner.
Now, one could use the TextTestRunner bundled python that execute the tests and show the results on the standard output, but when testing with django, one need to do one thing before and after the test: calling the function setup_test_environment() and teardown_test_environment().
So I just created a class that inherit from TextTestRunner and redefine its methode run() in order that it execute the two functions provided by Django.
Here it is :
from restrictedauthentification.tests import TestRestrictedAuthentification
from django.test.utils import setup_test_environment, teardown_test_environment
from unittest import TextTestRunner
class DeadSimpleDjangoTestRunner(TextTestRunner):
def run(self, test):
setup_test_environment()
super().run(test)
teardown_test_environment()

Custom test suite for django app

I have a pretty complex django app which has following structure.
/myapp
/myapp/obj1/..
/myapp/obj1/views.py
/myapp/obj1/forms.py
/myapp/obj2/..
/myapp/obj2/views.py
/myapp/obj2/forms.py
/myapp/tests/..
/myapp/tests/__init__.py
/myapp/tests/test_obj1.py
/myapp/tests/test_obj2.py
I have a lot more objects. In /myapp/tests/__init__.py I import TestCase instances from test_obj1.py and test_obj2.py and it is enough to run all available test.
What I'm trying to do is to create a custom test suite. According to the documentation:
There is a second way to define the test suite for a module: if you
define a function called suite() in either models.py or tests.py, the
Django test runner will use that function to construct the test suite
for that module. This follows the suggested organization for unit
tests. See the Python documentation for more details on how to
construct a complex test suite.
So, i've created this function like this:
def suite():
suite = unittest.TestSuite()
suite.addTest(TestObj1Form())
suite.addTest(TestObj2Form())
return suite
However, when I run tests I get this error: ValueError: no such test method in <class 'myproject.myapp.tests.test_obj1.TestObj1Form'>: runTest. Of course I can define this method, but then if I run test it will invoke only this method and ignore all of the test* methods.
Any suggestions how to create a custom test suite for django app properly? I've googled and I found nothing about that.
You should add all your tests with a special function:
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestObj1Form))

How do I skip a section of code when unittesting in Django?

In my Django application, I have a section of code that uploads a file to Amazon S3, and I would like to skip this section during unittests. Unittests happen to run with DEBUG=False, so I can't test for settings.DEBUG == True to skip this section. Any ideas?
You really don't want to "skip" code in your unit tests -- if you do, you'll never have coverage for those areas. It's far better to provide a mock interface to external systems, so you can insure that the rest of the code behaves as expected. This is especially critical when dealing with external resources that may be unavailable, as S3 can be in case of network issues, service interruptions, or configuration errors.
Alternately, you could just use the Django S3 storage backend in your production environment, while configuring tests for use local file storage instead.
You could -- and yes, this is a hack -- import the module that does the uploading, and replace the upload function in that module with another function, that does nothing. Something like this:
foo.py:
def bar():
return 42
biz.py:
import foo
print foo.bar() # prints 42
foo.bar = lambda: 37
print foo.bar() # prints 37
Again, it's a hack, but if this is the only place where you're going to need such functionality it might work for you.
You don't skip a function for testing.
You provide a mock implementation for something that you don't want to run as if it were production.
First, you design for testing by making the S3 Uploader a separate class that has exactly the API your application needs.
Then you write a mock version of this class with the same API. All it does is record that it was called.
Finally, you make sure your unit test plugs in your mock object instead of the real S3 Uploader.
Your Django application should not have any changes made -- except the change "injected" into it by the unit test.
Your views.py that does the upload
import the_uploader
import mock_uploader
from django.conf import settings
uploadClass = eval( settings.S3_UPLOAD_CLASS_NAME )
uploader= uploadClass( ... )
Now, you provide two settings.py files. The default settings.py has the proper uploader class name.
For testing, you have a test_settings.py which looks like this.
import settings.py
S3_UPLOAD_CLASS_NAME = "mock_uploader.mock_upload_class"
This allows you to actually test everything.