py version:Python 3.6.3
Context:
I am using HtmlTestRunner with unittest to get tidy post-deployment test reports for a REST API.
Issue: When I run the tests, I do get everything separated, but the subtests inherit their name 1:1 from the test.
Example view in html:
Example view in html with errors
Example code :
import unittest
import HtmlTestRunner
class testBadRequest(unittest.TestCase):
def test_serviceOne_bad_request(self):
with self.subTest():
x = endpointOne.getAll(acfg_url, cfg.badHeaders, acfg_host, acfg_params)
self.assertEqual(x["status_code"], 400)
with self.subTest():
x = endpointOne.getByID(cfg.badID, acfg_url, acfg_headers, acfg_host, acfg_params)
self.assertEqual(x["status_code"], 400)
if __name__ == '__main__':
unittest.main(testRunner=HtmlTestRunner.HTMLTestRunner(combine_reports=True))
(answering my own question)
Yes, you add the msg parameter when creating a subTest, so:
class testBadRequest(unittest.TestCase):
def test_serviceOne_bad_request(self):
with self.subTest(msg="getAll"):
Related
I am somewhat of a beginner in python, i am currently writing a suite of test cases with selenium webdriver using unittest; i have also found a lot of useful answers here, but it's time a ask my first question, i have struggled a lot with this and cannot find a proper answer, so any help is greatly appreciated:
For short, i have a suite of multiple tests cases, and in each case the first step is always ".get('Some URL')"; i have written these test cases for a single environment, but i would like to be able to select the URL on which all tests will be executed. In the example below i called the "access_url" method with a specific environment, but i need to do this for all of my scenarios at once, is it possible to do this from where i execute the .py file (e.g. "python example.py")? or to pass it in the .run() method when i select what suite to run?
import HTMLTestRunner
from selenium import webdriver
import unittest
This is a custom class used to create the 'access_url' method
def MyClass(object):
def __init__(self, driver):
self.driver = driver
def access_url(self, URL):
if URL == 'environment 1':
self.driver.get('https://www.google.com/')
elif URL == 'environment 2':
self.driver.get('https://example.com/')
In the classes i use to write test cases the first step is always 'access URL'
class TestScenario01(unittest.TestCase):
def setUp(self):
[...]
def test_01_access(self):
MyClass(self.driver).access_url(URL='environment 2')
def test_02(self):
[...]
def test_03(self):
[...]
In order to run the tests i place them all in a suite and use .run() on them
tc_scenario01 = unittest.TestLoader().loadTestsFromTestCase(TestScenario01)
test_suite = unittest.TestSuite([tc_scenario01])
HTMLReporterCustom.HTMLTestRunner().run(test_suite)
Finally, in order to execute the script i type the follwoing line in CMD: 'python example_file.py
As i mentioned above, all i want to do is to be able to somehow pass the URL one time to all test cases that call the "access_url()" method. Thanks!
You can maintain environment properties in separate config file.
config.py
DEFAULT_ENVIRONMENT='environment1'
URL = {
'environment1': 'https://www.google.com/',
'environment2': 'https://example.com/'
}
Your Class,
from package import config
def MyClass(object):
def __init__(self, driver):
self.driver = driver
def access_url(self):
self.driver.get(config.URL[config.DEFAULT_ENVIRONMENT])
Then test class will be as expected,
class TestScenario01(unittest.TestCase):
def setUp(self):
[...]
def test_01_access(self):
MyClass(self.driver).access_url()
def test_02(self):
[...]
def test_03(self):
[...]
While running test you can change,
main.py
from package import config
config.DEFAULT_ENVIRONMENT = 'enviroment2'
tc_scenario01 = unittest.TestLoader().loadTestsFromTestCase(TestScenario01)
test_suite = unittest.TestSuite([tc_scenario01])
HTMLReporterCustom.HTMLTestRunner().run(test_suite)
You can also pass the environment name while running python main.py.
main.py
if __name__ == '__main__':
config.DEFAULT_ENVIRONMENT = sys.argv[1] if len(sys.argv) > 2 else 'dev'
tc_scenario01 = unittest.TestLoader().loadTestsFromTestCase(TestScenario01)
test_suite = unittest.TestSuite([tc_scenario01])
HTMLReporterCustom.HTMLTestRunner().run(test_suite)
Below is displayed my test class . This is good test when i call it once - python3 main.py.
There is a problem if I want this test run for example 100 times. How to do that?
When I try call by pytest there are warnings sth like this:
main.py::TestLoginPage::test_login_invalid_credentials
/home/-----/.local/lib/python3.5/site-packages/pytest_repeat.py:31: UserWarning: Repeating unittest class tests not supported
"Repeating unittest class tests not supported")
main.py::TestLoginPage::test_login_valid_credentials
/home/-----/.local/lib/python3.5/site-packages/pytest_repeat.py:31: UserWarning: Repeating unittest class tests not supported
"Repeating unittest class tests not supported")
This is my test class - test_login_page.py
import unittest
from selenium import webdriver
from tests.test_driver import TestDriver
from pages.login_page import LoginPage
class TestLoginPage(unittest.TestCase):
#classmethod
def setUpClass(cls):
cls.page_url = LoginPage.login_url
cls.webdriver = webdriver
TestDriver.setUp(cls, cls.page_url)
cls.page = LoginPage(cls.webdriver)
cls.option = 1
def __init_test_method(self):
# [TS001].Check if page is loaded correctly
self.assertTrue(self.page.check_page_loaded_correctly)
# [TS002].Check button contains 'login' text
self.assertEqual(self.page.get_button_text(), 'Login')
# TC001
def test_login_valid_credentials(self):
"""Test login with valid credentials"""
self.__init_test_method()
# [TS003].Clear form, fill with correct data and submit
self.assertEqual(self.page.login_process(self.option), 'Complexes')
# TC002 (invalid password) & TC003 (invalid username)
def test_login_invalid_credentials(self):
"""Test login with invalid credentials"""
for option in range(2, 4):
self.__init_test_method()
self.assertTrue(self.page.login_process(option))
#classmethod
def tearDownClass(cls):
cls.webdriver.quit()
This is main which I run all tests from console - main.py
import unittest
import os
import sys
cwd = os.getcwd()
sys.path.append(cwd)
from tests.test_login_page import TestLoginPage
if __name__ == '__main__':
unittest.main()
If you want to run your tests over and over, I would suggest you use ddt or data driven tests. You can find the documentation for complete usage here.
You will decorate your test class with ddt and your test method with file_data or data to pass in unique test parameters.
This will allow you to feed new test parameters for each iteration of your test and will cycle through and run the same test method for however many times you need.
Here is an example of how I use it:
from ddt import ddt, file_data
#ddt
class MyTestClass(unittest.TestCase):
#file_data('test_parameter_file.json')
def some_test_method1(self, test_package):
Some test method
I have the different test parameters in the test_parameter_file.json file and my test method is run for each parameter in the file.
I have some python code like
from pylons.i18n.translation import _
def get_message():
message = _(u"Translated message")
# interesting code to test
# [...]
return 'result'
which I would like to unittest like this:
class MyTest(TestCase):
def test_get_message(self):
assertTrue(get_message(), 'result')
Now running this test in nosetests gives me:
TypeError: No object (name: translator) has been registered for this thread
Is there a way to deactivate anything regarding translations when unittesting?
Let's say your production code is in my_module.py:
from unittest import TestCase
from mock import patch
from my_module import get_message
class MyTest(TestCase):
def test_get_message(self):
with patch("my_module._"):
result = get_message()
self.assertEqual("result", result)
With the patch your test changes the _() function to a MagicMock() object. Documentation here.
NOTE: mock is part of the standard library from Python 3.3 and onwards. Otherwise you should install it first using pip install mock.
I have started using nose to run tests. I discovered the multiprocessing plugin has a timeout, that I can change on the command line.
Is there a way to extend the timeout for individual tests (in the test code) so I don't have a massive global timeout?
I haven't any experience with the multiprocessing plugin but if you subclass the plugin with something like this:
from nose.plugins.multiprocess import MultiProcess
PLUGIN = None
class TimeoutMultiProcess(MultiProcess):
def configure(self, options, conf):
global PLUGIN
PLUGIN = self
super(TimeoutMultiProcess, self).configure(options, conf)
if not self.enabled:
return
then you can create your own test running script like:
import unittest
class TestA(unittest.TestCase):
def setUp(self):
from runtest import PLUGIN
print PLUGIN.config.multiprocess_timeout
def test_a(self):
pass
def test_b(self):
pass
if __name__ == '__main__':
from runtest import TimeoutMultiProcess
import nose
nose.main(addplugins=[TimeoutMultiProcess()], defaultTest="./test.py")
You'll be able to change the config.multiprocess_timeout to different values within your tests. I'm not sure if it will work for you, but it's worth a shot.
I have two test cases (two different files) that I want to run together in a Test Suite. I can get the tests to run just by running python "normally" but when I select to run a python-unit test it says 0 tests run. Right now I'm just trying to get at least one test to run correectly.
import usertest
import configtest # first test
import unittest # second test
testSuite = unittest.TestSuite()
testResult = unittest.TestResult()
confTest = configtest.ConfigTestCase()
testSuite.addTest(configtest.suite())
test = testSuite.run(testResult)
print testResult.testsRun # prints 1 if run "normally"
Here's an example of my test case set up
class ConfigTestCase(unittest.TestCase):
def setUp(self):
##set up code
def runTest(self):
#runs test
def suite():
"""
Gather all the tests from this module in a test suite.
"""
test_suite = unittest.TestSuite()
test_suite.addTest(unittest.makeSuite(ConfigTestCase))
return test_suite
if __name__ == "__main__":
#So you can run tests from this module individually.
unittest.main()
What do I have to do to get this work correctly?
you want to use a testsuit. So you need not call unittest.main().
Use of testsuit should be like this:
#import usertest
#import configtest # first test
import unittest # second test
class ConfigTestCase(unittest.TestCase):
def setUp(self):
print 'stp'
##set up code
def runTest(self):
#runs test
print 'stp'
def suite():
"""
Gather all the tests from this module in a test suite.
"""
test_suite = unittest.TestSuite()
test_suite.addTest(unittest.makeSuite(ConfigTestCase))
return test_suite
mySuit=suite()
runner=unittest.TextTestRunner()
runner.run(mySuit)
All of the code to create a loader and suite is unnecessary. You should write your tests so that they are runnable via test discovery using your favorite test runner. That just means naming your methods in a standard way, putting them in an importable place (or passing a folder containing them to the runner), and inheriting from unittest.TestCase. After you've done that, you can use python -m unittest discover at the simplest, or a nicer third party runner to discover and then run your tests.
If you are trying to manually collect TestCases, this is useful: unittest.loader.findTestCases():
# Given a module, M, with tests:
mySuite = unittest.loader.findTestCases(M)
runner = unittest.TextTestRunner()
runner.run(mySuit)
I am assuming you are referring to running python-unit test against the module that consolidates the two test. It will work if you create test case for that module ie. subclassing unittest.TestCase and having a simple test that starts with the word 'test'.
e.g.
class testall(unittest.TestCase):
def test_all(self):
testSuite = unittest.TestSuite()
testResult = unittest.TestResult()
confTest = configtest.ConfigTestCase()
testSuite.addTest(configtest.suite())
test = testSuite.run(testResult)
print testResult.testsRun # prints 1 if run "normally"
if __name__ == "__main__":
unittest.main()