mercredi 30 octobre 2019

Testing a sever launching cli application - Python, Flask

Question How to test a blocking cli application in python?

What I am trying to accomplish

I have a manage.py file, which is used to start the flask development web server (and other admin stuff that are out of the scope of this question). Typically I would run this as python manage.py runserver, but for keeping this question simple and relevant I have edited that file (see Minimal, Reproducible Example) to just run the server. So the below command works

$ python manage.py 
 * Serving Flask app "app" (lazy loading)
 * Environment: production...

The issue comes with testing the functionality of this cli application. Click has the CliRunner.invoke(), which allows one to test cli applications. But in this particular case, it fails as the server is running.

from manage import runserver
from click.testing import CliRunner
import requests
import unittest


class TestManage(unittest.TestCase):

    def test_runserver(self):
        runner = CliRunner()
        runner.invoke(runserver)
        # It blocks above, does not go below!
        r = requests.get('http://127.0.0.1')
        assert r.status_code == 200


if __name__ == '__main__':
    unittest.main()

Attempts at solution

After trying many approaches I have found a "solution", which is to spawn a Process to start the server, run the tests and then terminating the process on exit (this is included in the example). This feels more like a hack rather than a "real" solution.

Minimal, Reproducible Example

  • Folder structure

    flask_app/
       - app.py
       - manage.py
       - test_manage.py
    
  • app.py

    from flask import Flask
    
    app = Flask(__name__)
    
    
    @app.route('/')
    def hello():
        return 'Hello, World!'
    
  • manage.py

    import click
    from app import app
    
    
    @click.command()
    def runserver():
        app.run()
    
    
    if __name__ == "__main__":
        runserver()
    
  • test_manage.py

    from manage import runserver
    from multiprocessing import Process
    from click.testing import CliRunner
    import requests
    import unittest
    
    
    class TestManage(unittest.TestCase):
    
        def setUp(self):
            self.runner = CliRunner()
            self.server = Process(
                target=self.runner.invoke,
                args=(runserver, )
            )
            self.server.start()
    
        def test_runserver(self):
            r = requests.get('http://127.0.0.1')
            assert r.status_code == 200
    
        def tearDown(self):
            self.server.terminate()
    
    
    if __name__ == '__main__':
        unittest.main()
    

    And run the test using $ python test_manage.py.

Aucun commentaire:

Enregistrer un commentaire