Pytest code coverage for if __name__ == '__main__' - python

I'm trying to get 100% code coverage with pytest-cov and I'm just missing the lines 245-248 according to this.
server.py, 97%, 245-248
Here are the relevant lines in server.py:
if __name__ == '__main__':
with app.app_context():
db.drop_all() # Drop all existing tables in the database
db.create_all() # Create new tables in the database
app.run(port=5000, debug=True)
testing.py:
import tempfile
import os
import pytest
import server
import time
# Set up client
#pytest.fixture
def client():
db_fd, name = tempfile.mkstemp() # Creates and opens a temporary file
server.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+str(name)
server.app.config['TESTING'] = True
with server.app.test_client() as client:
with server.app.app_context():
server.db.drop_all() # Clear database between test functions
server.db.create_all() # Create new database
yield client
# Test is over, close and remove the temporary file
os.close(db_fd) # Tear down
os.unlink(name) # Unlink
def test_server(client):
with server.app.app_context():
server.db.drop_all()
server.db.create_all()
with server.db.session.begin():
server.db.session.add(server.User(username='testuser'))
result = server.db.session.query(server.User).filter_by(username='testuser').first()
assert result is not None
assert isinstance(result, server.User)
It passed but it's not covering the test case. What do I need to change to make it work?

If you are worried that the code inside if __name__ == '__main__': is not getting tested, which would be a valid concern, then I would strongly suggest to move it out into a function that could be tested, so that you end up only with something like:
if __name__ == '__main__':
main()
If after that you are still not happy that you don't have 100% coverage, then I'm afraid that what you are chasing is a bit of a fallacy. However, you could then exclude all such blocks from your reports.
.coveragerc:
[report]
exclude_lines =
if __name__\s*==\s*['"]__main__['"]\s*:

You can add # pragma: no cover in the lines you want to exclude:
if __name__ == '__main__': # pragma: no cover
with app.app_context():
db.drop_all() # Drop all existing tables in the database
db.create_all() # Create new tables in the database
app.run(port=5000, debug=True)

Related

Mocking a load time variable in Python

I am trying to get coverage up on my Python code and am testing my Flask app. Without being too specific, let's look at the sample code here:
# from /my_project/app_demo/app.py
from private_module import logs
from flask import Flask
import logging
logs.init_logging()
logger = logging.getLogger(__name__)
app = Flask(__name__)
#app.route("/greet/<name:name>", methods=["GET"])
def greet(name):
logger.info(f"{name} wants a greeting")
return f"Hello, {name}!"
if __name__ == "__main__":
app.run(debug=True)
If I am to write a unit test for the greet function, I want to mock the logger to assert it is called when it is created and when the info method is called. For this example, the sources root folder is app_demo and there is an init.py in that python folder.
# from /my_project/tests/test_app.py
import pytest
from unittest import mock
#pytest.fixture
def client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client
def test_greet(client):
logs_mock = mock.patch("app_demo.app.logs.init_logging")
logger_mock = mock.patch("app_demo.app.logging.getLogger")
actual = client.get("George")
assert "Hello, George!" == actual
# these next assertions do not work
logs_mock.assert_called_once_with()
logging_mock.assert_called_once_with("app_demo.app") # not sure if this will be "__main__"
logging_mock.return_value.info.assert_called_once_with("George wants a greeting")
If I debug where the logger.info is called in the greet function, the object is a logger and not the mock object I want it to be.
I have tried making the mock in another fixture as well but that did not make it work either.

Re-running an initialization function for each api reload

I am currently running an api project (fastapi served with uvicorn) where at startup a series of initializations are done.
If I set reload=True as an argument to my uvicorn startup function, my api correctly recognizes code changes and reloads. The issue is that I don't get the initializations when reloading the api, and this ends up breaking my workflow and effectively blocking me from using what I consider a very useful feature.
Example:
# fastapi app object is located in another module
def main() -> None:
various_initializations()
if __name__ == "__main__":
main()
uvicorn.run("my.project.location.for:app", host="0.0.0.0", port=my_port, reload=True)
In this case I need my main function to be run at each reload.
Edit:
Testable example
main.py
import uvicorn
from my_class import MyClass
def init_stuff() -> None:
MyClass.initialize()
if __name__ == "__main__":
init_stuff()
uvicorn.run("api:app", host="0.0.0.0", port=10000, reload=True)
my_class.py
class MyClass:
initialized = False
#staticmethod
def initialize() -> None:
MyClass.initialized = True
#staticmethod
def do_stuff() -> None:
if not MyClass.initialized:
raise ValueError("not initialized!")
print("doing stuff")
api.py
from fastapi import FastAPI
from my_class import MyClass
app = FastAPI()
#app.get("/stuff")
def api_stuff() -> None:
return MyClass.do_stuff()
When reload=False, if I hit the /stuff endpoint I get a correct behavior (doing stuff is printed on the terminal). With reload=True I get an exception indicating that MyClass hasn't been initialized (ValueError: not initialized!)
as #MatsLindh suggested, the (a?) way to do it is to hook on the startup event of fastapi, making the initialization run on each reload.
my main.py file now looks like this:
import uvicorn
from my_class import MyClass
from api import app
#app.on_event("startup")
def init_stuff() -> None:
MyClass.initialize()
if __name__ == "__main__":
uvicorn.run("api:app", host="0.0.0.0", port=10000, reload=True)
Happy api noises

Why do so many people put the app.run() in flask sites into a __name__ == "__main__" condition?

I know what if __name__ == "__main__" does and use it, but why do so many people and the documentation put the " app.run() " function of flask in this condition?
This is because there are certain situation where you have to access certain variables for example when you have a sqlalchemy database and If you want create tables. You have to do
from app import db
In such situation you app will run
Also when you move to production you don't need run and app is called from elsewhere if you add if __name__ == "__main__" part run will not be called unnecessarly.
This will ensure that when this module is imported, it won't run the code within
if __name__ == "__main__":
Your name == "main" when you run the .py directly.
ex:
foo.py
import bar
print("This is foo file)
now the inverse:
bar.py
import foo
def main():
print(This is bar file)
print(__name__)
if __name__ == "__main__":
main()
(running the bar.py file)
python bar.py
This is foo file
This is bar file
"__main__"
(running the foo.py file)
python foo.py
this is foo file
I know others will comment and provide a more thorough answer. But this is one way of looking at it.
Cheers.

write unit test for web.py application by using pytest

I would like to write unit test for web.py application by using pytest. How to invoke the web.py services in pytest.
Code:
import web
urls = (
'/', 'index'
)
app = web.application(urls, globals())
class index:
def GET(self):
return "Hello, world!"
if __name__ == "__main__":
app.run()
It can be done by using python requests module, when we run the web.py services, it will run http://localhost:8080/. Then import requests module and use get method and in response object, you can verify the result. That's fine.
By using Paste and nose also we can achieve this as per web.py official documentation. http://webpy.org/docs/0.3/tutorial.
Is there any solution in pytest like option in paste and nose.
Yes. Actually the code from the web.py recipe Testing with Paste and Nose could be used with py.test almost as is, just removing the nose.tools import and updating assertions appropriately.
But if you want to know how to write tests for web.py applications in the py.test style, they could look like this:
from paste.fixture import TestApp
# I assume the code from the question is saved in a file named app.py,
# in the same directory as the tests. From this file I'm importing the variable 'app'
from app import app
def test_index():
middleware = []
test_app = TestApp(app.wsgifunc(*middleware))
r = test_app.get('/')
assert r.status == 200
assert 'Hello, world!' in r
As you will add further tests, you will probably refactor creation of the test app out to a fixture:
from pytest import fixture # added
from paste.fixture import TestApp
from app import app
def test_index(test_app):
r = test_app.get('/')
assert r.status == 200
assert 'Hello, world!' in r
#fixture()
def test_app():
middleware = []
return TestApp(app.wsgifunc(*middleware))

Importing values in config.py

I wanted to mix a config.py approach and ConfigParser to set some default values in config.py which could be overridden by the user in its root folder:
import ConfigParser
import os
CACHE_FOLDER = 'cache'
CSV_FOLDER = 'csv'
def main():
cp = ConfigParser.ConfigParser()
cp.readfp(open('defaults.cfg'))
cp.read(os.path.expanduser('~/.python-tools.cfg'))
CACHE_FOLDER = cp.get('folders', 'cache_folder')
CSV_FOLDER = cp.get('folders', 'csv_folder')
if __name__ == '__main__':
main()
When running this module I can see the value of CACHE_FOLDER being changed. However when in another module I do the following:
import config
def main()
print config.CACHE_FOLDER
This will print the original value of the variable ('cache').
Am I doing something wrong ?
The main function in the code you show only gets run when that module is run as a script (due to the if __name__ == '__main__' block). If you want that turn run any time the module is loaded, you should get rid of that restriction. If there's extra code that actually does something useful in the main function, in addition to setting up the configuration, you might want to split that part out from the setup code:
def setup():
# the configuration stuff from main in the question
def main():
# other stuff to be done when run as a script
setup() # called unconditionally, so it will run if you import this module
if __name__ == "__main__":
main() # this is called only when the module is run as a script

Categories

Resources