Testing recursive class methods using unit testing python - python-3.x

class Login:
def admin_login(self):
user_name = input('Enter UserName: -')
password = input('please enter password:- ')
try:
with open(r'/home/Downloads/amis/config.yaml') as file:
credentials = yaml.load(file)
if credentials['userid'] == user_name and credentials['password'] == password:
admin_op = AdminOps(credentials)
admin_op.admin_ops()
else:
print("please enter correct login creds")
self.admin_login()
except:
print('err')
self.admin_login()
I am testing this class method and i am trying to mock the method then it is not even going into this method for testing. Can anyone help me writing unit test for this class method.
I tried this for the wrong credentials.
#mock.patch('users.logins.input', create=True)
def test_admin_login_fail(self,ext, inp1):
inp1.side_effect = ['qqq', 'qqq']
login_cls = Login()
login_cls.admin_login()
with mock.patch('file path') as mock_login:
assert mock_login.called

reproducible problem
Admittedly I don't know much about pytest but the problems I see are generic ones and not linked to use of a specific language or library. I'm going to simplify the problem a little bit. Hopefully it can show us a path forward -
def login(creds = {}):
u = input("username:")
p = input("password:")
if u in creds and creds[u] == p:
print(f"{u} logged in")
else:
print(f"invalid login. please retry...")
login(creds)
login({"ripley": "password1"})
I run the program -
username:foo
password:bar
invalid login. please retry...
username:ripley
password:password1
ripley logged in
how to test?
Okay, it works but I still have a testing problem. When an invalid login is provided, login loops endlessly. However, this is not a testing problem, it's a problem with how we wrote our program. The program has a problem of mixing concerns. We already separated one concern by passing credentials as an argument to the login function. Already, if there's a read or yaml.parse error, we don't have to deal with those inside of login. But there's additional concern of input and print side effects...
def login(creds = {}, u = "", p = ""):
return u in creds and creds[u] == p
creds = {"ripley": "password1"}
print(login(creds, "foo", "bar")) # False
print(login(creds, "ripley", "password1")) # True
Now testing is basic login functionality is easy -
def test_login(self):
assert login({}, "foo", "bar") == False
assert login({"foo": "bar"}, "foo", "bar") == True
creds, user, and pass are all arguments to the login function so we can verify our function's behavior without worrying about read or yaml.parse
write functions with parameters
Okay, but what if we want to test login in a looping procedure? Again, we apply what we learned above and write functions that accept arguments -
class ui:
def __init__(self, creds, I, O): # <-- params: creds, input, output
self.creds = creds
self.I = I
self.O = O
self.user = None
def login(self, exit = None): # <-- param: exit handler
u = self.I("user:")
p = self.I("pass:")
if (login(self.creds, u, p)):
self.O(f"{u} logged in")
self.user = u
elif exit is None:
self.O("invalid login")
self.login()
else: exit()
In production, we might run our program like this -
creds = {"ripley": "password1"}
app = ui(creds, input, print) # <-- args: creds, input, output
app.login()
In testing, we could write test_ui_login_failure like this -
def test_ui_login_failure():
def I (prompt): # input
if prompt == "user:":
return "foo"
elif prompt == "pass:":
return "bar"
def O (s): # output
assert s == "invalid login"
app = ui({}, I, O) # <-- args: empty creds, input, and output
app.login(lambda: None) # <-- args: exit handler
Writing test_ui_login_success might look like this -
def test_ui_login_success():
def I (prompt):
if prompt == "user:":
return "foo"
elif prompt == "pass:":
return "bar"
def O (s):
assert s == "foo logged in"
app = ui({"foo": "bar"}, I, O)
app.login(lambda: None)
higher-order control
Adding the exit parameter to ui.login gives us the control we need in the testing environment and it let's us implement a more sophisticated login routine, if needed -
def retry (n = 0, f = lambda _: None):
if n <= 1:
return lambda: None
else:
return lambda: f(retry(n - 1, f))
class ui:
def __init__(self, creds, I, O):
# ...
def login(self, exit = None):
# ...
if (login(self.creds, u, p)):
# ...
elif exit is None:
self.O("invalid login")
self.login(retry(2, self.login)) # <-- retry login twice more
else:
self.O("invalid login")
exit()
We can run ui.login same as last time -
creds = {"ripley": "password1"}
app = ui(creds, input, print)
app.login()
The loop stops after 3 failed attempts -
user:foo
pass:bar
invalid login
user:spaghet
pass:meatbal
invalid login
user:ada
pass:lovelace
invalid login
Our previous tests test_ui_login_failure and test_ui_login_success still pass. Of course you could write a test to verify the retry behaviour as well. This exercise is left for the reader.
remarks
Hopefully these techniques help show that subtle differences in how we write a program can results in dramatic differences in how we test it.

