How do I "reload" my config file after every test in python? - python

I have two tests in which I'm trying to import things from the same myconfig.py file. But I need some variables in myconfig.py to be changed after every test so the next can use a new one.
Is there any possible way to do it?
I already have function that re-writes info and it works correctly, but both tests import the very first version of myconfig.py.
I tried to use fixtures, but it didn't work. I also tried to run/exec myconfig.py before every launch, didn't help either.
I use two same small tests, run them as a package called test_package.
from myconfig import url, file_name, line_index
from test_package.functions_filee import upgrade_number
from test_package.pages.my_page import MyPageObject
from file_where_info_should be_rewritten import phone_number
def test_rewrite_number(self, browser):
my_page = MyPageObject(browser, url)
my_page.go_to_site()
my_page.input_phone_number(phone_number)
upgrade_number(file_name, line_index, phone_number)
upgrade_number function code:
def upgrade_number(file_name, line_index, phone_number):
with open(file_name, 'r+') as f:
lines = f.readlines()
new_number = str(int(phone_number) + 1)
new_str = f"phone_number = '{new_number}'\n"
lines[line_index] = new_str
f.seek(0)
f.writelines(lines)
File, where info should be rewritten consists of only a single line:
phone_number = "1111111111"
So basically I want the first test to import 1111111111 as a phone number, then increase it to 1111111112 so that the second test can use 1111111112 as its test data.
EDIT: I did it like this
import sys
import importlib
importlib.reload(sys.modules['dynamic_vars'])
from dynamic_vars import *
Eventually I gave up on this method of making new phone number for every test, but maybe it will be helpful for anyone

The classical pattern for this is called Test setup. In pytest you have setup_method which you can implement to reload your config and it will be done before each test is executed.
Similarly, you can use teardown_method to run code after each test. Your test can also set a class variable so each invocation of setup_method or teardown_method knows which value to use.
BUT
You should not do what you explain here. This makes tests dependent on each other which is a very problematic antipattern. Instead, make each test setup what it needs explicitly at the beginning. So, if a second test needs 1111111112 as its input data, specify this at the beginning of the test - by calling an utility function that will prepare the config appropriately.
This not only makes the tests independent - it also makes them more readable, as it's clear at the beginning of the test what the scenario is.

Related

Using Pytest to test a Python Program

