Building and Testing a Python Project with GitHub Actions

This article will guide you in creating a standard Continuous Integration (CI) workflow for a Python application, ensuring your code is automatically tested and linted across multiple Python versions every time a change is pushed to your repository.

Step #1: Create Project Structure

Before defining the automation, we must first establish the structure of our Python project and write the core application logic and its corresponding unit tests.

We start by creating a source directory to hold our main application logic, which is a common and recommended practice for structured Python projects.

  • Folder to create: src/
  • File to create: src/app.py

Defining the Main Application (src/app.py)

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "Hello, world!"


if __name__ == "__main__":
    app.run()
Building and Testing a Python Project with GitHub Actions 1

Explanation:

  • from flask import Flask: Imports the necessary Flask class.
  • app = Flask(__name__): Creates the core Flask application object.
  • @app.route("/"): This decorator registers the index() function to handle requests made to the root URL path (/).
  • return "Hello, world!": This is the content that will be returned to the client when the root page is accessed.

Since we are testing a web application, our tests must check for HTTP response statuses (like 200 for success) and the content returned by the routes. We use Pytest and Flask’s built-in testing client for this.

File to update: test/test_app.py

from app import index


def test_index():
    assert index() == "Hello, world!"
Building and Testing a Python Project with GitHub Actions 2

Explanation:

  • from src.app import index: This import is crucial. It relies on the CI workflow setting the PYTHONPATH environment variable to the repository root, allowing Python to find the src/ directory and import the app module from within it.
  • assert index() == "Hello, world!": The test executes the index() function directly and asserts that its return value exactly matches the expected string.

Then we will create The Dependency List. (requirements.txt)

flask
pytest
gunicorn
Building and Testing a Python Project with GitHub Actions 3

Explanation:

  • flask: The web framework required by the application itself.
  • pytest: The tool used to discover and run the tests.
  • gunicorn: A production-grade WSGI HTTP Server, often used to serve Flask apps in a deployed environment (and included here as part of the project dependencies).

Step #2: Creating the GitHub Actions Workflow

This is the control file that automates the entire CI process on GitHub’s virtual machines.

  • File to create: .github/workflows/python-app.yml
name: Python application

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Set up Python 3.10
      uses: actions/setup-python@v3
      with:
        python-version: "3.10"
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install flake8 pytest
        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
    - name: Lint with flake8
      run: |
        # stop the build if there are Python syntax errors or undefined names
        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
    - name: Test with pytest
      run: |
        pip install pytest
        export PYTHONPATH=src
        pytest
Building and Testing a Python Project with GitHub Actions 4

Explanation:

  • Workflow Triggers and Permissions
    • Name: The workflow is identified as Python application.
    • Events (on): It runs automatically on code pushes and pull requests targeting the "main" branch.
    • Permissions: It is configured with read-only access to the repository contents for security.
  • Job Configuration
    • Job ID: The sole job is named build.
    • Runner: It executes on a GitHub-hosted virtual machine running ubuntu-latest.
  • Execution Steps
    • Checkout Code: Downloads the repository files using actions/checkout@v3.
    • Set up Python: Configures Python version 3.10 for the runner environment.
    • Install Dependencies: Installs flake8 and pytest, followed by project dependencies (Flask, Gunicorn) from requirements.txt.
    • Lint with Flake8: Runs code quality checks for syntax errors and PEP 8 style compliance.
    • Test with Pytest:
      • Sets the PYTHONPATH=src environment variable to allow tests to import modules from the src/ directory.
      • Executes pytest to run all unit tests.

Once you commit and push these files to your GitHub repository, navigate to the Actions tab to monitor the execution.

Successful Output Log (from Test with pytest step). The log for the unit testing step confirms that Pytest successfully discovered and executed the test_index function, which passed by verifying the “Hello, world!” string.

Final Outcome: The entire “Python application” workflow job will display a green checkmark on the GitHub Actions page, confirming that your Flask application’s code is clean (passed linting) and functionally correct (passed unit tests) on the target Python 3.10 environment.

Building and Testing a Python Project with GitHub Actions 5

Conclusion:

In conclusion, this workflow sets up essential Continuous Integration (CI) for your Flask application by automatically configuring Python 3.10, installing dependencies, performing code quality checks with Flake8, and running all functional unit tests with Pytest to validate code before merging to main.

Related Articles:

Top 40 GitHub Actions Interview Questions and Answers

Reference:

GitHub Actions official documentation

Prasad Hole

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Share via
Copy link
Powered by Social Snap