Related

My instance variable ID is changing each time I print the instance. The if statement I've written doesn't fix it. Code included

I'm trying to use UUID4 to create an ID for each instance of a class, User, however each time I print the instance a new UUID is generated. I'd thought that the if statement would mean it's only generated once but this isn't the case. Any help/guidance will be much appreciated, thanks.
This is my first time posting so please let me know if i can improve the post or add more info.
class User:
def __init__(self) -> None:
self.user_id = None
self.auth_key = None
if self.user_id == None: self.user_id = uuid4()
def __repr__(self) -> str:
return f"""User ID: {self.user_id}
Authorisation Key: {self.auth_key}"""
new_user = User()
print(new_user)
I've just tried your code without problems. You probably are executing your script many times over and getting different ID's because of that. Each time you run it, new instances will be created by your current Python session and those are totally independent from each other.
from uuid import uuid4
class User:
def __init__(self) -> None:
self.user_id = uuid4()
self.auth_key = None
def __repr__(self) -> str:
return f"User ID: {self.user_id}\nAuthorization Key: {self.auth_key}"
def __eq__(self, other):
return self.user_id == other.user_id
new_user = User()
# Same instance on same session:
print(new_user)
print(new_user)
print(new_user)
# Different instance on same session:
another_user = User()
# Comparing them:
print(f"new_user: {new_user.user_id}")
print(f"another_user: {another_user.user_id}")
# Identity test:
print(f"new_user is new_user: {new_user is new_user}")
print(f"new_user is another_user: {new_user is another_user}")

Can't call a variable from another file

I have two files. The first file, we'll call it "a.py". The second, "b.py".
Here's an example of "a.py":
#!/usr/bin/env python
# coding=utf-8
CHOOSE = input ('''
\033[1;35m choose 1 or 2:\033[0m
1)tom
2)jack
''')
a = 666
b = "bbb"
def f():
print("this is a test")
return "function"
if __name__ == '__main__':
if CHOOSE == '1':
username = 'tom'
print(username)
elif CHOOSE == '2':
username = 'jack'
print(username)
else:
print('wrong choice,scipt is exit...')
Here's an example of "b.py":
#!/usr/bin/env python
# coding=utf-8
import a
from a import b,f,CHOOSE,username
a = a.a
f()
print(b,a,CHOOSE,username)
when i run python b.py,system feedback error:
wherem am i wrong?how to fix it?
Because this snippet:
if __name__ == '__main__':
if CHOOSE == '1':
username = 'tom'
print(username)
elif CHOOSE == '2':
username = 'jack'
print(username)
else:
print('wrong choice,scipt is exit...')
Will get executed only if the a.py run as the main python file not imported from other module, so username will not be defined so you can not import it. Here how to fix it:
a.py:
...
def foo(CHOOSE):
if CHOOSE == '1':
username = 'tom'
elif CHOOSE == '2':
username = 'jack'
else:
username = None
return username
b.py:
from a import foo
CHOOSE = input ('''
\033[1;35m choose 1 or 2:\033[0m
1)tom
2)jack
''')
username = foo(CHOOSE)
if username:
print(username)
else:
print('Wrong choice')
Explanation: First you need to extract the code that calculate the name into something reusable. Function are meant for code reusability so I defined one which take one parameter and it return the corresponding value. This function is then used (imported then invoked) in b.py.
Usually if __name__ == '__main__': is placed in the module that you consider your entry-point, so if you want to use it maybe b.py is a better place.
The block if __name__ == '__main__' only triggers if the script is run with a.py being the main module (e.g. you ran it using the command python a.py). If anything else was used as the main module, and a was imported, then this condition is not met and the code inside of it does not run.
The variable username is created and added to a's namespace in the if __name__ == '__main__' block. When you do python b.py, the code inside this block does not execute, and as a result username never gets added to the namespace. So when you try to import it, immediately after loading the file a, you get an error saying that the 'username' doesn't exist - it was never created.

How can I create a continuous / infinite CLI with Click?

