Python unit testing on class methods with no input arguments - python

Given a class with class methods that contain only self input:
class ABC():
def __init__(self, input_dict)
self.variable_0 = input_dict['variable_0']
self.variable_1 = input_dict['variable_1']
self.variable_2 = input_dict['variable_2']
self.variable_3 = input_dict['variable_3']
def some_operation_0(self):
return self.variable_0 + self.variable_1
def some_operation_1(self):
return self.variable_2 + self.variable_3
First question: Is this very bad practice? Should I just refactor some_operation_0(self) to explicitly take the necessary inputs, some_operation_0(self, variable_0, variable_1)? If so, the testing is very straightforward.
Second question: What is the correct way to setup my unit test on the method some_operation_0(self)?
Should I setup a fixture in which I initialize input_dict, and then instantiate the class with a mock object?
#pytest.fixture
def generator_inputs():
f = open('inputs.txt', 'r')
input_dict = eval(f.read())
f.close()
mock_obj = ABC(input_dict)
def test_some_operation_0():
assert mock_obj.some_operation_0() == some_value
(I am new to both python and general unit testing...)

Those methods do take an argument: self. There is no need to mock anything. Instead, you can simply create an instance, and verify that the methods return the expected value when invoked.
For your example:
def test_abc():
a = ABC({'variable_0':0, 'variable_1':1, 'variable_2':2, 'variable_3':3))
assert a.some_operation_0() == 1
assert a.some_operation_1() == 5
If constructing an instance is very difficult, you might want to change your code so that the class can be instantiated from standard in-memory data structures (e.g. a dictionary). In that case, you could create a separate function that reads/parses data from a file and uses the "data-structure-based" __init__ method, e.g. make_abc() or a class method.
If this approach does not generalize to your real problem, you could imagine providing programmatic access to the key names or other metadata that ABC recognizes or cares about. Then, you could programmatically construct a "defaulted" instance, e.g. an instance where every value in the input dict is a default-constructed value (such as 0 for int):
class ABC():
PROPERTY_NAMES = ['variable_0', 'variable_1', 'variable_2', 'variable_3']
def __init__(self, input_dict):
# implementation omitted for brevity
pass
def some_operation_0(self):
return self.variable_0 + self.variable_1
def some_operation_1(self):
return self.variable_2 + self.variable_3
def test_abc():
a = ABC({name: 0 for name in ABC.PROPERTY_NAMES})
assert a.some_operation_0() == 0
assert a.some_operation_1() == 0

Related

The class doesn't take values as numbers when creating an object of a class, but takes them as default values

The task is to write a class decorator, which reads a JSON file and makes its key/values to become properties of the class.
But one of conditions is that there has to be the ability to pass values manually (by creating a class object) as well.
I almost did it. There's just a tiny problem. The program reads data from JSON file and passes them successfully to the class. But when passing values manually during creation of an object of the class, values don't change and they are still being taken from JSON.
The problem only disappears when passing values as default values.
room = Room(1, 1) # Doesn't work
room = Room(tables=1, chairs=1) # Does work
Since arguments have to be passed only as numbers in tests, I have to manage it to work with just numbers, not default values.
Here's the code.
from json import load
def json_read_data(file):
def decorate(cls):
def decorated(*args, **kwargs):
if kwargs == {}:
with open(file) as f:
params = {}
for key, value in load(f).items():
params[key] = value
return cls(**params)
else:
return cls(*args, **kwargs)
return decorated
return decorate
#json_read_data('furniture.json')
class Room:
def __init__(self, tables=None, chairs=None):
self.tables = tables
self.chairs = chairs
def is_it_enough(self):
return self.chairs * 0.5 - self.tables > 0.4
kitchen = Room() # This is passing values from JSON file
print(kitchen.__dict__) # Prints {'tables': 2, 'chairs': 5}
room = Room(tables=1, chairs=1) # This is passing values manually
print(room.__dict__) # Prints {'tables': 1, 'chairs': 1}
'''
JSON file:
{
"tables": 2,
"chairs": 5
}
'''
But if we change to room = Room(1, 1), print(room.dict) prints {'tables': 2, 'chairs': 5} again. Please help me solve this problem!
You need to add your arguments to the decorator. Remember that your decorator is called first and then it calls the decorated function.
You could declare your decorator as: def json_read_data(file, *args): then the subsequent calls to cls() would have to be adapted to accept them. The second one already does, the first one needs modification.
It seems, this edit really worked:
def decorated(*args, **kwargs):
if not args and not kwargs:

