Here is my structure:
directory/
__init__.py (blank)
myClass.py
test/
UnitTest.py
IntegrationTest.py
__init__.py (blank)
Let's use UnitTest as the example (both of them result in 0 tests). Feel free to critique my imports, I was fiddling around with it forever to import the class correctly so I can execute the UnitTest script. Also yes, I did copy a lot of code from other stack exchange questions in my search.
Before I moved to this structure, I had everything in one directory, so I know the test file works (outside of the imports)
import sys
from pathlib import Path
if __name__ == '__main__' and __package__ is None:
file = Path(__file__).resolve()
parent, top = file.parent, file.parents[2]
sys.path.append(str(top))
try:
sys.path.remove(str(parent))
except ValueError: # Already removed
pass
import directory.test
__package__ = 'directory.test'
from myClass import myClass
import unittest
from unittest import mock
print("Running Unit Tests...")
mc = myClass(params)
def mockingResponse(*args, **kwargs):
class MockResponse:
def __init__(self, json_data, status_code):
self.json_data = json_data
self.status_code = status_code
def json(self):
return self.json_data
#if/elses that return value
#mock.patch('myClass.requests.get', side_effect = mockingResponse)
class unitTest(unittest.TestCase):
print("this should run before test call")
def testsomefunc():
response = mc.somefunc()
differenceSet = set(response) ^ set(mockrespose from if/elses)
assert len(differenceSet) == 0
def testotherfunc():
#7 tests in total, same layout, different mockresponse
print("is this actually running")
unittest.main()
Then I get this in terminal:
privacy:~/git/directory$ python3 -m test.UnitTest.py
Running Unit Tests...
this should run before test call
is this actually running
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
Checking online, I know it's not the issue of not having my names start with test and it's not because I have it indented wrong. Can't find much else in my search.
My only other thought is that it might be because I ran it as a module, but if I don't I have more problems with importing myClass. Of the many solutions I've tried for importing, this is the only one that has worked thus far.
Related
My tests are in the following folder structure. Though I have also placed a sample test in the root, which doesn't work either. I get no tests ran in 6.08s
root/
test/
integrations/
test_sample.py
__init__.py
__init__.py
I have tried using the command pytest by itself as well as specifying the test location pytest ./test/integrations/test_sample.py
I've been using a class to define multiple tests, but simple functions aren't running either.
Class:
class TestClass:
def add(self):
pass
Function:
def add():
pass
You need to write a test as function with a name beginning with test. See pytest's docs: test-discovery.
In this test or function:
instantiate your class
and run the class' method or function
import pytest
# this class will be tested
class ClassUnderTest:
def add(self):
return "it ran"
# This test will run because its name begins with "test"
def test_add():
cls = ClassUnderTest()
assert cls.add() == "it ran"
# This won't run because its name does not begin with "test"
def run_add():
cls = ClassUnderTest()
assert cls.add() == "it ran"
# ===== 1 passed in 0.05s =====
Methods name should start with test_. (inside class or out) is this what you're missing?
I have the following project structure:
root/
|-mylib/
|-tests/
| |-__init__.py
| |-test_trend.py
|-__init__.py
|-data.py
|-trend.py
In trend.py:
from mylib.data import get_item
def get_trend(input: str):
item = get_item(input)
return f"Trend is: {item}"
Inside data.py:
def get_item(id):
print("ORIGINAL")
return get_item_from_db(id)
Testing
I want to test get_trend in isolation, therefore I patch get_item inside: test_trend.py:
import unittest
from unittest.mock import patch
from mylib.trend import get_trend
def p__get_item(input):
return 0
class MyTestCase(unittest.TestCase):
#patch("mylib.data.get_item", new=p__get_item)
def test_trend(self):
v = get_trend()
self.assertEqual(v, "Trend is: 0")
But when I run the tests (the command is run from inside the root directory):
python -m unittest discover
I see that the log shows that the original get_item is called. The test is failing of course.
What am I doing wrong?
Attempt 1
If I try this other flavor of the patch APIL
class MyTestCase(unittest.TestCase):
#patch("mylib.data.get_item")
def test_trend(self, mocked_fun):
mocked_fun.return_value = 0
v = get_trend()
self.assertEqual(v, "Trend is: 0")
It still does not work. In the console log I can see ORIGINAL being printed and the test fails.
Experiment 1
If I change the target in #patch to a non existing attribute like:
#patch("mylib.data.non_existing", new=p__get_item)
I actually get an error from the library saying the module does not contain such attribute. So, it seems like mylib.data.get_item is correctly being targeted, but still the patching is not happening.
Explanation
get_item is called in mylib.trend module, however, you patched it in mylib.data module, which is the wrong place. patch works by replacing objects on import. In this case, you want to patch get_item inside mylib.trend module not mylib.data.
Solution
class MyTestCase(unittest.TestCase):
#patch("mylib.trend.get_item")
def test_trend(self, mocked_get_item):
mocked_get_item.return_value = 0
...
Notes
Further explanation on where to patch can be found on the official python documentation here.
How can I get test_greet to run in the below; note: test_one(when uncommented) is seen and run by the test runner; to be specific, I want the line unittest.main() to correctly pick up the module level test (test_greet).
import unittest
#class MyTests(unittest.TestCase):
# def test_one(self):
# assert 1==2
def test_greet():
assert 1==3
if __name__=="__main__":
unittest.main()
Let's say i have a file called MyTests.py as below:
import unittest
class MyTests(unittest.TestCase):
def test_greet(self):
self.assertEqual(1,3)
Then:
Open a CMD in the folder that MyTests.py exists
Run python -m unittest MyTests
Please note that, all your tests must have the test_ otherwise, it will not be run.
I have a bash script to execute my python tests and I would like to filter all test cases that have NOT_DONE in them
This is what I tried
python3 -m unittest discover -s ${FOLDER} -p 'test_((?!NOT_DONE).)*_ALL.py'
Input example :
test_word_NOT_DONE_more_words_alot_more_words_ALL.py <- This test shouldn't be executed
But this one should :
test_word_more_words_alot_more_words_ALL.py
Path Solution
Directory Structure
unittesting/
launcher.py
tests/
__init__.py
test_finished.py
test_NOT_DONE.py
folder/
__init__.py
test_finished2.py
test_NOT_DONE2.py
Inside each test file is print(__file__), nested under a TestCase method. Therefore, only if the module is imported and the test cases run, will it be executed.
Code:
import importlib
import os
import sys
import unittest
HOME = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, 'tests')
def check_file(file):
'''Check if file is test'''
return all((
'NOT_DONE' not in file,
file.endswith('.py'),
file != '__init__.py'
))
def find_paths(home=HOME):
'''Find all paths'''
os.chdir(HOME)
for root, dirs, files in os.walk('tests'):
for file in files:
if check_file(file):
if root != 'tests':
yield os.path.join(root[len('tests/'):], file)
else:
yield file
def normalize(path):
'''Normalize path to dotted name'''
path = os.path.splitext(path)[0]
unix = path.replace('/', '.')
return unix.replace('\\', '.')
def tests(paths=None):
'''Load and run tests'''
if paths is None:
paths = map(normalize, find_paths())
modules = (importlib.import_module(i) for i in paths)
suite = unittest.TestSuite()
loader = unittest.TestLoader()
for module in modules:
tests = loader.loadTestsFromModule(module)
suite.addTests(tests)
runner = unittest.TextTestRunner()
runner.run(suite)
if __name__ == '__main__':
tests()
As you can see, this gets unwieldly quickly, and very hard to manage. There's a simpler way. It runs, however.
$ python /home/alex/git/unittesting/launcher.py
tests/test_finished.pyc
.tests/folder/test_finished2.pyc
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
Pythonic Solution
Inside each of my files that is not complete, I put the variable NOT_DONE = True, and each of my classes has a decorator skipif.
Directory Structure
unittesting/
launcher.py
tests/
__init__.py
test1.py
test2.py
folder/
__init__.py
test3.py
test4.py
In this example, test2 and test4 have NOT_DONE = True, while test1 and test3 have NOT_DONE = False.
An example file is as follows:
import unittest
NOT_DONE = False
# CASES
# -----
#unittest.skipIf(NOT_DONE, 'Reason')
class TestPrint(unittest.TestCase):
def test_print(self):
print(__file__)
if __name__ == '__main__':
unittest.main()
Now, to run I simply do:
$ python -m unittest discover tests
tests/test1.py
tests/folder/test3.py
.
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK (skipped=2)
Best Approach
Unfinished unittests should have a unittest.skipIf(True, 'Unfinished') line, so you get control not only at the module level, but also at the class or even method level. In the following example, I have one, finished unittest and one unfinished unittest. Running the example skips the first unittest, but runs the rest of the module.
import unittest
# CASES
# -----
#unittest.skipIf(True, 'Not Finished')
class TestPrint(unittest.TestCase):
def test_print(self):
print(__file__)
class TestPrinting(unittest.TestCase):
def test_print(self):
print(__file__)
if __name__ == '__main__':
unittest.main()
I'm writing a Lua wrapper and the highest level of abstraction calls lua_close in it's __del__ method. As far as I can tell every test of this passes except the setuptools test (i.e. regular unit testing works, unit testing w/ setuptools does not). Am I doing something wrong, or is there a bug in setuptools/unittest?
My setup.py:
from setuptools import setup
setup(name="PyLua",
version="0.1",
description="A cffi based lua package.",
packages=['lua'],
author="Alex Orange",
author_email="crazycasta#gmail.com",
license="AGPLv3",
test_suite='test',
)
My lua/lua.py:
from math import sin
class Test(object):
def __del__(self):
print sin(1)
My test/lua.py:
from __future__ import absolute_import
import unittest
def load_tests(loader, tests, pattern):
suite = unittest.TestSuite()
tests = loader.loadTestsFromTestCase(RealCodeTestCase)
suite.addTests(tests)
return suite
class RealCodeTestCase(unittest.TestCase):
def setUp(self):
from lua.lua import Test
self.L = Test()
def testCallStuff(self):
self.assertEqual(1,1)
My test2.py:
import unittest
import test.lua
suite = unittest.TestLoader().loadTestsFromTestCase(test.lua.RealCodeTestCase)
unittest.TextTestRunner(verbosity=2).run(suite)
Results of python setup.py test:
running test
running egg_info
writing PyLua.egg-info/PKG-INFO
writing top-level names to PyLua.egg-info/top_level.txt
writing dependency_links to PyLua.egg-info/dependency_links.txt
reading manifest file 'PyLua.egg-info/SOURCES.txt'
writing manifest file 'PyLua.egg-info/SOURCES.txt'
running build_ext
testCallStuff (test.lua.RealCodeTestCase) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Exception TypeError: "'NoneType' object is not callable" in <bound method Test.__del__ of <lua.lua.Test object at 0x7fa546ccc350>> ignored
Results of python test2.py
testCallStuff (test.lua.RealCodeTestCase) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
0.841470984808
P.S. Python is CPython-2.7
It appears you can prevent this from happening by providing an explicit tearDown in your test:
class RealCodeTestCase(unittest.TestCase):
def setUp(self):
from lua.lua import Test
self.L = Test()
def tearDown(self):
del self.L
def testCallStuff(self):
self.assertEqual(1,1)
I don't really know why the error occurs, but since the tearDown method prevents the error, my guess is that behind the scenes, setuptools implements its own variant of tearDown, which clears a bit too much, including imports, such as the from math import sin.
The error message indicates sin can't be called, since while the name exists, it has been turned into a None. I can only guess this happens somewhere in that tearDown method implemented by setuptools.