I'm trying to use Click to create a CLI for my Python 3 app. Basically I need the app to run continuously, waiting for user commands and executing them, and quitting if a specific command (say, "q") is entered. Couldn't find an example in Click docs or elsewhere.
An example of interactive shell would be like this:
myapp.py
> PLEASE ENTER LOGIN:
mylogin
> PLEASE ENTER PASSWORD:
mypwd
> ENTER COMMAND:
a
> Wrong command!
> USAGE: COMMAND [q|s|t|w|f] OPTIONS ARGUMENTS
> ENTER COMMAND:
f
> (output of "f" command...)
> ENTER COMMAND:
q
> QUITTING APP...
I've tried like so:
import click
quitapp = False # global flag
#click.group()
def cli():
pass
#cli.command(name='c')
#click.argument('username')
def command1(uname):
pass # do smth
# other commands...
#cli.command(name='q')
def quitapp():
global quitapp
quitapp = True
def main():
while not quitapp:
cli()
if __name__ == '__main__':
main()
But the console just runs the app once all the same.
I've actually switched to fire and managed to make a shell-like continuous function like so:
COMMAND_PROMPT = '\nCOMMAND? [w to quit] >'
CAPTCHA_PROMPT = '\tEnter captcha text (see your browser) >'
BYE_MSG = 'QUITTING APP...'
WRONG_CMD_MSG = 'Wrong command! Type "h" for help.'
EMPTY_CMD_MSG = 'Empty command!'
class MyClass:
def __init__(self):
# dict associating one-letter commands to methods of this class
self.commands = {'r': self.reset, 'q': self.query, 'l': self.limits_next, 'L': self.limits_all,
'y': self.yandex_logo, 'v': self.view_params, 'h': self.showhelp, 'c': self.sample_captcha, 'w': None}
# help (usage) strings
self.usage = '\nUSAGE:\t[{}] [value1] [value2] [--param3=value3] [--param4=value4]'.format('|'.join(sorted(self.commands.keys())))
self.usage2 = '\t' + '\n\t'.join(['{}:{}'.format(fn, self.commands[fn].__doc__) for fn in self.commands if fn != 'w'])
def run(self):
"""
Provides a continuously running commandline shell.
The one-letter commands used are listed in the commands dict.
"""
entered = ''
while True:
try:
print(COMMAND_PROMPT, end='\t')
entered = str(input())
if not entered:
print(EMPTY_CMD_MSG)
continue
e = entered[0]
if e in self.commands:
if self.commands[e] is None:
print(BYE_MSG)
break
cmds = entered.split(' ')
# invoke Fire to process command & args
fire.Fire(self.commands[e], ' '.join(cmds[1:]) if len(cmds) > 1 else '-')
else:
print(WRONG_CMD_MSG)
self.showhelp()
continue
except KeyboardInterrupt:
print(BYE_MSG)
break
except Exception:
continue
# OTHER METHODS...
if __name__ == '__main__':
fire.Fire(MyClass)
Still, I'd appreciate if someone showed how to do that with click (which appears to me to be more feature-rich than fire).
I've finally found out other libraries for interactive shells in Python: cmd2 and prompt, which are way more advanced for REPL-like shells out of the box...
There's a quick example of how to do a continuous CLI application with Click here: python click module input for each function
It only has a way of running click commands on a loop, but you can put in any custom logic you want, either in commands or the main body of the loop. Hope it helps!
Here I found click in the loop but it is error prone when we try to use different commands with different options
!Caution: This is not a perfect solution
import click
import cmd
import sys
from click import BaseCommand, UsageError
class REPL(cmd.Cmd):
def __init__(self, ctx):
cmd.Cmd.__init__(self)
self.ctx = ctx
def default(self, line):
subcommand = line.split()[0]
args = line.split()[1:]
subcommand = cli.commands.get(subcommand)
if subcommand:
try:
subcommand.parse_args(self.ctx, args)
self.ctx.forward(subcommand)
except UsageError as e:
print(e.format_message())
else:
return cmd.Cmd.default(self, line)
#click.group(invoke_without_command=True)
#click.pass_context
def cli(ctx):
if ctx.invoked_subcommand is None:
repl = REPL(ctx)
repl.cmdloop()
# Both commands has --foo but if it had different options,
# it throws error after using other command
#cli.command()
#click.option('--foo', required=True)
def a(foo):
print("a")
print(foo)
return 'banana'
#cli.command()
#click.option('--foo', required=True)
def b(foo):
print("b")
print(foo)
# Throws c() got an unexpected keyword argument 'foo' after executing above commands
#cli.command()
#click.option('--bar', required=True)
def c(bar):
print("b")
print(bar)
if __name__ == "__main__":
cli()

How to make a global inside two functions