TI am quite new to Python Programming and have a question on testing using Pytest. In a high-level, I have a program that takes 3 pieces of user input and generates a text file in the end. For my tests, I want to basically compare the files my program outputted, with what it should be.
Now, I am not sure how to go about testing. The program itself takes no arguments, but just relies on 3 pieces of user input, which I'll use monkeypatch to simulate. Do I create a new python file called program_test.py and have methods in here that call the original program? I have tried this, but I'm having trouble actually calling the original program and sending in the simulated inputs. Or, do I have tests in the original program (which doesn't make much sense to me).
I want something like this:
import my_program
def test_1():
inputs = iter(['input1', 'input2', 'input3'])
monkeypatch.setattr('builtins.input', lambda x: next(inputs))
my_program
# now do some assertion with some file comparison
# pseudocode
assert filecompare.cmp(expectedfile, actualfile)
This just seems to be running the original program and I think its to do with the import statement i.e. it is never running test_1(), probably because I never call it? Any help would be appreciated!
Without providing your my_program code it's hard to tell what's going on.
Since you are mentioning import problems, I guess your not defining main() and if __name__ == "__main__".
Here's a little example of how you can test that.
First, structure your my_program to have main function which contains the code and then add if __name__ == "__main__" which will allow you to run main function if the my_program is executed directly but also to import my_program as module to other files (without running it, for more information please see: What does if name == "main": do?).
my_program:
def main():
x = input()
y = input()
z = input()
with open("test", "w") as f_out:
f_out.write(f"{x}-{y}-{z}")
if __name__ == "__main__":
main()
Now you can create a test.py file and test the main function of my_program:
import os
import filecmp
import my_program
def test_success(monkeypatch):
inputs = ["input1", "input2", "input3"]
monkeypatch.setattr("builtins.input", lambda: next(iter(inputs)))
my_program.main()
with open("expected", "w") as f_out:
f_out.write("-".join(inputs))
assert filecmp.cmp("expected", "test")
os.remove("test")
os.remove("expected")
def test_fail(monkeypatch):
inputs = ["input1", "input2", "input3"]
monkeypatch.setattr("builtins.input", lambda: next(iter(inputs)))
my_program.main()
with open("expected", "w") as f_out:
f_out.write("something-else-test")
assert not filecmp.cmp("expected", "test")
os.remove("test")
os.remove("expected")
This is an example so I used os.remove to delete the files. Ideally you would define fixtures in your tests to use tempfile and generate random temporary files which will be automatically deleted after your tests.

Writing a unit test for multiple Python files

I'm trying to write a testing program to test many(identical) student assignments. I have a test written using the unittest library. The documentation seems to indicate that each test should be associated with one file. Instead, I'd like to have one test file and use command line arguments to point the test to the file it should test.
I know I can do this by using the argparse module in my unit tests, but is there a better way? It seems like this behavior should be supported in unittest, but I can't find anything in the documentation...
Create the Main test directory and add sub test packages. Have a test runner created for you pointing to the test directory. It could act as a suite. I have attached a piece of code that I have used for my test suite.
import os
import unittest
def main(test_path, test_pattern):
print(('Discovering tests in : {}'.format(test_path)))
suite = unittest.TestLoader().discover(test_path, test_pattern)
unittest.TextTestRunner(verbosity=2).run(suite)
if __name__ == '__main__':
root_path = os.path.abspath('.')
test_path = os.path.join(root_path, 'src/tests/')
test_pattern = 'test_*'
main(test_path, test_pattern)
Generally speaking, unittest is used to test module level python code, not interactions python code has with external programs. AFAIK, writing to stdout (ie. print) means you are either debugging or passing information to another program.
In your case, I don't think unittest is really necessary, unless you want to give assignments that are to "pass this unittest" (which is common in the wild).
Instead I would simply iterate over the directory that contains the assignments, check the stdout using subprocess, then write the results to a csv file:
import subprocess
import os
import csv
ASSIGNMENT_DIR = '/path/to/assignments'
expected_stdout = 'Hello World!'
def _determine_grade(stdout):
if stdout == expected_stdout:
return '100%'
return '0%'
grades = {}
for assignment in os.listdir(ASSIGNMENT_DIR):
filepath = os.path.join(ASSIGNMENT_DIR, assignment)
stdout = subprocesss.check_output(f'python3 {filepath}', shell=True)
grade = _determine_grade(stdout)
grades.append({'assignment':assignment, 'grade':grade})
with open('/path/to/grades.csv', 'w+') as f:
w = csv.DictWriter(f, ('assignment', 'grade'))
w.writeheader()
w.writerows(grades)

Intercepting python interpreter code before it executes

Is it possible to intercept interpreter's code before it executes?
Let's say I want to handle a case like:
>>> for f in glob.glob('*.*'): # I'd like to intercept this code before it executes
... something_to_do(f) # and play with it in some dangerous fashion :)
...
ERROR: Using glob not allowed. # e.g.
But there are tons of other examples (like altering the code, or sending it somewhere).
I can write my own interpreter, but that's not really the point.
Ok, solved it by creating new module which starts new interpreter instance and do whatever.
I just put the code below in the module and import it.
import code
class GlobeFilterConsole(code.InteractiveConsole):
def push(self, line):
self.buffer.append(line)
source = "\n".join(self.buffer)
if 'glob' in source: # do whatever you want with the source
print('glob usage not allowed')
more = False
else:
more = self.runsource(source, self.filename)
if not more:
self.resetbuffer()
return more
console = GlobeFilterConsole()
console.interact()

How can I test the standard input and standard output in Python Script with a Unittest test?

I'm trying to test a Python script (2.7) where I work with the standar input (readed with raw_input() and writed with a simple print) but I don't find how do this and I'm sure that this issue is very simple.
This is a very very very resume code of my script:
def example():
number = raw_input()
print number
if __name__ == '__main__':
example()
I want to write a unittest test to check this, but I don't find how. I've trying with StringIO and other things but I don't find the solution to do this really simple.
Somebody have a idea?
PD: Of course in the real script I use data blocks with several lines and other kind of data.
Thank you so much.
EDIT:
Thank you so much for the first really specific answer, it works perfectly, only I've had a little problem importing StringIO, because I was doing import StringIO and I needed to import like from StringIO import StringIO (I don't understand really why), but be that as It may, it works.
But I I've found another problem using this way, in my project I need test a scripts with this way (that work perfectly thanks to your support) but I want do this:
I have a file with a lot of test to pass over a script, so I open the file and read blocks of info with their result blocks and I would like to do that the code will be able to process a block checking their result and do the same with other and another...
Something like this:
class Test(unittest.TestCase):
...
#open file and process saving data like datablocks and results
...
allTest = True
for test in tests:
stub_stdin(self, test.dataBlock)
stub_stdouts(self)
runScrip()
if sys.stdout.getvalue() != test.expectResult:
allTest = False
self.assertEqual(allTest, True)
I know that maybe unittest doesn't has sense now, but you can do a idea about I want. So, this way fails and I don't know why.
Typical techniques involve mocking the standard sys.stdin and sys.stdout with your desired items. If you do not care for Python 3 compatibility you can just use the StringIO module, however if you want forward thinking and is willing to restrict to Python 2.7 and 3.3+, supporting for this both Python 2 and 3 in this way becomes possible without too much work through the io module (but requires a bit of modification, but put this thought on hold for now).
Assuming you already have a unittest.TestCase going, you can create a utility function (or method in the same class) that will replace sys.stdin/sys.stdout as outlined. First the imports:
import sys
import io
import unittest
In one of my recent projects I've done this for stdin, where it take a str for the inputs that the user (or another program through pipes) will enter into yours as stdin:
def stub_stdin(testcase_inst, inputs):
stdin = sys.stdin
def cleanup():
sys.stdin = stdin
testcase_inst.addCleanup(cleanup)
sys.stdin = StringIO(inputs)
As for stdout and stderr:
def stub_stdouts(testcase_inst):
stderr = sys.stderr
stdout = sys.stdout
def cleanup():
sys.stderr = stderr
sys.stdout = stdout
testcase_inst.addCleanup(cleanup)
sys.stderr = StringIO()
sys.stdout = StringIO()
Note that in both cases, it accepts a testcase instance, and calls its addCleanup method that adds the cleanup function call that will reset them back to where they were when the duration of a test method is concluded. The effect is that for the duration from when this was invoked in the test case until the end, sys.stdout and friends will be replaced with the io.StringIO version, meaning you can check its value easily, and don't have to worry about leaving a mess behind.
Better to show this as an example. To use this, you can simply create a test case like so:
class ExampleTestCase(unittest.TestCase):
def test_example(self):
stub_stdin(self, '42')
stub_stdouts(self)
example()
self.assertEqual(sys.stdout.getvalue(), '42\n')
Now, in Python 2, this test will only pass if the StringIO class is from the StringIO module, and in Python 3 no such module exists. What you can do is use the version from the io module with a modification that makes it slightly more lenient in terms of what input it accepts, so that the unicode encoding/decoding will be done automatically rather than triggering an exception (such as print statements in Python 2 will not work nicely without the following). I typically do this for cross compatibility between Python 2 and 3:
class StringIO(io.StringIO):
"""
A "safely" wrapped version
"""
def __init__(self, value=''):
value = value.encode('utf8', 'backslashreplace').decode('utf8')
io.StringIO.__init__(self, value)
def write(self, msg):
io.StringIO.write(self, msg.encode(
'utf8', 'backslashreplace').decode('utf8'))
Now plug your example function plus every code fragment in this answer into one file, you will get your self contained unittest that works in both Python 2 and 3 (although you need to call print as a function in Python 3) for doing testing against stdio.
One more note: you can always put the stub_ function calls in the setUp method of the TestCase if every single test method requires that.
Of course, if you want to use various mocks related libraries out there to stub out stdin/stdout, you are free to do so, but this way relies on no external dependencies if this is your goal.
For your second issue, test cases have to be written in a certain way, where they must be encapsulated within a method and not at the class level, your original example will fail. However you might want to do something like this:
class Test(unittest.TestCase):
def helper(self, data, answer, runner):
stub_stdin(self, data)
stub_stdouts(self)
runner()
self.assertEqual(sys.stdout.getvalue(), answer)
self.doCleanups() # optional, see comments below
def test_various_inputs(self):
data_and_answers = [
('hello', 'HELLOhello'),
('goodbye', 'GOODBYEgoodbye'),
]
runScript = upperlower # the function I want to test
for data, answer in data_and_answers:
self.helper(data, answer, runScript)
The reason why you might want to call doCleanups is to prevent the cleanup stack from getting as deep as all the data_and_answers pairs are there, but that will pop everything off the cleanup stack so if you had any other things that need to be cleaned up at the end this might end up being problematic - you are free to leave that there as all of the stdio related objects will be restored at the end in the same order, so the real one will always be there. Now the function I wanted to test:
def upperlower():
raw = raw_input()
print (raw.upper() + raw),
So yes, a bit of explanation for what I did might help: remember within a TestCase class, the framework relies strictly on the instance's assertEqual and friends for it to function. So to ensure testing being done at the right level you really want to call those asserts all the time so that helpful error messages will be shown at the moment the error occurred with the inputs/answers that didn't quite show up right, rather than until the very end like what you did with the for loop (that will tell you something was wrong, but not exactly where out of the hundreds and now you are mad). Also the helper method - you can call it anything you want, as long as it doesn't start with test because then the framework will try to run it as one and it will fail terribly. So just follow this convention and you can basically have templates within your test case to run your test with - you can then use it in a loop with a bunch of inputs/outputs like what I did.
As for your other question:
only I've had a little problem importing StringIO, because I was doing import StringIO and I needed to import like from StringIO import StringIO (I don't understand really why), but be that as It may, it works.
Well, if you look at my original code I did show you how did import io and then overrode the io.StringIO class by defining class StringIO(io.StringIO). However it works for you because you are doing this strictly from Python 2, whereas I do try to target my answers to Python 3 whenever possible given that Python 2 will (probably definitely this time) not be supported in less than 5 years. Think of the future users that might be reading this post who had similar problem as you. Anyway, yes, the original from StringIO import StringIO works, as that's the StringIO class from the StringIO module. Though from cStringIO import StringIO should work as that imports the C version of the StringIO module. It works because they all offer close enough interfaces, and so they will basically work as intended (until of course you try to run this under Python 3).
Again, putting all this together along with my code should result in a self-contained working test script. Do remember to look at documentation and follow the form of the code, and not invent your own syntax and hoping things to work (and as for exactly why your code didn't work, because the "test" code was defined at where the class was being constructed, so all of that was executed while Python was importing your module, and since none of the things that are needed for the test to run are even available (namely the class itself doesn't even exist yet), the whole thing just dies in fits of twitching agony). Asking questions here help too, even though the issue you face is something really common, not having a quick and simple name to search for your exact problem does make it difficult to figure out where you went wrong, I supposed? :) Anyway good luck, and good on you for taking the effort to test your code.
There are other methods, but given that the other questions/answers I looked at here at SO doesn't seem to help, I hope this one this. Other ones for reference:
How to supply stdin, files and environment variable inputs to Python unit tests?
python mocking raw input in unittests
Naturally, it bares repeating that all of this can be done using unittest.mock available in Python 3.3+ or the original/rolling backport version on pypi, but given that those libraries hides some of the intricacies on what actually happens, they may end up hiding some of the details on what actually happens (or need to happen) or how the redirection actually happens. If you want, you can read up on unittest.mock.patch and go down slightly to the StringIO patching sys.stdout section.

