I am trying to mock a class which is instantiated in the constructor of the class I am trying to test. If I define the class I am trying to mock in the same module as the one I am trying to test, everything works fine but when they are in separate modules, I get errors.
Here's my example, taken from here (Note that in my real example, the test class is in a "tests" submodule and the other two files are in "app.src.code..." module.
What am I missing?
helper.py:
import os
class Helper:
def __init__(self, path):
self.path = path
def get_path(self):
base_path = os.getcwd()
return os.path.join(base_path, self.path)
worker.py:
from helper import Helper
class Worker:
def __init__(self):
self.helper = Helper('db')
def work(self):
path = self.helper.get_path()
print(f'Working on {path}')
return path
test_worker.py:
import unittest
from unittest.mock import patch
from worker import Worker
class WorkerTest(unittest.TestCase):
def test_patching_class(self):
with patch('helper.Helper') as MockHelper:
MockHelper.return_value.get_path.return_value = 'testing'
worker = Worker()
MockHelper.assert_called_once_with('db')
self.assertEqual(worker.work(), 'testing')
You need to use the patch decorators to create the mock for the Helper class of the helper.py module.
E.g.
helper.py:
import os
class Helper:
def __init__(self, path):
self.path = path
def get_path(self):
base_path = os.getcwd()
return os.path.join(base_path, self.path)
worker.py:
from helper import Helper
class Worker:
def __init__(self):
self.helper = Helper('db')
def work(self):
path = self.helper.get_path()
print(f'Working on {path}')
return path
test_worker.py:
import unittest
from unittest.mock import patch
from worker import Worker
class TestWorker(unittest.TestCase):
def test_work(self):
with patch('worker.Helper') as mock_Helper:
mock_helper_instance = mock_Helper.return_value
mock_helper_instance.get_path.return_value = 'testing'
worker = Worker()
mock_Helper.assert_called_once_with('db')
self.assertEqual(worker.work(), 'testing')
if __name__ == '__main__':
unittest.main()
unit test results with coverage report:
Working on testing
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Name Stmts Miss Cover Missing
-------------------------------------------------------------------------
src/stackoverflow/61008064/helper.py 7 3 57% 6, 9-10
src/stackoverflow/61008064/test_worker.py 13 0 100%
src/stackoverflow/61008064/worker.py 8 0 100%
-------------------------------------------------------------------------
TOTAL 28 3 89%
Related
When I have BaseTestCase in the same file as my test VS code Test explorer is able to identity my test.
Sample Code
Folder structure
MyProject/
app/
test/
myworking_test.py
base.py
mynotworking_test.py
myworking_test.py This one works and able to identify by VS code Explorer
import unittest
class BaseTestCase(unittest.TestCase):
def setUp(self):
self.num = 1
def tearDown(self):
self.num = None
class MyTest(BaseTestCase):
def runTest(self):
self.assertEqual(self.num, 1)
If I split this into 2 file where BaseTestCase is another file and test in another file then how do I configure it.
base.py
import unittest
class BaseTestCase(unittest.TestCase):
def setUp(self):
self.num = 1
def tearDown(self):
self.num = None
mynotworking_test.py
from test.base import BaseTestCase
class MyTest(BaseTestCase):
def runTest(self):
self.assertEqual(self.num, 1)
Updated based on suggestion given below:
As you can see it is giving unresolved import base. The same goes for when using just "import base"
The files are in the same directory so instead try
import base
k = base.BaseTestCase()
class MyTest(k):
from base import BaseTestCase
class MyTest(BaseTestCase):
def runTest(self):
self.assertEqual(self.num, 1)
or
import base
class MyTest(base.BaseTestCase):
def runTest(self):
self.assertEqual(self.num, 1)
Edit 1
If I run the example code the problem seems to be the file name: base.py
I get a lot of compiler warnings.
Any other name but base is ok.
With the following files I have it working
base_test.py
import unittest
class BaseTestCase(unittest.TestCase):
def setUp(self):
self.num = 1
def tearDown(self):
self.num = None
mynowworking_test.py
from test.base_test import BaseTestCase
class MyTest(BaseTestCase):
def runTest(self):
self.assertEqual(self.num, 1)
or
import test.base_test
class MyTest(test.base_test.BaseTestCase):
def runTest(self):
self.assertEqual(self.num, 1)
Edit 2
I renamed base_test.py to base.py.
Change the import to:
from test.base import BaseTestCase
import test.base
I still have the errors in the Problem panel but the tests work.
I close VSC and start it again.
Now the Problem panel does not show errors in base.py and the tests work.
It looks like I have the original files from the repo back and it works.
But it did not run the first time I opened the project.
How to find class parent and subclasses across different modules without running the code (static analysis)
Module Contains __init__.py and 4 files as below
Example first_file.py
class Parent(object):
def __init__(self):
pass
def method1(self):
print('parent')
Example second_file.py
from first_file import Parent
class Child(Parent):
def __init__(self):
pass
def method1(self):
print('child')
Example third_file.py
from first_file import Parent
class Child1(Parent):
def __init__(self):
pass
def method1(self):
print('child1')
Example fourth_file.py
from second_file import Child
class Child2(Child):
def __init__(self):
pass
def method1(self):
print('child2')
I want the way to list down the subclasses of parent given filename and class
example
>>> findclasshierchay first_file.py --class Parent
and it will list down all subclasses with filenames
Here's a stab at using cls.mro().
Disclaimer, I don't know if you consider this to be running the code or not. It's not static analysis, but if you have no great side effects upon module load, it really doesn't do that much either.
file1.py
class File1:
pass
file2.py
from file1 import File1
class File2(File1):
pass
listhier.py
import sys
import copy
import os
from importlib import import_module
from importlib.util import find_spec as importlib_find
#shamelessly copied from django 👇
def import_string(dotted_path):
"""
Import a dotted module path and return the attribute/class designated by the
last name in the path. Raise ImportError if the import failed.
"""
try:
module_path, class_name = dotted_path.rsplit('.', 1)
except ValueError as err:
raise ImportError("%s doesn't look like a module path" % dotted_path) from err
module = import_module(module_path)
try:
return getattr(module, class_name)
except AttributeError as err:
raise ImportError('Module "%s" does not define a "%s" attribute/class' % (
module_path, class_name)
) from err
toload = sys.argv[1]
cls = import_string(toload)
for cls_ in cls.mro():
print("%s.%s" % (cls_.__module__, cls_.__name__))
python listhier.py file2.File2
output:
file2.File2
file1.File1
builtins.object
How to import classes from all .py in a module with same structure and run by iterating over it. For Example,
module_one:
script_a:
class A:
def __init__(self,**kwargs):
code here
def run(self,**kwargs):
code here
def finish(self,**kwargs):
code here
script_b:
class B:
def __init__(self,**kwargs):
code here
def run(self,**kwargs):
code here
def finish(self,**kwargs):
code here
and so on ...
module_two:
script:
class Run:
def run_all(self,**kwargs):
for class in classes_from_module_one:
c = class()
c.run()
c.finish()
First of all, please note that module refers to python file, while to what you refer as module is usually called a package.
Now, for your problem, you could use some mixture of utilities found in pkgutil, importlib, and inspect or simple dir(). For example, using walk_modules and get_members:
# pack/mod_a.py
class A:
def __init__(self):
pass
def run(self):
print("A run!")
def finish(self):
print("A finished!")
# pack/mod_b.py
class B:
def __init__(self):
pass
def run(self):
print("B run!")
def finish(self):
print("B finished!")
# all.py
from importlib import import_module
from inspect import getmembers
from pkgutil import iter_modules
class Run:
def run_all(self, **kwargs):
modules = iter_modules(["pack"])
for module in modules:
members = getmembers(import_module(f"pack.{module[1]}"))
my_classes = [member for name, member in members if not name.startswith("_")]
for cls in my_classes:
c = cls()
c.run()
c.finish()
if __name__ == '__main__':
run = Run()
run.run_all()
The output is:
A run!
A finished!
B run!
B finished!
However for this solution you have to note, that getmembers will return all members of module - including built-ins, and imported entities to the modules - so you will have to implement your own check to properly filter-out those unwanted (see simple startswith in my example).
When I import MyApp from app.py, a instance of the SerialConnection class
is created immediately. I want to mock the SerialConnection class but I still need a function from within this SerialConnection class.
app.py
# A module that creates strings that is sent via SerialConnection
from myserial import SerialConnection
class MyApp():
global ser
ser = SerialConnection() # ---> Needs to be mocked
def __init__(self):
pass
#ser.decorator # ---> Needs to be by-passed
def myfunc(self):
return 'testing1234'
myserial.py
# A module to write and read to a serial/UART buffer
from functools import wraps
import serial
class SerialConnection():
def __init__(self):
""" Initilize the Serial instance. """
self.ser = serial.Serial(port=2, baudrate=9600)
def decorator(self, func):
#wraps(func)
def wrapper_func(*args):
return func(*args)
return wrapper_func
test_app.py
# This file tests the function "myfunc" from "MyApp" class.
from patch import mock
#patch('myserial.SerialConnection')
def test_myfunc(mock_class):
# I can now import MyApp successfuly...
from app import MyApp
# ..but I need to bypass the decorator myserial.SerialConnection.decorator
# do I add a by-pass decorator to the "mock_class"?
# myfunc() turns out to be mocked and not a real function
assert MyApp().myfunc() == 'testing1234'
I'm trying to load test data base on the application name from a configuration file.
I'm using ConfigParser which is a nosetest plug in.
Here is my code. I just don't know how to pass the app name while loading the tests on the fly. I tried the constructor, but could not figure out a way to pass parameters to the loadTestsFromTestCase method.
Any ideas?
import unittest
class TestMyApp(unittest.TestCase):
def test1(self):
print "test1: %s" % self.app
def test2(self):
print "test2: %s" % self.app
if __name__ == '__main__':
# here specify the app name
suite = unittest.TestLoader().loadTestsFromTestCase(TestMyApp)
# here specify the other app name
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMyApp))
unittest.TextTestRunner(verbosity=2).run(suite)
You are need a test parametrization! It is easy to done with pytest. Look at this code example:
import pytest
class AppImpl:
def __init__(self, app_name):
self.name = app_name
def get_name(self):
return self.name
#pytest.fixture(params=['App1', 'App2'])
def app(request):
return AppImpl(request.param)
def test_1(app):
assert app.get_name()
def test_2(app):
assert 'App' in app.get_name()
if __name__ == '__main__':
pytest.main(args=[__file__, '-v'])
By using class implementation of you logic AppImpl, you can create a fixture app, which can be parametrized by specific arg params=['App1', 'App2']. Then in your tests test_1 and test_2, use fixture name app in funcargs. This possibility provides more modularity and readability tests.