import getpass
from passlib.hash import sha256_crypt
def register():
username = str(input('username '))
password = str(getpass.getpass('password ',stream=None))
exec('global '+username)
exec(username+'=user('+"'"+username+"'"+','+"'"+password+"'"+')')
def hashPassword(password):
Passhash = sha256_crypt.hash(password)
return Passhash
def verifyPassword(password,hashpass):
return sha256_crypt.verify(password,hashpass)
class user(object):
users=[]
def __init__(self, username, password):
password = str(password)
if len(password) <= 20:
self.username = username
user.users.append(username)
self.password = hashPassword(password)
else:
print("No more than 20 characters in the password")
def login(username, passsword):
if username in user.users:
if verifyPassword(password,exec(username+'.password'))==True:
print('logged in.')
else:
print('wrong password')
else:
print('unknown user.')
I am trying to make a text based login/register system since I am fairly new to coding. For some reason something with the register() function doesn't correctly register a user because when I go to login verifypassword() it says
if verifyPassword(password,exec(username+'.password'))==True:
File "<string>", line 1, in <module>
NameError: name 'test' is not defined
>>>
if someone could tell me what is happening. I think it it something with global variables but I don't know how to fix it
global in exec doesn't work.
Use globals()[var_name] = var_value to set dynamic variable names in global scope.
exec is gererally a (very) bad idea if called with user-supplied input.
It also has (more or less) unexpected bahaviour in functions, see this example:
def f():
exec('a=3')
print(a)
>>> f()
Traceback [...]
NameError: name 'a' is not defined
(This has something to do with local scope being known at compile-time, see here or here)
Also, you might consider storing the actual user objects in user.users -- this prevents users picking names that you actually use inside your code and prevents unexpected behavior
Edit: Elaboration on the "local scope known at compile-time"
Since the compiler knows what local variables you are using, access is by the bytecode STORE_FAST and LOAD_FAST instructions, which store and load to and from a kind of array (you can look at local variable names via f.__code__.co_varnames), you can't just add stuff dynamically.
Why is this relevant for global?
Well, as said above, the STORE_FAST and LOAD_FAST instructions are used (you guessed it, for speed), the bytecode for following function will be:
>>> def f():
exec('global x')
x = 3
>>> dis.dis(f)
2 0 LOAD_GLOBAL 0 (exec)
2 LOAD_CONST 1 ('global x')
4 CALL_FUNCTION 1
6 POP_TOP
3 8 LOAD_CONST 2 (3)
10 STORE_FAST 0 (x)
12 LOAD_CONST 0 (None)
14 RETURN_VALUE
The first part deals with calling exec. The second part is the assignment. STORE_FAST assigns to a local variable x, no matter what exec just did.
This is also the reason creating new local variables in exec doesn't work: there just isn't space for them. Note this isn't valid for local variables set in exec but also "normally", they will have space assigned nevertheless.
In python, there is a dictionary of global-variables, that you can access by calling globals(). I'm not sure why exec(...) doesn't work, but I would consider a different solution than storing users as variables, named by username. But to solve your problem, keeping this design-choice, you can set the global user like this: globals()[username] = user(username, password) and when loggin in, do verifyPassword(password, globals()[username].password).
But since globals is really just a dictionary, it might be better practice to maintain a dictionary of users yourself. A user could potentially overwrite a global variable, by choosing a username that clashes with another global variable. You could then also eliminate the static users list on your user class. It could look like this, where repository is your dictionary with users:
import hashlib
import base64
import uuid
import getpass
from passlib.hash import sha256_crypt
repository = {}
def register():
username = str(input('username '))
password = str(getpass.getpass('password ',stream=None))
repository[username] = user(username, password)
def hashPassword(password):
Passhash = sha256_crypt.hash(password)
return Passhash
def verifyPassword(password,hashpass):
return sha256_crypt.verify(password,hashpass)
class user(object):
def __init__(self,username,password):
password = str(password)
if len(password) <= 20:
self.username = username
self.password = hashPassword(password)
else:
print("No more than 20 characters in the password")
def loginUser(username):
if username == 'exit':
start()
if username in repository:
if loginPass(username)==True:
print('success')
else:
print('passfail')
else:
print('incorrect login USERNAME NOT IN USER.USERS LIST')
def loginPass(username):
password = getpass.getpass('password ',stream=None)
if verifyPassword(password, repository[username].password) == True:
return True
else:
return False
def start():
while 1:
key1=input('login or register (l/r)')
if key1=='register':
del key1
register()
elif key1=='r':
del key1
register()
elif key1=='l':
del key1
loginUser(input('username or exit to got to l/r screen '))
else:
print('ERROR string not reconised among ifs')
start()

'SyntaxError: invalid syntax' in python 3 IDLE

Why is this a syntax error? And how do I fix it?
class Queue:
#Queue is basicliy a List:
def __init__(self):
self.queue = []
#add to the top of the list (the left side)
def Enqueue(self, num):
if isinstance(num, int):
self.queue.append(num)
#remove from the top of the list and return it to user
def Dequeue(self):
return self.queue.pop(0)
#this function gets inputs from user, and adds them to queue,
#until it gets 0.
def addToQueue(queue, num):
num = input()
while num != 0:
queue.Enqueue(num)
num = input()
The interactive mode (with the >>> prompt) only accepts one statement at a time. You've entered two to be processed at once.
After you've entered the class definition, be sure to add an extra blank line so that the interactive prompt knows you're done. Once you're prompted with a >>>, you'll know it is ready for the function definition.
The rest of your code looks fine. Happy computing :-)

Resources