I'm following this tutorial on web2py where you get to make a testdriven environment. However when I try to run the test with unittest, selenium I get this error:
$ python functional_tests.py
running tests
Traceback (most recent call last):
File "functional_tests.py", line 56, in <module>
run_functional_tests()
File "functional_tests.py", line 46, in run_functional_tests
tests = unittest.defaultTestLoader.discover('fts')
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/unittest/loader.py", line 202, in discover
raise ImportError('Start directory is not importable: %r' % start_dir)
ImportError: Start directory is not importable: 'fts'
This is how the functional_tests.py looks like:
#!/usr/bin/env python
try: import unittest2 as unittest #for Python <= 2.6
except: import unittest
import sys, urllib2
sys.path.append('./fts/lib')
from selenium import webdriver
import subprocess
import sys
import os.path
ROOT = 'http://localhost:8001'
class FunctionalTest(unittest.TestCase):
#classmethod
def setUpClass(self):
self.web2py = start_web2py_server()
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(1)
#classmethod
def tearDownClass(self):
self.browser.close()
self.web2py.kill()
def get_response_code(self, url):
"""Returns the response code of the given url
url the url to check for
return the response code of the given url
"""
handler = urllib2.urlopen(url)
return handler.getcode()
def start_web2py_server():
#noreload ensures single process
print os.path.curdir
return subprocess.Popen([
'python', '../../web2py.py', 'runserver', '-a "passwd"', '-p 8001'
])
def run_functional_tests(pattern=None):
print 'running tests'
if pattern is None:
tests = unittest.defaultTestLoader.discover('fts')
else:
pattern_with_globs = '*%s*' % (pattern,)
tests = unittest.defaultTestLoader.discover('fts', pattern=pattern_with_globs)
runner = unittest.TextTestRunner()
runner.run(tests)
if __name__ == '__main__':
if len(sys.argv) == 1:
run_functional_tests()
else:
run_functional_tests(pattern=sys.argv[1])
I solved this problem by replacing fts with the full path i.e. /home/simon/web2py/applications/testapp/fts
Hope this helps
I had the same problem and based on an excellent article unit testing with web2py,
I got this to work by doing the following:
Create a tests folder in the tukker directory
Copy/save the amended code(below) into tests folder as alt_functional_tests.py
Alter the web2py path in the start_web2py_server function to your own path
To run, enter the command: python web2py.py -S tukker -M -R applications/tukker/tests/alt_functional_tests.py
I am no expert but hopefully this will work for you also.
import unittest
from selenium import webdriver
import subprocess
import urllib2
execfile("applications/tukker/controllers/default.py", globals())
ROOT = 'http://localhost:8001'
def start_web2py_server():
return subprocess.Popen([
'python', '/home/alan/web2py/web2py/web2py.py', 'runserver',
'-a "passwd"', '-p 8001' ])
class FunctionalTest(unittest.TestCase):
#classmethod
def setUpClass(self):
self.web2py = start_web2py_server()
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(1)
#classmethod
def tearDownClass(self):
self.browser.close()
self.web2py.kill()
def get_response_code(self, url):
"""Returns the response code of the given url
url the url to check for
return the response code of the given url
"""
handler = urllib2.urlopen(url)
return handler.getcode()
def test_can_view_home_page(self):
# John opens his browser and goes to the home-page of the tukker app
self.browser.get(ROOT + '/tukker/')
# He's looking at homepage and sees Heading "Messages With 300 Chars"
body = self.browser.find_element_by_tag_name('body')
self.assertIn('Messages With 300 Chars', body.text)
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(FunctionalTest))
unittest.TextTestRunner(verbosity=2).run(suite)
First you have to do some changes in wrong paths in fts/functional_tests.py
search for
'python', '../../web2py.py', 'runserver', '-a "passwd"', '-p 8001'
and change it to
'python', '../../../web2py.py', 'runserver', '-a "passwd"', '-p 8001'
then
tests = unittest.defaultTestLoader.discover('fts')
to
tests = unittest.defaultTestLoader.discover('.')
then
tests = unittest.defaultTestLoader.discover('fts', pattern=pattern_with_globs)
to
tests = unittest.defaultTestLoader.discover('.', pattern=pattern_with_globs)
and
sys.path.append('fts/lib')
to
sys.path.append('./lib')
Related
I have a simple class with Flask_restx:
from flask_restx import Namespace, Resource
from flask import current_app
ping_namespace = Namespace("ping")
class Ping(Resource):
def get(self):
return {
"status": "success",
"message": "system up and running",
"api_version": current_app.config["APP_VERSION"],
}
def pingFromCommand():
print(current_app.config["APP_VERSION"])
ping_namespace.add_resource(Ping, "")
This is the simple command in manage.py
[...]
app = create_app()
cli = FlaskGroup(create_app=create_app)
from project.batch.v2.ping.ping import Ping
#cli.command('ping')
def ping():
Ping.pingFromCommand()
If I run from command line, I get the desiderated result:
(env) $ python manage.py ping
(env) $ 2.0.0
I know how write tests for API resources with pytest, but I don't know how can I write a test for the pingFromCommand method.
I did try:
from project.batch.v2.ping.ping import Ping
def test_ping_command(test_app):
assert Ping.pingFromCommand() == "0.0.0"
But I get
> assert Ping.pingFromCommand() == "0.0.0"
E AssertionError: assert None == '0.0.0'
E + where None = <function Ping.pingFromCommand at 0x7fd41c619160>()
E + where <function Ping.pingFromCommand at 0x7fd41c619160> = Ping.pingFromCommand
Someone can help me or address me? Thank you in advance.
In the pingFromCommand function, print is used to print the current app version. That function returns None which is what you get in the AssertionError.
You can update the function to return the app version
def pingFromCommand():
return current_app.config["APP_VERSION"]
Or you might be able to use capsys fixture provided by pytest which will capture
standard output:
def test_ping_command(capsys, test_app):
Ping.pingFromCommand()
captured = capsys.readouterr() # Capture output
assert captured.out == "0.0.0" # Assert stdout
I have read many article over the last 6 hours and i still don't understand mocking and unit-testing. I want to unit test a open function, how can i do this correctly?
i am also concerned as the bulk of my code is using external files for data import and manipulation. I understand that i need to mock them for testing, but I am struggling to understand how to move forward.
Some advice please. Thank you in advance
prototype5.py
import os
import sys
import io
import pandas
pandas.set_option('display.width', None)
def openSetupConfig (a):
"""
SUMMARY
Read setup file
setup file will ONLY hold the file path of the working directory
:param a: str
:return: contents of the file stored as str
"""
try:
setupConfig = open(a, "r")
return setupConfig.read()
except Exception as ve:
ve = (str(ve) + "\n\nPlease ensure setup file " + str(a) + " is available")
sys.exit(ve)
dirPath = openSetupConfig("Setup.dat")
test_prototype5.py
import prototype5
import unittest
class TEST_openSetupConfig (unittest.TestCase):
"""
Test the openSetupConfig function from the prototype 5 library
"""
def test_open_correct_file(self):
result = prototype5.openSetupConfig("Setup.dat")
self.assertTrue(result)
if __name__ == '__main__':
unittest.main()
So the rule of thumb is to mock, stub or fake all external dependencies to the method/function under test. The point is to test the logic in isolation. So in your case you want to test that it can open a file or log an error message if it can't be opened.
import unittest
from mock import patch
from prototype5 import openSetupConfig # you don't want to run the whole file
import __builtin__ # needed to mock open
def test_openSetupConfig_with_valid_file(self):
"""
It should return file contents when passed a valid file.
"""
expect = 'fake_contents'
with patch('__builtin__.open', return_value=expect) as mock_open:
actual = openSetupConfig("Setup.dat")
self.assertEqual(expect, actual)
mock_open.assert_called()
#patch('prototype5.sys.exit')
def test_openSetupConfig_with_invalid_file(self, mock_exit):
"""
It should log an error and exit when passed an invalid file.
"""
with patch('__builtin__.open', side_effect=FileNotFoundError) as mock_open:
openSetupConfig('foo')
mock_exit.assert_called()
I am having trouble trying to save -all- of the results shown from pytest to a file (txt, log, doesn't matter). In the test example below, I would like to capture what is shown in console into a text/log file of some sort:
import pytest
import os
def test_func1():
assert True
def test_func2():
assert 0 == 1
if __name__ == '__main__':
pytest.main(args=['-sv', os.path.abspath(__file__)])
Console output I'd like to save to a text file:
test-mbp:hi_world ua$ python test_out.py
================================================= test session starts =================================================
platform darwin -- Python 2.7.6 -- py-1.4.28 -- pytest-2.7.1 -- /usr/bin/python
rootdir: /Users/tester/PycharmProjects/hi_world, inifile:
plugins: capturelog
collected 2 items
test_out.py::test_func1 PASSED
test_out.py::test_func2 FAILED
====================================================== FAILURES =======================================================
_____________________________________________________ test_func2 ______________________________________________________
def test_func2():
> assert 0 == 1
E assert 0 == 1
test_out.py:9: AssertionError
========================================= 1 failed, 1 passed in 0.01 seconds ==========================================
test-mbp:hi_world ua$
It appears that all of your test output is going stdout, so you simply need to “redirect” your python invocation's output there:
python test_out.py >myoutput.log
You can also “tee” the output to multiple places. E.g., you might want to log to the file yet also see the output on your console. The above example then becomes:
python test_out.py | tee myoutput.log
I derive this from pastebin as suggest by Bruno Oliveira :
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Pytest Plugin that save failure or test session information to a file pass as a command line argument to pytest.
It put in a file exactly what pytest return to the stdout.
To use it :
Put this file in the root of tests/ edit your conftest and insert in the top of the file :
pytest_plugins = 'pytest_session_to_file'
Then you can launch your test with the new option --session_to_file= like this :
py.test --session_to_file=FILENAME
Or :
py.test -p pytest_session_to_file --session_to_file=FILENAME
Inspire by _pytest.pastebin
Ref: https://github.com/pytest-dev/pytest/blob/master/_pytest/pastebin.py
Version : 0.1
Date : 30 sept. 2015 11:25
Copyright (C) 2015 Richard Vézina <ml.richard.vezinar # gmail.com>
Licence : Public Domain
"""
import pytest
import sys
import tempfile
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting")
group._addoption('--session_to_file', action='store', metavar='path', default='pytest_session.txt',
help="Save to file the pytest session information")
#pytest.hookimpl(trylast=True)
def pytest_configure(config):
tr = config.pluginmanager.getplugin('terminalreporter')
# if no terminal reporter plugin is present, nothing we can do here;
# this can happen when this function executes in a slave node
# when using pytest-xdist, for example
if tr is not None:
config._pytestsessionfile = tempfile.TemporaryFile('w+')
oldwrite = tr._tw.write
def tee_write(s, **kwargs):
oldwrite(s, **kwargs)
config._pytestsessionfile.write(str(s))
tr._tw.write = tee_write
def pytest_unconfigure(config):
if hasattr(config, '_pytestsessionfile'):
# get terminal contents and delete file
config._pytestsessionfile.seek(0)
sessionlog = config._pytestsessionfile.read()
config._pytestsessionfile.close()
del config._pytestsessionfile
# undo our patching in the terminal reporter
tr = config.pluginmanager.getplugin('terminalreporter')
del tr._tw.__dict__['write']
# write summary
create_new_file(config=config, contents=sessionlog)
def create_new_file(config, contents):
"""
Creates a new file with pytest session contents.
:contents: paste contents
:returns: url to the pasted contents
"""
# import _pytest.config
# path = _pytest.config.option.session_to_file
# path = 'pytest_session.txt'
path = config.option.session_to_file
with open(path, 'w') as f:
f.writelines(contents)
def pytest_terminal_summary(terminalreporter):
import _pytest.config
tr = terminalreporter
if 'failed' in tr.stats:
for rep in terminalreporter.stats.get('failed'):
try:
msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
except AttributeError:
msg = tr._getfailureheadline(rep)
tw = _pytest.config.create_terminal_writer(terminalreporter.config, stringio=True)
rep.toterminal(tw)
s = tw.stringio.getvalue()
assert len(s)
create_new_file(config=_pytest.config, contents=s)
The pastebin internal plugin does exactly that, but sends the output directly to bpaste.net. You can look at the plugin implementation to understand how to reuse it for your needs.
Here is a fixture in order for you to be able to do this, I used the pytest Cache feature in order to leverage a fixture that can be passed around to multiple test files, including distributed tests(xdist), in order to be able to collect and print test results.
conftest.py:
from _pytest.cacheprovider import Cache
from collections import defaultdict
import _pytest.cacheprovider
import pytest
#pytest.hookimpl(tryfirst=True)
def pytest_configure(config):
config.cache = Cache(config)
config.cache.set('record_s', defaultdict(list))
#pytest.fixture(autouse=True)
def record(request):
cache = request.config.cache
record_s = cache.get('record_s', {})
testname = request.node.name
# Tried to avoid the initialization, but it throws errors.
record_s[testname] = []
yield record_s[testname]
cache.set('record_s', record_s)
#pytest.hookimpl(trylast=True)
def pytest_unconfigure(config):
print("====================================================================\n")
print("\t\tTerminal Test Report Summary: \n")
print("====================================================================\n")
r_cache = config.cache.get('record_s',{})
print str(r_cache)
Use:
def test_foo(record):
record.append(('PASS', "reason", { "some": "other_stuff" }))
Output:
====================================================================
Terminal Test Report Summary:
====================================================================
{u'test_foo': [[u'PASS',u'reason', { u'some': u'other_stuff' } ]]}
I got this error when run test.py
C:\Python32>python.exe test.py
Traceback (most recent call last):
File "test.py", line 5, in <module>
import httplib
ImportError: No module named httplib
How to correct it?
Code block for test.py:
#!/usr/local/bin/python
import httplib
import sys
import re
from HTMLParser import HTMLParser
class miniHTMLParser( HTMLParser ):
viewedQueue = []
instQueue = []
def get_next_link( self ):
if self.instQueue == []:
return ''
else:
return self.instQueue.pop(0)
def gethtmlfile( self, site, page ):
try:
httpconn = httplib.HTTPConnection(site)
httpconn.request("GET", page)
resp = httpconn.getresponse()
resppage = resp.read()
except:
resppage = ""
return resppage
def handle_starttag( self, tag, attrs ):
if tag == 'a':
newstr = str(attrs[0][1])
if re.search('http', newstr) == None:
if re.search('mailto', newstr) == None:
if re.search('htm', newstr) != None:
if (newstr in self.viewedQueue) == False:
print (" adding", newstr)
self.instQueue.append( newstr )
self.viewedQueue.append( newstr )
else:
print (" ignoring", newstr)
else:
print (" ignoring", newstr)
else:
print (" ignoring", newstr)
def main():
if sys.argv[1] == '':
print ("usage is ./minispider.py site link")
sys.exit(2)
mySpider = miniHTMLParser()
link = sys.argv[2]
while link != '':
print ("\nChecking link ", link)
# Get the file from the site and link
retfile = mySpider.gethtmlfile( sys.argv[1], link )
# Feed the file into the HTML parser
mySpider.feed(retfile)
# Search the retfile here
# Get the next link in level traversal order
link = mySpider.get_next_link()
mySpider.close()
print ("\ndone\n")
if __name__ == "__main__":
main()
You are running Python 2 code on Python 3. In Python 3, the module has been renamed to http.client.
You could try to run the 2to3 tool on your code, and try to have it translated automatically. References to httplib will automatically be rewritten to use http.client instead.
you can just import http.client and rename it to httplib with this code :
import http.client as httplib
If you use PyCharm, please change you 'Project Interpreter' to '2.7.x'
I had this issue when I was trying to make my Docker container smaller. It was because I'd installed Python 2.7 with:
apt-get install -y --no-install-recommends python
And I should not have included the --no-install-recommends flag:
apt-get install -y python
I'm tying to use Cherrypy for a website, but I have some problem with mapping the url of the page I want to display with function in the python code.
Now I have this code
#!/usr/bin/env python
import os
localDir = os.path.dirname(__file__)
absDir = os.path.join(os.getcwd(), localDir)
import cherrypy
from genshi.template import TemplateLoader
loader = TemplateLoader('../html', auto_reload=True)
class Root(object):
#cherrypy.expose
def index(self):
tmpl = loader.load('index.html')
return tmpl.generate().render('html', doctype='html')
#cherrypy.expose
def upload(self, datafile):
#do something
...
return out % (size, datafile.filename, datafile.content_type)
cherrypy.root.index = index
cherrypy.root.upload = upload
conf = os.path.join(os.path.dirname(__file__), 'server.config')
cherrypy.quickstart(Root(), '/', config=conf)
And the configuration file is this:
[/index.html]
tools.staticfile.on = True
tools.staticfile.filename = "/path-to-file/html/index.html"
[/impemails.html]
tools.staticfile.on = True
tools.staticfile.filename = "/path-to-file/html/impemails.html"
[/css/style.css]
tools.staticfile.on = True
tools.staticfile.filename = "/path-to-file/css/style.css"
[/css/index.css]
tools.staticfile.on = True
tools.staticfile.filename = "/path-to-file/css/index.css"
[/css/imp.css]
tools.staticfile.on = True
tools.staticfile.filename = "/path-to-file/css/imp.css"
For all the file specified in the configuration file there are no problem, but when I try to access upload with the link http://localhost:8080/upload I get the "404 Not found Message"
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/cherrypy/_cprequest.py", line 656, in respond
response.body = self.handler()
File "/usr/local/lib/python2.7/dist-packages/cherrypy/lib/encoding.py", line 188, in __call__
self.body = self.oldhandler(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/cherrypy/_cperror.py", line 386, in __call__
raise self
NotFound: (404, "The path '/upload' was not found.")
I tried many different ways to solve this problem as shown in the tutorial http://docs.cherrypy.org/dev/concepts/dispatching.html but I failed.
I think I'm missing some configurations which are not reported in the tutorial.
Anybody as some ideas?
Thank you in advance
My bad, I misunderstood some configurations.
I've solved with this:
class Root(object):
#cherrypy.expose
def index(self):
tmpl = loader.load('index.html')
return tmpl.generate().render('html', doctype='html')
#cherrypy.expose
def upload(couchdb, maillist, datafile):
return "upload file"
conf = os.path.join(os.path.dirname(__file__), 'server.config')
root = Root()
root.upload = upload
cherrypy.tree.mount(root, '/', config=conf)
cherrypy.engine.start()
cherrypy.engine.block()
Basically I've just moved the function outside the Root class and added the path with root.upload = upload
Now it works.