Pytest - how to stub member variable - python

I apologise - this is my first attempt at using pytest or any python testing library, but I have done a small amount of JUnit so am vaguely familiar with the principles.
Basically, the class I want to test has a couple of member variables that I want to stub. Specifically, I only need some customer details. I access this under the OrderController class in its class variable 'orders' (a list of dictionaries with purchase id as key and order objects as values). When I get this order object I would like to access the customer attribute which is comprised of their name and address - this address attribute is another member variable.
Below is the address_label.py module (I'm sorry about the comments - it is for University)
"""
--------------------------------------------------------------------------------------------
title : address_label.py
description : Formats order data for creation of address labels in the pdf.py module.
python_version : 3.7.9
--------------------------------------------------------------------------------------------
"""
from .order_controller import OrderController
from .pdf import Pdf
class AddressLabel:
"""
A class for formatting data to be input into address label pdf.
...
Attributes
----------
order_controller : OrderController
member variable to access order instances
pdf : Pdf
member variable to invoke writing of pdf
Methods
-------
create_address_label(orders_selected):
Create strings to be output in pdf
"""
def __init__(self):
"""
Constructs all the necessary attributes for the AddressLabel object.
"""
self._order_controller = OrderController(None)
self._pdf = Pdf()
def create_address_label(self, orders_selected):
"""
For each of the orders selected with checkboxes will find the data for that order
and format is suitable for the pdf module.
Parameters:
orders_selected: an array of the row data from each row checked with a checkbox
(each item is a string).
"""
for index, order in enumerate(orders_selected):
order_id = int(order[0]) - 1
order_obj = self._order_controller.orders['order_' + order[0]]
address = [order_obj.customer.first_name + ' ' + order_obj.customer.last_name,
order_obj.customer.address.line_one, order_obj.customer.address.city]
self._pdf.write_address_label(address, order_id, index)
return address, order_id, index
This is what I have so far for test_address_label.py, but I notice that it is still contacting the main OrderController class and therefore failing - how can I stop this?
import pytest
from main.business_logic.address_label import AddressLabel
class Address:
def __init__(self, line_one, line_two, city):
self.line_one = line_one
self.line_two = line_two
self.city = city
class Customer:
def __init__(self, address, first_name, last_name):
self.address = address
self.first_name = first_name
self.last_name = last_name
class Order:
def __init__(self, customer):
self.customer = customer
class OrderController:
orders = {
'order_1': Order(customer=setup_customer())
}
def __init__(self, x):
pass
#staticmethod
def setup_customer():
def setup_address():
return Address(line_one='Test Line One',
line_two='Test Line Two', city='Test City')
address = setup_address()
return Customer(address=address, first_name='Test First Name', last_name='Test Last Name')
#pytest.fixture
def _order_controller():
return OrderController()
def test_address_label(_order_controller):
address_label = AddressLabel()
orders_selected = [['1', 'Test Name', '2021-03-12', 'Status', '£200']]
scenario_one_address = ['Test First Name Test Last Name', 'Test Line One', 'Test City']
address_label_contents = address_label.create_address_label(
orders_selected)
assert address_label_contents == (scenario_one_address, 1, 0)
In any case, if anyone had any good resources to learn this from that'd be great - I've read a lot of tutorials but they all use such elementary examples that don't apply to a lot of my use cases...
Thank you in advance!

Related

Create a method that lists instances that were set to True in a previous class

I have the following code that records job candidates' personal details in a class Employee:
class Employee:
def __init__(self, name, role, id):
self.name = name
self.role = role
self.id = id
self.interviewed = False
def __str__(self):
text = f'Candidate {self.name}; {self.id}. '
if self.interviewed == False:
return text + 'Not interviewed yet.'
else:
return text + 'Interviewed.'
def interview(self):
self.interviewed = True
I also have another class Database that lists all the candidates in a database for a particular employer:
class Database:
def __init__(self, company, employer):
self.company = company
self.employer = employer
self.candidates = []
def __str__(self):
text = f'{The hiring company is {self.company} and the employers name is {self.employer}'
return text
def add_candidate(self, candidate):
self.candidates.append(candidate)
Now, if we record personal details of two candidates in the class Employee and add them to the class Database using the method add_candidate, how do I create a new method called list_interviewed_candidates(self) in the class Database that will print all candidates that have self.interviewed set to True?
This is what I tried:
class Database:
def __init__(self, company, employer):
self.company = company
self.employer = employer
self.candidates = []
def __str__(self):
text = f'{The hiring company is {self.company} and the employers name is {self.employer}'
return text
def add_candidate(self, candidate):
self.candidates.append(candidate)
def list_interviewed_candidates(self):
for employee in self.candidates:
if employee.interviewed == True:
return employee
But that doesn't work. I have also tried list comprehension but it seems I just cannot access the boolean value that was set in the first class. Ideally, the output should look something like this:
database1 = Database('Google', 'Jack H')
print(database1)
'The hiring company is Google and the employers name is Jack H'
candidate1 = Employee('Anna S', 'web-designer', 12)
database1.add_candidate(candidate1)
print(database1.list_interviewed_candidates())
[]
candidate1.interview()
print(database1.list_interviewed_candidates())
['Candidate Ana S; 12 - Interviewed']
In your Database.list_interviewed_candidates method you are returning the first employee that was interviewed. Keep in mind that return exits from the current function (method in this case) as soon as is hit.
So it starts looking at your candidates and as soon as one interviewed is found, it returns that one.
You probably want to gather them all in a list and return that:
def list_interviewed_candidates(self):
retval = []
for employee in self.candidates:
if employee.interviewed == True:
retval.append(employee)
return retval
Something pretty interesting you could also use is yield:
def list_interviewed_candidates(self):
for employee in self.candidates:
if employee.interviewed == True:
yield employee
Which... you can try by doing:
print(list(database1.list_interviewed_candidates()))
Pretty cool. This opens the really fun world of iterators!
A list comprehension works, but realize __str__ is used by print but __repr__ is used for items displayed in a list. __repr__ is also used if __str__ isn't defined.
Try the following, but change __str__ to __repr__ in Employee:
def list_interviewed_candidates(self):
return [employee for employee in self.candidates if employee.interviewed]
Then:
database1 = Database('Google', 'Jack H')
print(database1)
candidate1 = Employee('Anna S', 'web-designer', 12)
candidate2 = Employee('Mark T', 'pythonista', 13)
database1.add_candidate(candidate1)
database1.add_candidate(candidate2)
print(database1.list_interviewed_candidates())
candidate1.interview()
candidate2.interview()
print(database1.list_interviewed_candidates())
Outputs:
The hiring company is Google and the employers name is Jack H
[]
[Candidate Anna S; 12. Interviewed., Candidate Mark T; 13. Interviewed.]
Customize __str__ and __repr__ individually if you want differing output between direct print of Employee and how it is displayed in a list.

Python create instance of class using string manipulation

I wrote Class and created two lists. The first for the users and the second for the user-attributes.
I would now like to loop trough the two lists in order to create multiple Class instances with the respective data.
class Employee:
def __init__(self, first, last, pay):
self.first = first
self.last = last
self.pay = pay
self.email = first + '.' + last + '#company.com'
emp_1 = Employee('Corey', 'Schafer', '50000')
emp_2 = Employee('Test', 'User', '60000')
by printing print(amp_1.email) i can create the instance of the class:
will print
Corey.Schafer#company.com
Now i dont want to write it out manually so i want to loop trough it:
for user in users:
for user_atr in user_atrs:
print(user + '.' + user_atr)
will print:
empy_1.first
empy_1.last
empy_1.pay
empy_1.email
empy_2.first
empy_2.last
empy_2.pay
empy_2.email
Instead of:
Corey
Schafer
50000
Corey.Schafer#email.com
Test
User
60000
Test.User#email.com
How can i use that loop to actually create the instance of the class and not just the blueprint?
Basic Solution
a list of string of the users : ['emp_1', 'emp_2']
a list of string that are attributs name ['first', 'last', 'pay', 'email']
Then use the builtin method globals() to get the variable and getattr(obj, name, default) but that isn't nice and requires to type variable names
emp_1 = Employee('Corey', 'Schafer', 50000)
emp_2 = Employee('Test', 'User', '60000')
for user in ['emp_1', 'emp_2']:
for user_atr in ['first', 'last', 'pay', 'email']:
print(getattr(globals()[user], user_atr))
Better Solution
a list of Employee instances : [emp_1, emp_2]
Access object properties with __dict__ (key is name, value are property's value)
for user in [emp_1, emp_2]:
for user_atr in user.__dict__.values():
print(user_atr)
Corey
Schafer
50000
Corey.Schafer#company.com
...
To read both name and value at the same time
for user in [emp_1, emp_2]:
for user_atr in user.__dict__.items():
print(user_atr)
('first', 'Corey')
('last', 'Schafer')
('pay', 50000)
('email', 'Corey.Schafer#company.com')
...
class Employee:
def __init__(self, first, last, pay):
self.first = first
self.last = last
self.pay = pay
self.email = first + '.' + last + '#company.com'
emp_1 = Employee('Corey', 'Schafer', '50000')
emp_2 = Employee('Test', 'User', '60000')
users = {"emp_1": emp_1,
"emp_2": emp_2}
for user in users.values():
for attribute in list(user.__dict__.keys()):
print(f"{name}.{attribute}")
This will do that for you by storing the users in a dictionary with the name of the user as the dictionary key and then using the __dict__ attribute from the Employee class to get a dictionary of all atributes and then printing the name of each attribute from that dictionary.
If you are trying to print the value of each of these attributes then it can be changed to the following:
users = {"emp_1": emp_1,
"emp_2": emp_2}
for name, user in users.items():
for attribute in list(user.__dict__.keys()):
print(getattr(user,attribute))
Using this method means that any more attributes added to the employee class will be printed also.

PonyORM retrieve object from class Entity problem

Let's say I have these two classes:
class TeamMember(db.Entity):
member_id= PrimaryKey(int, auto=True)
name = Required(str)
team = Required('Team')
class Team(db.Entity):
team_id= PrimaryKey(int, auto=True)
name = Required(str)
team_members = Set(TeamMember)
I want to select all TeamMembers that are in specific team (ex. team_id==1). Query would look something like this (C1):
TeamMember.select(lambda member: member.team == 1)[:]
If I write it like that, I'm getting error below:
Incomparable types 'Team' and 'int' in expression: member.team == 1
On the other hand, I can write this and it will work (C2):
TeamMember.select(lambda member: member.team == Team[1])[:]
But, I don't wan't to write it like it, because I want to create generic function that will work for every Entity class:
def get_instances_from_db(classname, classname_var, var_value):
"""
:param classname: name of class
:param classname_var: name of class variable to search by
:param var_value: value of class variable
:return:
"""
return classname.select(lambda v: getattr(v, classname_var) == var_value)[:]
Above method will work for variable that's isn't relating to other class Entity like:
members = get_instances_from_db(TeamMember, "name", "some_team_member_name")
Finally, my question is: Is it possible to set query to search by integer, and not by Entity object. Or, is there way to use line 'C1'?
Hope I'm clear enough! :)

Printing classes in Python with multiple values

I have spent a good while searching through this website so I hope this question hasn't been asked before - apologises if it has. I'm learning classes for the first time and I'm making a class with multiple users (could be 50+ but for now, I just have 2 in my example). What I'm trying to do is have certain information about users/employees and be able to print them all in one go... in a way that isn't a complete eyesore! This is what I have attempted:
class User:
def __init__(self, user_id, first, last, address):
self.user_id = user_id
self.first = first
self.last = last
self.address = address
self.email = first + '.' + last + '#python.com'
def all_users(self):
print()
print('User ID: {} First Name: {} {} {} {}'.format(self.user_id, self.first, self.last, self.address, self.email))
print()
user_1 = User(123, 'Kim', 'N', 'London')
user_2 = User(312, 'Chris', 'E', 'Japan')
print(all_users(User))
This is the error message that I am receiving:
print('User ID: {} First Name: {} {} {} {}'.format(self.user_id, self.first, self.last, self.address, self.email))
AttributeError: type object 'User' has no attribute 'user_id'
Thanks in advance for any help or guidance.
Sounds like you want the User class to contain a list of all users.
This is called a class variable because it is attached to the class itself, instead of being attached to a particular instance of the class.
Example code:
class User(object):
users = []
def __init__(self, first, last):
self.first = first
self.last = last
# now that this instance is fully initialized, add it
# to the master list of users
User.users.append(self)
def __str__(self):
return '{} {}'.format(self.first, self.last)
#staticmethod
def all_users():
for user in User.users:
print (user)
user_1 = User('Kim', 'Smith')
user_2 = User('Chris', 'Jones')
User.all_users()
You should probably implement the __str__ or __repr__ special methods, which are designed to print human readable and "official" representations of the class instances, respectively
class User():
...
def __str__(self):
attrs = ['{}={}'.format(k, repr(v)) for k, v in self.__dict__.items()]
return '{}({})'.format(self.__class__.__name__, ', '.join(attrs))
Then it would look like this
>>> user = User('123', 'John', 'Doe', 'USA')
>>> print(user)
User(user_id='123', first='John', last='Doe', address='USA', email='John.Doe#python.com')

How to create object from another object in Python unit test

I'm having trouble understanding unit testing in Python. I have an object, retailer, which creates another object, deal. deal refers to an attribute created in retailer, so I'm passing it a reference:
class deal():
def __init__(self, deal_container, parent):
The deal_container attribute also comes from retailer, which calls its own methods to create it. So how do I create everything I need to easily make a deal object?
Do I have to create an instance of retailer in my unit test and then call the method in that object that creates deal?
Can I use FactoryBoy to create an instance of retailer and how do I include the method that creates deal in that object?
What's the best way to approach this?
Here's the unit test. I'm setting up the soup_obj I need to give deal:
class TestExtractString(TestCase):
fixtures = ['deals_test_data.json']
def setUp(self):
with open('/home/danny/PycharmProjects/askarby/deals/tests/BestBuyTest.html', 'r') as myfile:
text = myfile.read().replace('\n', '')
self.soup_obj = bs4.BeautifulSoup(text,"html.parser")
self.deal = self.soup_obj.find_all('div',attrs={'class':'list-item'})[0]
def test_extracts_title(self):
z = Retailer.objects.get(pk=1)
s = dealscan.retailer(z)
d = dealscan.deal(self.deal,s)
result = d.extract_string(self.deal,'title')
and here's the relevant bit of the deal class in dealscan. There's a retailer class that creates a deal, but I haven't even written the bit in retailer that creates deal yet. I'm hoping I can mock the bits I need for deal without having to invoke retailer at all, but then how do I deal with the fact that deal references retailer?
class deal():
def __init__(self, deal_container, parent):
'''
Initializes deal object
Precondition: 0 > price
Precondition: 0 > old_price
Precondition: len(currency) = 3
:param deal_container: obj
'''
self.css = self.parent.css
self.deal_container = deal_container
self.parent = parent
self.title = self.extract_string('title')
self.currency = self.parent.currency
self.price = self.extract_price('price')
self.old_price = self.extract_price('old_price')
self.brand = self.extract_string('brand')
self.image = self.extract_image('image')
self.description = self.extract_string('description')
#define amazon category as clearance_url
#define all marketplace deals
def __str__(self):
return self.title
def extract_string(self, element, deal):
'''
:param object deal: deal object to extract title from
:param string element: element to look for in CSS
:return string result: result of string extract from CSS
'''
tag = self.css[element]['tag']
attr = self.css[element]['attr']
name = self.css[element]['name']
result = deal.find(tag, attrs={attr: name})
if result:
if element == 'title':
return result.text
elif element == 'price':
result = self.extract_price(result).text
if result:
return result
elif element == 'image':
result = self.extract_image(result)
return False
The problem is that the deal object is referencing the parent before it sets the self.parent attribute. Use:
self.parent = parent
self.css = self.parent.css
self.deal_container = deal_container
and the AttributeError goes away.
As for the question about whether it's good form to use an object to create another object in a unit test, the answer is that you can use mocks, but it's fine to do it this way. Using a helper method to set up the parent object once in setUp is acceptable and will make the code easier to read, and may improve test performance a little.

Categories