How to best implement a class method that is different for every object of this class

As a teacher I want to code a worksheet-generator for mathematical problems.
Python should generate mathematical problems and their solution (e.g. create a polynomial function and calculate their zeros). It then writes a LaTeX input file and creates a pdf via pdflatex. It works for several problems, but now I want to generalize it and make it object-orientated to speed up the creation of further worksheets.
So I created a class Problem with several parameters and methods. But every Problem-instance should have a different function for creating the text for the mathematical problem and its solution (because every object is a different mathematical problem). And I've got no clue how I should manage this in an elegant/proper way.
At the moment I'm doing something like this:
import random
class Problem_generator(object):
def __init__(self):
self.problemlist = []
def create_worksheet(self):
""" Creates the latex-document by writing the output in a LaTeX input file. For
this minimal example, I'll just print the outputs instead.
"""
for problem in self.problemlist:
text = problem.create()
print("Problem: " + text[0])
print("Solution: " + text[1])
class Problem(object):
def __init__(self, problem_generator, function):
# do some stuff like to create Tkinter-GUI-objects for every problem
self.function = function
problem_generator.problemlist.append(self)
def create(self):
return self.function()
def add_numbers():
""" Create the problem to add two numbers. """
a, b = random.randint(0, 100), random.randint(0, 100)
problem_text = str(a) + " + " + str(b)
solution_text = str(a+b)
return problem_text, solution_text
generator = Problem_generator()
problem_1 = Problem(generator, add_numbers)
generator.create_worksheet() # in the complete program I start this over a GUI
It works alright, but it doesn't feel "right".
I also thought about implementing the Problem class with a create() method that only raises a not-implemented-error and then to update the create() method for every problem I create. But as far as I read, that would update the create() method for every object of the class.
So I would be happy to get some tips/suggestions how I could manage the described problem in an "elegant" way.
Here's the way I'd do it:
import random
from typing import Callable, List, Tuple
ProblemCreator = Callable[[], Tuple[str, str]] # returns (problem, solution)
class Problem:
def __init__(self, function: ProblemCreator) -> None:
# do some stuff like to create Tkinter-GUI-objects for every problem
self.create = function
class ProblemGenerator:
def __init__(self) -> None:
self.problem_list: List[Problem] = []
def create_worksheet(self) -> None:
"""
Creates the latex-document by writing the output in a *.tex-file.
For this minimal example, I'll just print the outputs instead.
"""
for problem in self.problem_list:
p, s = problem.create()
print(f"Problem: {p}")
print(f"Solution: {s}")
def generate_problem(self, problem: ProblemCreator) -> None:
self.problem_list.append(Problem(problem))
def add_numbers() -> Tuple[str, str]:
""" Create the problem to add two numbers. """
a, b = random.randint(0, 100), random.randint(0, 100)
return f"{a} + {b}", f"{a+b}"
generator = ProblemGenerator()
generator.generate_problem(add_numbers)
generator.create_worksheet() # in the complete program I start this over a GUI
I've used type annotations and other Python 3 features (like f-strings) to improve clarity.
There is no need for create to be a method -- just make it a callable attribute (I've given the type of this callable a name, ProblemCreator, since it forms an important part of this interface). Similarly, there's no need for Problem to know about ProblemGenerator and be responsible for adding itself to the generator's list; it just creates a circular dependency that you don't need. Instead, have ProblemGenerator be in charge of generating problems (like its name says)!
I really don't find any use for the Problem_generator class, here is how I would do it (I added a ProblemGenerator class but you can loop and call problem.create)
By using this you can
define problems as a subclass of Problem (see AddTwoNumbers class)
define problems as functions (see subtract_numbers function) and use the problem_decorator to convert them to FunctionProblems (subclass of Problem).
from abc import ABC, abstractmethod
from functools import wraps
import random
from typing import Callable, List, Tuple
random.seed(0) # for reproducability
class Problem(ABC):
#property
#abstractmethod
def text(self) -> str:
pass
#property
#abstractmethod
def solution_text(self) -> str:
pass
class AddTwoNumbers(Problem):
def __init__(self) -> None:
self._a = random.randint(0, 100)
self._b = random.randint(0, 100)
#property
def text(self) -> str:
return f'{self._a} + {self._b}'
#property
def solution_text(self) -> str:
return str(self._a + self._b)
# If you want to define functions as problems you can do something like that
class FunctionProblem(Problem):
def __init__(self, func: Callable[[], Tuple[str, str]]):
self._text, self._solution_text = func()
#property
def text(self) -> str:
return self._text
#property
def solution_text(self) -> str:
return self._solution_text
# Define a decorator so that functions become FunctionProblems
def problem_decorator(func: Callable[[], Tuple[str, str]]) -> Callable[[], FunctionProblem]:
#wraps(func)
def wrapper():
return FunctionProblem(func)
return wrapper
#problem_decorator
def subtract_numbers() -> Tuple[str, str]:
a, b = random.randint(0, 100), random.randint(0, 100)
text = f'{a} - {b}'
solution = str(a - b)
return text, solution
# If you really want to define a ProblemGenerator
class ProblemGenerator:
def __init__(self, *problems: Problem) -> None:
self.problems = list(problems)
def add_problem(self, problem: Problem) -> None:
self.problems.append(problem)
def create_worksheet(self) -> List[Tuple[str, str]]:
for problem in self.problems:
print(f'Problem text is {problem.text!r}, Solution is {problem.solution_text!r}')
generator = ProblemGenerator(AddTwoNumbers())
generator.add_problem(subtract_numbers())
generator.create_worksheet()
prints
Problem text is '49 + 97', Solution is '146'
Problem text is '53 - 5', Solution is '48'
As I already said in a comment, the classic object-oriented programming (OOP) way to do handle such a scenario is to define an abstract base class with the overall interface and perhaps some generic helper methods and then define problem-specific "concrete" subclasses. Here's a toy example of doing something like that on Python it with based on the code in your question. If it seems like you're creating a bunch of almost-the-same classes. Sometimes the remedy for that is to subclass your subclasses, but often it just means you haven't abstracted the problem well…
From the comments in your sample code, it sounds like you also want have your classes be responsible for creating their own tkinter objects for a graphical user interface (GUI). Generally speaking that's probably not a good idea — classes should generally only have a single responsibility, which mean trying pile many of them on can greatly complicate matters by make testing, debugging, and extending what you have a lot more difficult.
There's a commonly used software design pattern called model–view–controller (MVC) that is commonly used for developing user interfaces that a program up into three interconnected components, but keeps each one separate to reduce complexity — so I suggest you invest a the time studying it.
import abc
import random
class Problem(metaclass=abc.ABCMeta):
""" Abstract base class of all mathematical problems. """
def __init__(self):
self.lines = []
#abc.abstractmethod
def create_problem(self):
""" Creates input to be fed to the create_worksheet method. """
...
def create_worksheet(self, indent=4, char=' '):
""" Creates the latex-document by writing the output in a LaTeX input file.
In this toy example, it just print the lines the create_problem() method
generated.
"""
padding = indent * char
for line in self.lines:
print(padding + line)
class AddNumbers(Problem):
def __init__(self):
super().__init__() # Initialize base class.
self.create_problem() # Create subclass-specific data.
def create_problem(self):
a, b = random.randint(0, 100), random.randint(0, 100)
self.lines.append(f'Problem: Add the two numbers {a} and {b} together')
self.lines.append(f'Solution: {a+b}')
if __name__ == '__main__':
# Create some sample Problem subclass instances.
problems = [AddNumbers(), AddNumbers()]
# Create worksheet the worksheet for each one.
for i, problem in enumerate(problems, start=1):
print(f'Worksheet {i}:')
problem.create_worksheet(indent=2)

Reset class and class variables for each test in Python via pytest

I created a class to make my life easier while doing some integration tests involving workers and their contracts. The code looks like this:
class ContractID(str):
contract_counter = 0
contract_list = list()
def __new__(cls):
cls.contract_counter += 1
new_entry = super().__new__(cls, f'Some_internal_name-{cls.contract_counter:10d}')
cls.contract_list.append(new_entry)
return new_entry
#classmethod
def get_contract_no(cls, worker_number):
return cls.contract_list[worker_number-1] # -1 so WORKER1 has contract #1 and not #0 etc.
When I'm unit-testing the class, I'm using the following code:
from test_helpers import ContractID
#pytest.fixture
def get_contract_numbers():
test_string_1 = ContractID()
test_string_2 = ContractID()
test_string_3 = ContractID()
return test_string_1, test_string_2, test_string_3
def test_contract_id(get_contract_numbers):
assert get_contract_ids[0] == 'Some_internal_name-0000000001'
assert get_contract_ids[1] == 'Some_internal_name-0000000002'
assert get_contract_ids[2] == 'Some_internal_name-0000000003'
def test_contract_id_get_contract_no(get_contract_numbers):
assert ContractID.get_contract_no(1) == 'Some_internal_name-0000000001'
assert ContractID.get_contract_no(2) == 'Some_internal_name-0000000002'
assert ContractID.get_contract_no(3) == 'Some_internal_name-0000000003'
with pytest.raises(IndexError) as py_e:
ContractID.get_contract_no(4)
assert py_e.type == IndexError
However, when I try to run these tests, the second one (test_contract_id_get_contract_no) fails, because it does not raise the error as there are more than three values. Furthermore, when I try to run all my tests in my folder test/, it fails even the first test (test_contract_id), which is probably because I'm trying to use this function in other tests that run before this test.
After reading this book, my understanding of fixtures was that it provides objects as if they were never called before, which is obviously not the case here. Is there a way how to tell the tests to use the class as if it hasn't been used before anywhere else?
If I understand that correctly, you want to run the fixture as setup code, so that your class has exactly 3 instances. If the fixture is function-scoped (the default) it is indeed run before each test, which will each time create 3 new instances for your class. If you want to reset your class after the test, you have to do this yourself - there is no way pytest can guess what you want to do here.
So, a working solution would be something like this:
#pytest.fixture(autouse=True)
def get_contract_numbers():
test_string_1 = ContractID()
test_string_2 = ContractID()
test_string_3 = ContractID()
yield
ContractID.contract_counter = 0
ContractID.contract_list.clear()
def test_contract_id():
...
Note that I did not yield the test strings, as you don't need them in the shown tests - if you need them, you can yield them, of course. I also added autouse=True, which makes sense if you need this for all tests, so you don't have to reference the fixture in each test.
Another possibility would be to use a session-scoped fixture. In this case the setup would be done only once. If that is what you need, you can use this instead:
#pytest.fixture(autouse=True, scope="session")
def get_contract_numbers():
test_string_1 = ContractID()
test_string_2 = ContractID()
test_string_3 = ContractID()
yield

Looking for a short way to generate subclass (that would be created into a new file), with parent class' methods included in it (Python)

Will use following example to explain.
Existing python file (a.py) contains one class:
class A:
def method1(self, par1, par2='e'):
# some code here
pass
def method2(self, parA):
# some code here
pass
def method3(self, a, b, c):
# lots of code here
pass
def anothermethod(self):
pass
if __name__ == '__main__':
A().anothermethod()
Now, there is a need to create another py file (b.py), which would contain subclass (class B) of class A.
And there is a need to have all the methods included (all inherited from parent class), but without
implementation in it. Result might look like:
class B(A):
def method1(self, par1, par2='e'):
# empty here; ready to override
pass
def method2(self, parA):
# empty here; ready to override
pass
def method3(self, a, b, c):
# empty here; ready to override
pass
def anothermethod(self):
# empty here; ready to override
pass
if __name__ == '__main__':
B().anothermethod()
Having described the example, the question is: how could one generate last mentioned (skeleton-like) py file? So that after generating you can just open generated file and start right away with filling specific implementation.
There must be a shorter way, 1-2 line solution. Maybe it is already solvable by some existing functionality within modules already provided by Python (Python 3)?
Edit (2018 Mar 14). Thank you https://stackoverflow.com/a/49152537/4958287 (though was looking for short and already existing solution here). Will have to settle with longer solution for now -- will include its rough version here, maybe it would be helpful to someone else:
import inspect
from a import A
def construct_skeleton_subclass_from_parent(subcl_name, parent_cl_obj):
"""
subcl_name : str
Name for subclass and
file to be generated.
parent_cl_obj : obj (of any class to create subclass for)
Object of parent class.
"""
lines = []
subcl_name = subcl_name.capitalize()
parent_cl_module_name = parent_cl_obj.__class__.__module__
parent_cl_name = parent_cl_obj.__class__.__name__
lines.append('from {} import {}'.format(parent_cl_module_name, parent_cl_name))
lines.append('')
lines.append('class {}({}):'.format(subcl_name, parent_cl_name))
for name, method in inspect.getmembers(parent_cl_obj, predicate=inspect.ismethod):
args = inspect.signature(method)
args_others = str(args).strip('()').strip()
if len(args_others) == 0:
lines.append(' def {}(self):'.format(name))
else:
lines.append(' def {}(self, {}):'.format(name, str(args).strip('()')))
lines.append(' pass')
lines.append('')
#...
#lines.append('if __name__ == \'__main__\':')
#lines.append(' ' + subcl_name + '().anothermethod()')
#...
with open(subcl_name.lower() + '.py', 'w') as f:
for c in lines:
f.write(c + '\n')
a_obj = A()
construct_skeleton_subclass_from_parent('B', a_obj)
Get the list of methods and each of their signatures using the inspect module:
import a
import inspect
for name, method in inspect.getmembers(a.A, predicate=inspect.ismethod):
args = inspect.signature(method)
print(" def {}({}):".format(name, args))
print(" pass")
print()

Def return in class as second, third argument for Class

I am creating a class to make some calculations. The class would have 3 arguments to get started. I have done like this in a simplified representation:
class TheCalcs:
def __init__(self, pk_from_db, cat_score_list, final_score):
self.pk_from_db = pk_from_db
self.cat_score_list = cat_score_list
self.final_score = final_score
def calculate_cat_score(self):
#Do some calcs with the data of the pk_from_db and return that!
a_list_of_scores = [] # create a list of scores
return a_list_of_scores
def final_score(self): # The argument for this function would be the return of the calculate_cat_score function!
# Again do some calcs and return the final score
the_final_score = int()
return the_final_score
def score_grade(self): # the argument this this function again the return but now from the final_score function
# Do some cals and return the grade
the_grade = ("a string", "an integer")
return the_grade
When I call the class I would have to present the arguments --> However as you can see I just do now the value of the first argument. The second and the third being calculated throughout the class. When I call the class just with one argument I will of course have an error of failing arguments. Anyone has an idea on that?
If those values are calculated, simply don't make them arguments. You could instead call those calculation methods to compute the values:
class TheCalcs:
def __init__(self, pk_from_db):
self.pk_from_db = pk_from_db
self.cat_score_list = self.calculate_cat_score()
self.final_score = self.calculate_final_score()
# ...
or postpone calculations until you need them.

Categories

Resources