G
GuideDevOps
Lesson 13 of 15

Writing Unit Tests (pytest)

Part of the Python for DevOps tutorial series.

In DevOps, an untested script can bring down an entire production cluster. Unit testing ensures your automation logic is correct before it ever touches a real server.

1. Getting Started with pytest

pytest is the most popular testing framework for Python. It's simple, powerful, and requires less boilerplate than the built-in unittest module.

Your First Test

Action:

# content of test_sample.py
def add(a, b):
    return a + b
 
def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

Run Command:

pytest test_sample.py

Result:

============================= test session starts =============================
platform linux -- Python 3.10.x, pytest-7.x.x, pluggy-1.x.x
collected 1 item
 
test_sample.py .                                                        [100%]
 
============================== 1 passed in 0.01s ==============================

2. Testing DevOps Logic

Let's test a function that validates a server's configuration.

Action:

# content of test_config.py
import pytest
 
def validate_config(config):
    if "hostname" not in config:
        return False
    if not (1 <= config.get("port", 0) <= 65535):
        return False
    return True
 
def test_valid_config():
    conf = {"hostname": "web-01", "port": 80}
    assert validate_config(conf) is True
 
def test_invalid_port():
    conf = {"hostname": "web-01", "port": 99999}
    assert validate_config(conf) is False
 
def test_missing_hostname():
    conf = {"port": 80}
    assert validate_config(conf) is False

Run Command:

pytest test_config.py

Result:

test_config.py ...                                                       [100%]
============================== 3 passed in 0.02s ==============================

3. Mocking External Dependencies

DevOps scripts often call external APIs (AWS, GitHub) or run shell commands. You should mock these during unit tests to avoid making real calls.

Action:

# test_deploy.py
from unittest.mock import patch
 
def check_site_up(url):
    import requests
    response = requests.get(url)
    return response.status_code == 200
 
@patch('requests.get')
def test_check_site_up(mock_get):
    # Setup the mock to return a 200 status code
    mock_get.return_value.status_code = 200
    
    assert check_site_up("http://example.com") is True
    mock_get.assert_called_once_with("http://example.com")

Run Command:

pytest test_deploy.py

Result:

test_deploy.py .                                                         [100%]
============================== 1 passed in 0.01s ==============================

4. Pytest Fixtures

Fixtures are a way to provide a fixed baseline upon which tests can reliably and repeatedly execute.

Action:

import pytest
 
@pytest.fixture
def sample_deployment():
    return {
        "id": "dep-123",
        "status": "pending",
        "env": "prod"
    }
 
def test_deployment_id(sample_deployment):
    assert sample_deployment["id"].startswith("dep-")
 
def test_deployment_env(sample_deployment):
    assert sample_deployment["env"] == "prod"

Result:

test_fixtures.py ..                                                      [100%]
============================== 2 passed in 0.01s ==============================

Summary

  • pytest is the preferred framework for DevOps testing.
  • Use assert statements to check for expected outcomes.
  • Use unittest.mock to simulate API calls and shell commands.
  • Use Fixtures to set up test data and reusable objects.
  • Aim for high test coverage in your automation logic.