Good way to collect programmatically generated test suites in nose or pytest

Say I've got a test suite like this:
class SafeTests(unittest.TestCase):
# snip 20 test functions
class BombTests(unittest.TestCase):
# snip 10 different test cases
I am currently doing the following:
suite = unittest.TestSuite()
loader = unittest.TestLoader()
safetests = loader.loadTestsFromTestCase(SafeTests)
suite.addTests(safetests)
if TARGET != 'prod':
unsafetests = loader.loadTestsFromTestCase(BombTests)
suite.addTests(unsafetests)
unittest.TextTestRunner().run(suite)
I have major problem, and one interesting point
I would like to be using nose or py.test (doestn't really matter which)
I have a large number of different applications that are exposing these test
suites via entry points.
I would like to be able to aggregate these custom tests across all installed
applications so I can't just use a clever naming convention. I don't
particularly care about these being exposed through entry points, but I
do care about being able to run tests across applications in
site-packages. (Without just importing... every module.)
I do not care about maintaining the current dependency on
unittest.TestCase, trashing that dependency is practically a goal.
EDIT This is to confirm that #Oleksiy's point about passing args to
nose.run does in fact work with some caveats.
Things that do not work:
passing all the files that one wants to execute (which, weird)
passing all the modules that one wants to execute. (This either executes
nothing, the wrong thing, or too many things. Interesting case of 0, 1 or
many, perhaps?)
Passing in the modules before the directories: the directories have to come
first, or else you will get duplicate tests.
This fragility is absurd, if you've got ideas for improving it I welcome
comments, or I set up
a github repo with my
experiments trying to get this to work.
All that aside, The following works, including picking up multiple projects
installed into site-packages:
#!python
import importlib, os, sys
import nose
def runtests():
modnames = []
dirs = set()
for modname in sys.argv[1:]:
modnames.append(modname)
mod = importlib.import_module(modname)
fname = mod.__file__
dirs.add(os.path.dirname(fname))
modnames = list(dirs) + modnames
nose.run(argv=modnames)
if __name__ == '__main__':
runtests()
which, if saved into a runtests.py file, does the right thing when run as:
runtests.py project.tests otherproject.tests
For nose you can have both tests in place and select which one to run using attribute plugin, which is great for selecting which tests to run. I would keep both tests and assign attributes to them:
from nose.plugins.attrib import attr
#attr("safe")
class SafeTests(unittest.TestCase):
# snip 20 test functions
class BombTests(unittest.TestCase):
# snip 10 different test cases
For you production code I would just call nose with nosetests -a safe, or setting NOSE_ATTR=safe in your os production test environment, or call run method on nose object to run it natively in python with -a command line options based on your TARGET:
import sys
import nose
if __name__ == '__main__':
module_name = sys.modules[__name__].__file__
argv = [sys.argv[0], module_name]
if TARGET == 'prod':
argv.append('-a slow')
result = nose.run(argv=argv)
Finally, if for some reason your tests are not discovered you can explicitly mark them as test with #istest attribute (from nose.tools import istest)
This turned out to be a mess: Nose pretty much exclusively uses the
TestLoader.load_tests_from_names function (it's the only function tested in
unit_tests/test_loader)
so since I wanted to actually load things from an arbitrary python object I
seemed to need to write my own figure out what kind of load function to use.
Then, in addition, to correctly get things to work like the nosetests script
I needed to import a large number of things. I'm not at all certain that this
is the best way to do things, not even kind of. But this is a stripped down
example (no error checking, less verbosity) that is working for me:
import sys
import types
import unittest
from nose.config import Config, all_config_files
from nose.core import run
from nose.loader import TestLoader
from nose.suite import ContextSuite
from nose.plugins.manager import PluginManager
from myapp import find_test_objects
def load_tests(config, obj):
"""Load tests from an object
Requires an already configured nose.config.Config object.
Returns a nose.suite.ContextSuite so that nose can actually give
formatted output.
"""
loader = TestLoader()
kinds = [
(unittest.TestCase, loader.loadTestsFromTestCase),
(types.ModuleType, loader.loadTestsFromModule),
(object, loader.loadTestsFromTestClass),
]
tests = None
for kind, load in kinds.items():
if isinstance(obj, kind) or issubclass(obj, kind):
log.debug("found tests for %s as %s", obj, kind)
tests = load(obj)
break
suite = ContextSuite(tests=tests, context=obj, config=config)
def main():
"Actually configure the nose config object and run the tests"
config = Config(files=all_config_files(), plugins=PluginManager())
config.configure(argv=sys.argv)
tests = []
for group in find_test_objects():
tests.append(load_tests(config, group))
run(suite=tests)
If your question is, "How do I get pytest to 'see' a test?", you'll need to prepend 'test_' to each test file and each test case (i.e. function). Then, just pass the directories you want to search on the pytest command line and it will recursively search for files that match 'test_XXX.py', collect the 'test_XXX' functions from them and run them.
As for the docs, you can try starting here.
If you don't like the default pytest test collection method, you can customize it using the directions here.
If you are willing to change your code to generate a py.test "suite" (my definition) instead of a unittest suite (tech term), you may do so easily. Create a file called conftest.py like the following stub
import pytest
def pytest_collect_file(parent, path):
if path.basename == "foo":
return MyFile(path, parent)
class MyFile(pytest.File):
def collect(self):
myname="foo"
yield MyItem(myname, self)
yield MyItem(myname, self)
class MyItem(pytest.Item):
SUCCEEDED=False
def __init__(self, name, parent):
super(MyItem, self).__init__(name, parent)
def runtest(self):
if not MyItem.SUCCEEDED:
MyItem.SUCCEEDED = True
print "good job, buddy"
return
else:
print "you sucker, buddy"
raise Exception()
def repr_failure(self, excinfo):
return ""
Where you will be generating/adding your code into your MyFile and MyItem classes (as opposed to the unittest.TestSuite and unittest.TestCase). I kept the naming convention of MyFile class that way, because it is intended to represent something that you read from a file, but of course you can basically decouple it (as I've done here). See here for an official example of that. The only limit is that in the way I've written this foo must exist as a file, but you can decouple that too, e.g. by using conftest.py or whatever other file name exist in your tree (and only once, otherwise everything will run for each files that matches -- and if you don't do the if path.basename test for every file that exists in your tree!!!)
You can run this from command line with
py.test -whatever -options
or programmactically from any code you with
import pytest
pytest.main("-whatever -options")
The nice thing with py.test is that you unlock many very powerful plugings such as html report

Categories