I have been working on fastAPI and have some async methods to generate an auth token
Writting the unit testing I'm getting the following error:
TypeError: test_get_auth_token() missing 2 required positional arguments: 'test_input' and 'expected_result'
my unit test looks like:
class TestGenerateAuthToken(IsolatedAsyncioTestCase):
"""
"""
#pytest.mark.parametrize(
"test_input,expected_result",
[("user", "user_token"), ("admin", "admin_token")],
)
#mock.patch("myaauth.get_token", new_callable=AsyncMock)
async def test_get_auth_token(self, get_token_mock, test_input, expected_result):
"""
Test get_auth_header
"""
def mock_generate_user_token(_type):
return f"{_type}_token"
get_token_mock.side_effect = mock_generate_user_token
assert await myaauth.get_token(test_input) == expected_result
I know is as simple as to just remove the parametrize, but I wanna know if is possible to do so
It is not related to mock.
The reason is that pytest.mark.parametrize is not compatible with unittest.IsolatedAsyncioTestCase.
Instead, you could try using pytest's plugin, for example, pytest-asyncio, to let pytest work with the coroutine test function.
from unittest import mock
from unittest.mock import AsyncMock
import pytest as pytest
import myaauth
class TestGenerateAuthToken:
#pytest.mark.parametrize(
"test_input,expected_result",
[("user", "user_token"), ("admin", "admin_token")],
)
#pytest.mark.asyncio
#mock.patch("myaauth.get_token", new_callable=AsyncMock)
async def test_get_auth_token(self, get_token_mock, test_input, expected_result):
"""
Test get_auth_header
"""
def mock_generate_user_token(_type):
return f"{_type}_token"
get_token_mock.side_effect = mock_generate_user_token
assert await myaauth.get_token(test_input) == expected_result
-> % python -m pytest pytest_coroutine.py
=================================================================================================== test session starts ===================================================================================================
platform darwin -- Python 3.10.8, pytest-7.2.0, pluggy-1.0.0
rootdir: /Users/james/PycharmProjects/stackoverflow
plugins: asyncio-0.20.2
asyncio: mode=strict
collected 2 items
pytest_coroutine.py .. [100%]
==================================================================================================== 2 passed in 0.03s ====================================================================================================
I am trying to test a class where I monkeypatch a variable within the class with another function stored in conftest.py, but none of the test are detected even with the correct naming convention of prefixing test_ to the test name as well as the file name.
This is conftest.py in the root of tests directory.
import pytest
from pathlib import Path
import spacy
# Pass path to staticdata to any test
#pytest.fixture(scope="session")
def get_staticdata_path():
static_data_path = (
Path(
__file__,
)
.resolve()
.parent
/ ".."
/ ".."
/ "staticdata"
)
return static_data_path
# Pass function that loads a spacy model based on data in staticdata
# instead of downloading it everytime
#pytest.fixture(scope="session")
def load_spacy_model():
static_data_path = get_staticdata_path()
model_path = static_data_path / "en_core_web_sm"
nlp = spacy.load(model_path)
return nlp
This is the file that is not getting picked up for testing which is located at tests/natural_language_processing/test_utils.py
import pytest
from omdenalore.natural_language_processing.utils import TextUtils
#pytest.fixture(autouse=True)
#pytest.mark.parametrize(
"text,expected,exception",
[
# No inputs
(None, None, Exception),
(
"This is a sample sentence, showing off the stop words filtration.",
"This sample sentence, showing stop words filtration.",
None,
),
],
)
def test_stop_word_removal(text, expected, exception, monkeypatch):
monkeypatch.setattr(TextUtils.stop_word_removal, "nlp", load_spacy_model)
with pytest.raises(exception):
result = TextUtils.stop_word_removal(text)
assert result == expected
Output when I run pytest at the root dir:
============================================================= test session starts =============================================================
platform darwin -- Python 3.7.11, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: /Users/yravindranath/OmdenaLore
collected 32 items
tests/computer_vision/test_IoU.py ... [ 9%]
tests/computer_vision/test_MSE.py ... [ 18%]
tests/computer_vision/test_PSNR.py ... [ 28%]
tests/computer_vision/test_RMSE.py ... [ 37%]
tests/computer_vision/test_SSIM.py ... [ 46%]
tests/datasets/test_download_datasets.py ..... [ 62%]
tests/optical_character_recognition/test_pytesseract_ocr.py ............ [100%]
test_stop_word_removal is declared as a fixture (#pytest.fixture(autouse=True)). It seems to be the reason why it is not detected as test.
The decorator #pytest.fixture(autouse=True) declares a fixture that will be automatically requested by all the tests, see pytest fixtures.
Here is the way to declare the test method using the fixture load_spacy_model.
def test_stop_word_removal(text, expected, exception, monkeypatch, load_spacy_model):
monkeypatch.setattr(TextUtils.stop_word_removal, "nlp", load_spacy_model)
# ...
When using #pytest.mark.parametrize('arg', param) is there a way to find out if the last item in param is being run? The reason I'm asking is I want to run a cleanup function unique to that test that should only run after the very last iteration of param.
param = [(1, 'James'), (2, 'John')]
#pytest.mark.parametrize('id, user', param)
def test_myfunc(id, user):
# Do something
print(user)
# Run this only after the last param which would be after (2, 'John')
print('All done!')
I can run a conditional which checks for the value of param but I was just wondering if pytest has a way for this.
You'll need to perform this logic within a pytest hook, specifically the pytest_runtest_teardown hook.
Assuming your test looks like the following,
import pytest
param = [(1, 'James'), (2, 'John')]
#pytest.mark.parametrize('id, user', param)
def test_myfunc(id, user):
print(f"Iteration number {id}")
In the root of your test folder, create a conftest.py file and place the following,
func_of_interest = "test_myfunc"
def pytest_runtest_teardown(item, nextitem):
curr_name = item.function.__qualname__
# check to see it is the function we want
if curr_name == func_of_interest:
# check to see if there are any more functions after this one
if nextitem is not None:
next_name = nextitem.function.__qualname__
else:
next_name = "random_name"
# check to see if the next item is a different function
if curr_name != next_name:
print("\nPerform some teardown once")
Then when we run it, it produces the following output,
===================================== test session starts ======================================
platform darwin -- Python 3.9.1, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
cachedir: .pytest_cache
rootdir: ***
collected 2 items
test_grab.py::test_myfunc[1-James] Iteration number 1
PASSED
test_grab.py::test_myfunc[2-John] Iteration number 2
PASSED
Perform some teardown once
As we can see, the teardown logic was called exactly once, after the final iteration of the test call.
I am using Python's unittest with simple code like so:
suite = unittest.TestSuite()
suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(module1))
suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(module2))
However, I am wanting to do some custom things to each test after they have been gathered by the suite. I thought I could do something like this to iterate over the test cases in suite:
print suite.countTestCases()
for test in suite: # Also tried with suite.__iter__()
# Do something with test
print test.__class__
However, for as many test cases as I load, it only ever prints
3
<class 'unittest.suite.TestSuite'>
Is there a way I can get all the objects of class TestCase from the suite? Is there some other way I should be loading test cases to facilitate this?
Try
for test in suite:
print test._tests
I use this function as some of the elements in suite._tests are suites themselves:
def list_of_tests_gen(s):
""" a generator of tests from a suite
"""
for test in s:
if unittest.suite._isnotsuite(test):
yield test
else:
for t in list_of_tests_gen(test):
yield t
A neat way of getting list of tests is to use the nose2 collect plugin.
$ nose2 -s <testdir> -v --plugin nose2.plugins.collect --collect-only
test_1 (test_test.TestClass1)
Test Desc 1 ... ok
test_2 (test_test.TestClass1)
Test Desc 2 ... ok
test_3 (test_test.TestClass1)
Test Desc 3 ... ok
test_2_1 (test_test.TestClass2)
Test Desc 2_1 ... ok
test_2_2 (test_test.TestClass2)
Test Desc 2_2 ... ok
----------------------------------------------------------------------
Ran 5 tests in 0.001s
OK
It doesn't really run the tests.
You can install nose2 (and it's plugins) like this:
$ pip install nose2
And of course you can use nose2 to run unit tests e.g. like this or this:
# run tests from testfile.py
$ nose2 -v -s . testfile
# generate junit xml results:
$ nose2 -v --plugin nose2.plugins.junitxml -X testfile --junit-xml
$ mv nose2-junit.xml results_testfile.xml
Here is an internal helper function that was recently committed to Django that lets one iterate over a test suite's test cases:
from unittest import TestCase
def iter_test_cases(suite, reverse=False):
"""Return an iterator over a test suite's unittest.TestCase objects."""
if reverse:
suite = reversed(tuple(suite))
for test in suite:
if isinstance(test, TestCase):
yield test
else:
# Otherwise, assume it is a test suite.
yield from iter_test_cases(test, reverse=reverse)
This approach is needed as test suites can be nested arbitrarily deeply.
If you want to get the list of test functions (method prefixed by test in unittest.TestCase) in all your test modules:
import unittest
loader = unittest.TestLoader()
suite = loader.discover(YOUR_TEST_PATH)
for test_suite in suite:
for test_case in test_suite:
for test in test_case:
print(f"- {test}")
that gives:
- test_isupper (test_suite1.test_file1.TestA)
- test_upper (test_suite1.test_file1.TestA)
- test_isupper (test_suite2.test_file2.TestB)
- test_upper (test_suite2.test_file2.TestB)
or:
import unittest
loader = unittest.TestLoader()
suite = loader.discover(YOUR_TEST_PATH)
for test_suite in suite:
for test_case in test_suite:
for test in test_case:
print(f"- {test.id()}")
that gives:
test_suite1.test_file1.TestA.test_isupper
test_suite1.test_file1.TestA.test_upper
test_suite2.test_file2.TestB.test_isupper
test_suite2.test_file2.TestB.test_upper
I have a directory that contains my Python unit tests. Each unit test module is of the form test_*.py. I am attempting to make a file called all_test.py that will, you guessed it, run all files in the aforementioned test form and return the result. I have tried two methods so far; both have failed. I will show the two methods, and I hope someone out there knows how to actually do this correctly.
For my first valiant attempt, I thought "If I just import all my testing modules in the file, and then call this unittest.main() doodad, it will work, right?" Well, turns out I was wrong.
import glob
import unittest
testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
if __name__ == "__main__":
unittest.main()
This did not work, the result I got was:
$ python all_test.py
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
For my second try, I though, ok, maybe I will try to do this whole testing thing in a more "manual" fashion. So I attempted to do that below:
import glob
import unittest
testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
[__import__(str) for str in module_strings]
suites = [unittest.TestLoader().loadTestsFromName(str) for str in module_strings]
[testSuite.addTest(suite) for suite in suites]
print testSuite
result = unittest.TestResult()
testSuite.run(result)
print result
#Ok, at this point I have a result
#How do I display it as the normal unit test command line output?
if __name__ == "__main__":
unittest.main()
This also did not work, but it seems so close!
$ python all_test.py
<unittest.TestSuite tests=[<unittest.TestSuite tests=[<unittest.TestSuite tests=[<test_main.TestMain testMethod=test_respondes_to_get>]>]>]>
<unittest.TestResult run=1 errors=0 failures=0>
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
I seem to have a suite of some sort, and I can execute the result. I am a little concerned about the fact that it says I have only run=1, seems like that should be run=2, but it is progress. But how do I pass and display the result to main? Or how do I basically get it working so I can just run this file, and in doing so, run all the unit tests in this directory?
With Python 2.7 and higher you don't have to write new code or use third-party tools to do this; recursive test execution via the command line is built-in. Put an __init__.py in your test directory and:
python -m unittest discover <test_directory>
# or
python -m unittest discover -s <directory> -p '*_test.py'
You can read more in the python 2.7
or python 3.x unittest documentation.
Update for 2021:
Lots of modern python projects use more advanced tools like pytest. For example, pull down matplotlib or scikit-learn and you will see they both use it.
It is important to know about these newer tools because when you have more than 7000 tests you need:
more advanced ways to summarize what passes, skipped, warnings, errors
easy ways to see how they failed
percent complete as it is running
total run time
ways to generate a test report
etc etc
In python 3, if you're using unittest.TestCase:
You must have an empty (or otherwise) __init__.py file in your test directory (must be named test/)
Your test files inside test/ match the pattern test_*.py.
They can be inside a subdirectory under test/. Those subdirs can be named as anything, but they all need to have an __init__.py file in them
Then, you can run all the tests with:
python -m unittest
Done! A solution less than 100 lines. Hopefully another python beginner saves time by finding this.
You could use a test runner that would do this for you. nose is very good for example. When run, it will find tests in the current tree and run them.
Updated:
Here's some code from my pre-nose days. You probably don't want the explicit list of module names, but maybe the rest will be useful to you.
testmodules = [
'cogapp.test_makefiles',
'cogapp.test_whiteutils',
'cogapp.test_cogapp',
]
suite = unittest.TestSuite()
for t in testmodules:
try:
# If the module defines a suite() function, call it to get the suite.
mod = __import__(t, globals(), locals(), ['suite'])
suitefn = getattr(mod, 'suite')
suite.addTest(suitefn())
except (ImportError, AttributeError):
# else, just load all the test cases from the module.
suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))
unittest.TextTestRunner().run(suite)
This is now possible directly from unittest: unittest.TestLoader.discover.
import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)
runner = unittest.TextTestRunner()
runner.run(suite)
Well by studying the code above a bit (specifically using TextTestRunner and defaultTestLoader), I was able to get pretty close. Eventually I fixed my code by also just passing all test suites to a single suites constructor, rather than adding them "manually", which fixed my other problems. So here is my solution.
import glob
import unittest
test_files = glob.glob('test_*.py')
module_strings = [test_file[0:len(test_file)-3] for test_file in test_files]
suites = [unittest.defaultTestLoader.loadTestsFromName(test_file) for test_file in module_strings]
test_suite = unittest.TestSuite(suites)
test_runner = unittest.TextTestRunner().run(test_suite)
Yeah, it is probably easier to just use nose than to do this, but that is besides the point.
If you want to run all the tests from various test case classes and you're happy to specify them explicitly then you can do it like this:
from unittest import TestLoader, TextTestRunner, TestSuite
from uclid.test.test_symbols import TestSymbols
from uclid.test.test_patterns import TestPatterns
if __name__ == "__main__":
loader = TestLoader()
tests = [
loader.loadTestsFromTestCase(test)
for test in (TestSymbols, TestPatterns)
]
suite = TestSuite(tests)
runner = TextTestRunner(verbosity=2)
runner.run(suite)
where uclid is my project and TestSymbols and TestPatterns are subclasses of TestCase.
I have used the discover method and an overloading of load_tests to achieve this result in a (minimal, I think) number lines of code:
def load_tests(loader, tests, pattern):
''' Discover and load all unit tests in all files named ``*_test.py`` in ``./src/``
'''
suite = TestSuite()
for all_test_suite in unittest.defaultTestLoader.discover('src', pattern='*_tests.py'):
for test_suite in all_test_suite:
suite.addTests(test_suite)
return suite
if __name__ == '__main__':
unittest.main()
Execution on fives something like
Ran 27 tests in 0.187s
OK
I tried various approaches but all seem flawed or I have to makeup some code, that's annoying. But there's a convinient way under linux, that is simply to find every test through certain pattern and then invoke them one by one.
find . -name 'Test*py' -exec python '{}' \;
and most importantly, it definitely works.
In case of a packaged library or application, you don't want to do it. setuptools will do it for you.
To use this command, your project’s tests must be wrapped in a unittest test suite by either a function, a TestCase class or method, or a module or package containing TestCase classes. If the named suite is a module, and the module has an additional_tests() function, it is called and the result (which must be a unittest.TestSuite) is added to the tests to be run. If the named suite is a package, any submodules and subpackages are recursively added to the overall test suite.
Just tell it where your root test package is, like:
setup(
# ...
test_suite = 'somepkg.test'
)
And run python setup.py test.
File-based discovery may be problematic in Python 3, unless you avoid relative imports in your test suite, because discover uses file import. Even though it supports optional top_level_dir, but I had some infinite recursion errors. So a simple solution for a non-packaged code is to put the following in __init__.py of your test package (see load_tests Protocol).
import unittest
from . import foo, bar
def load_tests(loader, tests, pattern):
suite = unittest.TestSuite()
suite.addTests(loader.loadTestsFromModule(foo))
suite.addTests(loader.loadTestsFromModule(bar))
return suite
This is an old question, but what worked for me now (in 2019) is:
python -m unittest *_test.py
All my test files are in the same folder as the source files and they end with _test.
I use PyDev/LiClipse and haven't really figured out how to run all tests at once from the GUI. (edit: you right click the root test folder and choose Run as -> Python unit-test
This is my current workaround:
import unittest
def load_tests(loader, tests, pattern):
return loader.discover('.')
if __name__ == '__main__':
unittest.main()
I put this code in a module called all in my test directory. If I run this module as a unittest from LiClipse then all tests are run. If I ask to only repeat specific or failed tests then only those tests are run. It doesn't interfere with my commandline test runner either (nosetests) -- it's ignored.
You may need to change the arguments to discover based on your project setup.
Based on the answer of Stephen Cagle I added support for nested test modules.
import fnmatch
import os
import unittest
def all_test_modules(root_dir, pattern):
test_file_names = all_files_in(root_dir, pattern)
return [path_to_module(str) for str in test_file_names]
def all_files_in(root_dir, pattern):
matches = []
for root, dirnames, filenames in os.walk(root_dir):
for filename in fnmatch.filter(filenames, pattern):
matches.append(os.path.join(root, filename))
return matches
def path_to_module(py_file):
return strip_leading_dots( \
replace_slash_by_dot( \
strip_extension(py_file)))
def strip_extension(py_file):
return py_file[0:len(py_file) - len('.py')]
def replace_slash_by_dot(str):
return str.replace('\\', '.').replace('/', '.')
def strip_leading_dots(str):
while str.startswith('.'):
str = str[1:len(str)]
return str
module_names = all_test_modules('.', '*Tests.py')
suites = [unittest.defaultTestLoader.loadTestsFromName(mname) for mname
in module_names]
testSuite = unittest.TestSuite(suites)
runner = unittest.TextTestRunner(verbosity=1)
runner.run(testSuite)
The code searches all subdirectories of . for *Tests.py files which are then loaded. It expects each *Tests.py to contain a single class *Tests(unittest.TestCase) which is loaded in turn and executed one after another.
This works with arbitrary deep nesting of directories/modules, but each directory in between needs to contain an empty __init__.py file at least. This allows the test to load the nested modules by replacing slashes (or backslashes) by dots (see replace_slash_by_dot).
I just created a discover.py file in my base test directory and added import statements for anything in my sub directories. Then discover is able to find all my tests in those directories by running it on discover.py
python -m unittest discover ./test -p '*.py'
# /test/discover.py
import unittest
from test.package1.mod1 import XYZTest
from test.package1.package2.mod2 import ABCTest
...
if __name__ == "__main__"
unittest.main()
Encountered the same issue.
The solution is to add an empty __init__.py to each folder and uses python -m unittest discover -s
Project Structure
tests/
__init__.py
domain/
value_object/
__init__.py
test_name.py
__init__.py
presentation/
__init__.py
test_app.py
And running the command
python -m unittest discover -s tests/domain
To get the expected outcome
.
----------------------------------------------------------------------
Ran 1 test in 0.007s
Because Test discovery seems to be a complete subject, there is some dedicated framework to test discovery :
nose
Py.Test
More reading here : https://wiki.python.org/moin/PythonTestingToolsTaxonomy
This BASH script will execute the python unittest test directory from ANYWHERE in the file system, no matter what working directory you are in: its working directory always be where that test directory is located.
ALL TESTS, independent $PWD
unittest Python module is sensitive to your current directory, unless you tell it where (using discover -s option).
This is useful when staying in the ./src or ./example working directory and you need a quick overall unit test:
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"
python -m unittest discover -s "$readlink"/test -v
SELECTED TESTS, independent $PWD
I name this utility file: runone.py and use it like this:
runone.py <test-python-filename-minus-dot-py-fileextension>
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"
(cd "$dirname"/test; python -m unittest $1)
No need for a test/__init__.py file to burden your package/memory-overhead during production.
I have no package and as mentioned on this page, this is creating issue while issing dicovery. So, I used the following solution. All the test result will be put in a given output folder.
RunAllUT.py:
"""
The given script is executing all the Unit Test of the project stored at the
path %relativePath2Src% currently fixed coded for the given project.
Prerequired:
- Anaconda should be install
- For the current user, an enviornment called "mtToolsEnv" should exists
- xmlrunner Library should be installed
"""
import sys
import os
import xmlrunner
from Repository import repository
relativePath2Src="./../.."
pythonPath=r'"C:\Users\%USERNAME%\.conda\envs\YourConfig\python.exe"'
outputTestReportFolder=os.path.dirname(os.path.abspath(__file__))+r'\test-reports' #subfolder in current file path
class UTTesting():
"""
Class tto run all the UT of the project
"""
def __init__(self):
"""
Initiate instance
Returns
-------
None.
"""
self.projectRepository = repository()
self.UTfile = [] #List all file
def retrieveAllUT(self):
"""
Generate the list of UT file in the project
Returns
-------
None.
"""
print(os.path.realpath(relativePath2Src))
self.projectRepository.retriveAllFilePaths(relativePath2Src)
#self.projectRepository.printAllFile() #debug
for file2scan in self.projectRepository.devfile:
if file2scan.endswith("_UT.py"):
self.UTfile.append(file2scan)
print(self.projectRepository.devfilepath[file2scan]+'/'+file2scan)
def runUT(self,UTtoRun):
"""
Run a single UT
Parameters
----------
UTtoRun : String
File Name of the UT
Returns
-------
None.
"""
print(UTtoRun)
if UTtoRun in self.projectRepository.devfilepath:
UTtoRunFolderPath=os.path.realpath(os.path.join(self.projectRepository.devfilepath[UTtoRun]))
UTtoRunPath = os.path.join(UTtoRunFolderPath, UTtoRun)
print(UTtoRunPath)
#set the correct execution context & run the test
os.system(" cd " + UTtoRunFolderPath + \
" & " + pythonPath + " " + UTtoRunPath + " " + outputTestReportFolder )
def runAllUT(self):
"""
Run all the UT contained in self
The function "retrieveAllUT" sjould ahve been performed before
Returns
-------
None.
"""
for UTfile in self.UTfile:
self.runUT(UTfile)
if __name__ == "__main__":
undertest=UTTesting()
undertest.retrieveAllUT()
undertest.runAllUT()
In my project specific, I have a class that I used in other script. This might be an overkill for your usecase.
Repository.py
import os
class repository():
"""
Class that decribed folder and file in a repository
"""
def __init__(self):
"""
Initiate instance
Returns
-------
None.
"""
self.devfile = [] #List all file
self.devfilepath = {} #List all file paths
def retriveAllFilePaths(self,pathrepo):
"""
Retrive all files and their path in the class
Parameters
----------
pathrepo : Path used for the parsin
Returns
-------
None.
"""
for path, subdirs, files in os.walk(pathrepo):
for file_name in files:
self.devfile.append(file_name)
self.devfilepath[file_name] = path
def printAllFile(self):
"""
Display all file with paths
Parameters
----------
def printAllFile : TYPE
DESCRIPTION.
Returns
-------
None.
"""
for file_loop in self.devfile:
print(self.devfilepath[file_loop]+'/'+file_loop)
In your test files, you need to have a main like this:
if __name__ == "__main__":
import xmlrunner
import sys
if len(sys.argv) > 1:
outputFolder = sys.argv.pop() #avoid conflic with unittest.main
else:
outputFolder = r'test-reports'
print("Report will be created and store there: " + outputFolder)
unittest.main(testRunner=xmlrunner.XMLTestRunner(output=outputFolder))
Here is my approach by creating a wrapper to run tests from the command line:
#!/usr/bin/env python3
import os, sys, unittest, argparse, inspect, logging
if __name__ == '__main__':
# Parse arguments.
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("-?", "--help", action="help", help="show this help message and exit" )
parser.add_argument("-v", "--verbose", action="store_true", dest="verbose", help="increase output verbosity" )
parser.add_argument("-d", "--debug", action="store_true", dest="debug", help="show debug messages" )
parser.add_argument("-h", "--host", action="store", dest="host", help="Destination host" )
parser.add_argument("-b", "--browser", action="store", dest="browser", help="Browser driver.", choices=["Firefox", "Chrome", "IE", "Opera", "PhantomJS"] )
parser.add_argument("-r", "--reports-dir", action="store", dest="dir", help="Directory to save screenshots.", default="reports")
parser.add_argument('files', nargs='*')
args = parser.parse_args()
# Load files from the arguments.
for filename in args.files:
exec(open(filename).read())
# See: http://codereview.stackexchange.com/q/88655/15346
def make_suite(tc_class):
testloader = unittest.TestLoader()
testnames = testloader.getTestCaseNames(tc_class)
suite = unittest.TestSuite()
for name in testnames:
suite.addTest(tc_class(name, cargs=args))
return suite
# Add all tests.
alltests = unittest.TestSuite()
for name, obj in inspect.getmembers(sys.modules[__name__]):
if inspect.isclass(obj) and name.startswith("FooTest"):
alltests.addTest(make_suite(obj))
# Set-up logger
verbose = bool(os.environ.get('VERBOSE', args.verbose))
debug = bool(os.environ.get('DEBUG', args.debug))
if verbose or debug:
logging.basicConfig( stream=sys.stdout )
root = logging.getLogger()
root.setLevel(logging.INFO if verbose else logging.DEBUG)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.INFO if verbose else logging.DEBUG)
ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
root.addHandler(ch)
else:
logging.basicConfig(stream=sys.stderr)
# Run tests.
result = unittest.TextTestRunner(verbosity=2).run(alltests)
sys.exit(not result.wasSuccessful())
For sake of simplicity, please excuse my non-PEP8 coding standards.
Then you can create BaseTest class for common components for all your tests, so each of your test would simply look like:
from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
def test_foo(self):
driver = self.driver
driver.get(self.base_url + "/")
To run, you simply specifying tests as part of the command line arguments, e.g.:
./run_tests.py -h http://example.com/ tests/**/*.py