I am trying to log the output of tests to a text file. I am using the unittest module and want to log results into a text file instead of the screen. I have some script here to explain what has been tryied so far. This is the test script.
import unittest, sys
class TestOne(unittest.TestCase):
def setUp(self):
self.var = 'Tuesday'
def tearDown(self):
self.var = None
class BasicTestOne(TestOne):
def runTest(self):
TestOne.setUp(self)
self.assertEqual(self.var, 'Tuesday')
class AbsoluteMoveTestSuite(unittest.TestSuite):
# Tests to be tested by test suite
def makeAbsoluteMoveTestSuite():
suite = unittest.TestSuite()
suite.addTest(TestOne("BasicTestOne"))
return suite
def suite():
return unittest.makeSuite(TestOne)
if __name__ == '__main__':
unittest.main()
I have added this to the file but it doesn't seem to work.
log_file = 'log_file.txt'
sys.stout = sys.sterr = open(log_file, 'w')
return suite >> sys.stout
and also:
log_file = 'log_file.txt'
return suite >> open(log_file, 'w')
I have tried several different versions of this command.
if __name__ == '__main__':
unittest.main() >> open(log_file, 'w')
I have tried this. I want the log file to be stored and created inside the python script. I do not want to have to call python tests.py >> log_file.txt.
Thanks for any help
You can pass the text runner into the main method. The text runner must be set up to write to a file rather than the std.err as it wraps the stream in a decorator. The following worked for me in python 2.6
if __name__ == '__main__':
log_file = 'log_file.txt'
with open(log_file, "w") as f:
runner = unittest.TextTestRunner(f)
unittest.main(testRunner=runner)
Related
I want to run a single test and output the result to a txt file. I understood that I can use loadTestsFromName to specify the test. However, I'm getting an error.
test.py
import unittest
import sys
import os
def main(out=sys.stderr, verbosity=2):
loader = unittest.TestLoader()
suite = loader.loadTestsFromName(sys.modules[__name__]=='test1')
unittest.TextTestRunner(out, verbosity=verbosity).run(suite)
class TestClass(unittest.TestCase):
def test1(self):
self.assertEqual(True, True)
if __name__ == '__main__':
with open('test-results.txt', 'w') as f:
main(f)
I run the test by executing python test.py
I'm not sure how to get test1. I tried sys.modules[__name__]=='test1' but it triggered this error.
parts = name.split('.')
AttributeError: 'bool' object has no attribute 'split'
According to the python doc-unittest.TestLoader.loadTestsFromName, below code is work for me.
import unittest
import sys
import os
def main(out=sys.stderr, verbosity=2):
loader = unittest.TestLoader()
suite = loader.loadTestsFromName('__main__.TestClass.test1')
unittest.TextTestRunner(out, verbosity=verbosity).run(suite)
class TestClass(unittest.TestCase):
def test1(self):
self.assertEqual(True, True)
if __name__ == '__main__':
with open('test-results.txt', 'w') as f:
main(f)
Besides, you'd better separate the TestCase to the single module, then change the __main__ to the module name.
Trying to figure out how to overwrite a variable during unit testing:
#mainscript.py
import json
org_file = 'unitfile.json'
def load_json (file_name):
with open ('{0}'.format(org_file)) as f:
json_object = json.load(f)
return json_object
new_file = load_json(org_file)
def print_me():
return (new_file['text'])
def main():
print_me()
if __name__ == '__main__':
main()
Unit test:
#test_mainscript.py
import json
import mainscript
import unittest
from unittest.mock import patch
class Testmainscript(unittest.TestCase):
# def setUp(self):
# Not needed when using mock
#patch('mainscript.org_file','unitfile2.json')
def test_print_me(self):
self.assertEqual(mainscript.org_file, 'unitfile2.json') # Correctly overwrites file
self.assertEqual(mainscript.print_me(),'Bye World') #does not pass, still reading unitfile.json instead of unitfile2.json
if __name__ == '__main__':
unittest.main()
This test should fail because because I'm overwriting org_file with unitestfile2.json (which contains {'text':'Bye World'}) instead of unittestfile.json (which contains {'text':'Hello World'})
But its currently passing because the variable isn't being overwritten
What you are looking for is called "mocking", an example for your case:
import json
import mainscript
import unittest
from unittest.mock import patch
class Testmainscript(unittest.TestCase):
# def setUp(self):
# Not needed when using mock
#patch('mainscript.org_file', 'unitfile2.json')
def test_print_me(self):
self.assertEqual(mainscript.print_me(),'Hello World')
if __name__ == '__main__':
unittest.main()
I found the issue is that I am overwriting the file as expected but when I call the test I am not using the mocked file. Wrapping the function seemed to work:
#mainscript.py
import json
org_file = 'unitfile.json'
def load_json (json_file):
with open ('{0}'.format(org_file)) as f:
json_object = json.load(f)
return json_object
def print_me(df_file):
return (df_file['text'])
def main():
print_me()
if __name__ == '__main__':
main()
Here I used the wrapped patch as an input
#test_mainscript.py
import json
import mainscript
import unittest
from unittest.mock import patch
class Testmainscript(unittest.TestCase):
#patch('mainscript.org_file','unitfile2.json')
def test_print_me(self):
self.assertEqual(mainscript.org_file, 'unitfile2.json')
self.assertEqual(mainscript.print_me(mainscript.load_json(mainscript.org_file)),'Bye World')
if __name__ == '__main__':
unittest.main()
Test now work:
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
pythonHelper.py:
import logging
log = logging.getLogger(__name__)
class PythonHelper():
def __init__(self):
log.info("message1")
pass
def func(self):
log.info("message2")
pass
def main(self):
self.func()
runner.py:
from pythonHelper import PythonHelper
class Runner(PythonHelper):
pass
if __name__ == "__main__":
a = Runner()
a.main()
# do something on a:
# which would print "message1" and "message2"
# to console screen and store them into files.
I cannot do any change in pythonHelper.py file. But I would like to suppress printing logs on console screen because printing is slow... How could I implement it? I only have a silly method using "command > output.txt" but I expect a method that change or add something in my runner code.
If I got your point you can add the file handler:
import logging
from pythonHelper import PythonHelper
logging.getLogger().setLevel(logging.DEBUG) # Set this level for demo only
fh = logging.FileHandler('output.log') # here the handler
logging.getLogger().addHandler(fh) # added to the logging
class Runner(PythonHelper):
def new_func(self):
pass
if __name__ == "__main__":
a = Runner()
a.func()
you can read more here using-logging-in-multiple-modules
Edit: Thanks #eemz for the idea to redesign the structure and use from unittest.mock import patch, but the problem persists.
So I just recently stumbled into unittest and I have a program which I normally start like this python run.py -config /path/to/config.file -y. I wanted to write a simple test in a separate test.py file: Execute the script, pass the mentioned arguments and get all of its output. I pass a prepared config file which is missing certain things, so the run.py will break and exactly log this error using logging.error: "xyz was missing in Config file!" (see example below). I'll get a few words from print() and then the logging instance kicks in and handles from there on. How do I get its output so I can check it? Feel free to rewrite this, as I'm still learning, please bear with me.
Simplified example:
run.py
import logging
def run(args):
< args.config = /path/to/config.file >
cnfg = Config(args.config)
cnfg.logger.info("Let's start with the rest of the code!") # This is NOT in 'output' of the unittest
< code >
if __name__ == "__main__":
print("Welcome! Starting execution.") # This is in 'output' of the unittest
< code to parse arguments 'args' >
run(args)
Config.py
import logging
class Config:
def __init__(self):
print("Creating logging instance, hold on ...") # This is in 'output' of the unittest
logger = logging.getLogger(__name__)
console_handler = logging.StreamHandler()
logger.addHandler(console_handler)
logger.info("Logging activated, let's go!") # This is NOT in 'output' of the unittest
self.logger = logger
if xyz not in config:
self.logger.error("xyz was missing in Config file!") # This is NOT in 'output' of the unittest
exit(1)
test.py
import unittest
from unittest.mock import patch
class TestConfigs(unittest.TestCase):
def test_xyz(self):
with patch('sys.stdout', new=StringIO()) as capture:
with self.assertRaises(SystemExit) as cm:
run("/p/to/f/missing/xyz/f", "", False, True)
output = capture.getvalue().strip()
self.assertEqual(cm.exception.code, 1)
# Following is working, because the print messages are in output
self.assertTrue("Welcome! Starting execution." in output)
# Following is NOT working, because the logging messages are not in output
self.assertTrue("xyz was missing in Config file!" in output)
if __name__ == "__main__":
unittest.main()
I would restructure run.py like this:
import logging
def main():
print("Welcome! Starting execution.")
Etc etc
if __name__ == "__main__":
main()
Then you can call the function run.main() in your unit test rather than forking a subprocess.
from io import StringIO
from unittest.mock import patch
import sys
import run
class etc etc
def test_run etc etc:
with patch('sys.stdout', new=StringIO()) as capture:
sys.argv = [‘run.py’, ‘-flag’, ‘-flag’, ‘-flag’]
run.main()
output = capture.getvalue().strip()
assert output == <whatever you expect it to be>
If you’re new to unit testing then you might not have seen mocks before. Effectively I am replacing stdout with a fake one to capture everything that gets sent there, so that I can pull it out later into the variable output.
In fact a second patch around sys.argv would be even better because what I’m doing here, an assignment to the real argv, will actually change it which will affect subsequent tests in the same file.
I ended up instantiating the logger of the main program with a specific name, so I could get the logger in the test.py again and assert that the logger was called with a specific text. I didn't know that I could just get the logger by using logging.getLogger("name") with the same name. Simplified example:
test.py
import unittest
from run import run
from unittest.mock import patch
main_logger = logging.getLogger("main_tool")
class TestConfigs(unittest.TestCase):
def test_xyz(self):
with patch('sys.stdout', new=StringIO()) as capture, \
self.assertRaises(SystemExit) as cm, \
patch.object(main_logger , "info") as mock_log1, \
patch.object(main_logger , "error") as mock_log2:
run("/path/to/file/missing/xyz.file")
output = capture.getvalue().strip()
self.assertTrue("Creating logging instance, hold on ..." in output)
mock_log1.assert_called_once_with("Logging activated, let's go!")
mock_log2.assert_called_once_with("xyz was missing in Config file!")
self.assertEqual(cm.exception.code, 1)
if __name__ == "__main__":
unittest.main()
run.py
def run(path: str):
cnfg = Config(path)
< code >
if __name__ == "__main__":
< code to parse arguments 'args' >
path = args.file_path
run(path)
Config.py
import logging
class Config:
def __init__(self, path: str):
print("Creating logging instance, hold on ...")
logger = logging.getLogger("main_tool")
console_handler = logging.StreamHandler()
logger.addHandler(console_handler)
logger.info("Logging activated, let's go!")
self.logger = logger
# Load file, simplified
config = load(path)
if xyz not in config:
self.logger.error("xyz was missing in Config file!")
exit(1)
This method seems to be very complicated and I got to this point by reading through a lot of other posts and the docs. Maybe some one knows a better way to achieve this.
I am running Unit TestCases from Python Using XMLTestRunner.
This is my main file:
#LoggingService.py
class LoggerWriter:
# used to redirect console msgs to the log
def write(self, message):
log_event('warning', 'Console', message)
def direct_console_to_log():
global console_redirected
# This function can be called several times. Avoid redirecting more than once
if not console_redirected:
console_output = LoggerWriter()
sys.stderr = console_output
sys.stdout = console_output
console_redirected = True
def log_event(level, _id, event_details):
# log the event
pass
This is my one of the testing file:
#LoggingServiceTest.py
import unittest
import LoggingService
from LoggingService import direct_console_to_log
class Test(unittest.TestCase):
#patch('LoggingService.log_event')
def test_log_writer(self,log_mock):
direct_console_to_log()
print ('console msg')
self.assertTrue(log_mock.called)
if __name__ == "__main__":
unittest.main()
I used to run all the test file from XMLRunner.py:
#XMLRunner.py
from xmlrunner import XMLTestRunner
import unittest
test_modules = [file1.Test, file2.Test, LoggingServiceTest.py, .... file25.Test]
suite = unittest.TestSuite()
for test_module in test_modules:
tests = unittest.TestLoader().loadTestsFromTestCase(test_module)
suite.addTests(tests)
if __name__ == '__main__':
# import xmlrunner
# unittest.main(testRunner=xmlrunner.XMLTestRunner(output='test-reports'))
XMLTestRunner(output='test-reports').run(suite)
When I am trying to run LoggingServiceTest.py file from "XMLRunner.py", then the test report does not generate.But when I try without "LoggingServiceTest.py", then the report generates successfully.
And I am not sure, what is happening with the test case function
(test_log_writer(self,log_mock)) in LoggingServiceTest.py running.
Can anyone clear me
I use the following command to run the test case:
Python2.7.14\python.exe -m coverage run -p --branch --source=. --omit=Unit_Tests/*.py Unit_Tests/Testing.py
Finally I got the solution after debugging more.
When start test case from XMLRunner, It is creating some file objects for to write stderr and stdout and it is defined in sys.stderr, sys.stdout variable
And my test case is overwrite the sys.stderr, sys.stdout variable.
So XMLRunner try to fetching file objects from sys.stderr, sys.stdout, but it is not there, then it is not generating report.