> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating Unit Tests for our Project

> Guide to building pytest unit and integration tests for a Python METAR decoder, including mocking network calls, Flask route tests, and edge case validations

In this lesson we create Python unit tests for a METAR decoder application. We use pytest and unittest.mock to validate parsing functions, full METAR decoding, Flask routes, network behavior, and edge cases. The test suite uses realistic mock METAR strings to assert human-readable outputs and deterministic decoding behavior.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/Xs8Ruv7b0QCZf7iM/images/Claude-Code-for-Beginners/Getting-Started-with-Claude-Code/Creating-Unit-Tests-for-our-Project/creating-unit-tests-demo-slide.jpg?fit=max&auto=format&n=Xs8Ruv7b0QCZf7iM&q=85&s=eef6728ee4df046b0cbfa4816608d77e" alt="A presentation slide titled &#x22;Creating Unit Tests for our Project&#x22; with a large &#x22;Demo&#x22; label on a dark curved shape at the right. The slide has a light background and a small &#x22;© Copyright KodeKloud&#x22; note in the bottom-left." width="1920" height="1080" data-path="images/Claude-Code-for-Beginners/Getting-Started-with-Claude-Code/Creating-Unit-Tests-for-our-Project/creating-unit-tests-demo-slide.jpg" />
</Frame>

Overview

* Test runner: pytest (with optional pytest-cov for coverage).
* Use unittest.mock (or pytest-mock) to stub network calls and simulate HTTP errors.
* Validate METARDecoder atomic parsing methods and full METAR integration outputs.
* Use the Flask test client to exercise web endpoints, form validation, and error handling.
* Include edge-case tests for malformed input, extreme values, and normalization behavior.

Using an AI assistant to bootstrap tests

* The assistant inspected `app.py`, recommended a layered test strategy, and produced a pytest-style scaffold with realistic mock METAR strings.
* Suggested test strategy:
  1. Unit tests for METARDecoder methods (atomic parsing helpers).
  2. Integration tests for end-to-end METAR decoding.
  3. Flask route tests using the Flask test client.
  4. Network/mocking tests for fetch routines and HTTP exceptions.
  5. Edge-case tests (malformed METARs, extreme values, missing fields).

Recommended test structure

| File / Class | Purpose | Examples |
| - | - | - |
| `tests/test_app.py` | Single consolidated test module (or split across multiple files) | `TestMETARDecoder`, `TestMETARDecodingIntegration`, `TestFetchMETAR`, `TestFlaskRoutes`, `TestEdgeCases` |
| `tests/fixtures.py` | Reusable fixtures for mock METAR strings, decoder instances | `@pytest.fixture def decoder(): return METARDecoder()` |
| `requirements-test.txt` | Test dependencies | `pytest`, `pytest-cov`, `requests-mock` |

High-level test classes and responsibilities

* TestMETARDecoder: unit tests for wind direction, visibility, cloud parsing, weather phenomena, and temperature conversions.
* TestMETARDecodingIntegration: full METAR strings decoded into expected human-readable descriptions.
* TestFetchMETAR: mock network responses (success, RequestException, HTTPError).
* TestFlaskRoutes: exercise endpoints, forms, and error templates using Flask's test client.
* TestEdgeCases: malformed METAR components, extreme wind/temperature values, missing data.

Sample tests (representative, cleaned and positioned correctly)

* Use pytest fixtures for setup and mocking. Below are representative examples that show proper structure and assertions.

```python theme={null}
# tests/test_app.py
import pytest
from unittest import mock
from requests.exceptions import RequestException, HTTPError
from app import METARDecoder, fetch_metar, create_app  # adjust imports to your app

@pytest.fixture
def decoder():
    return METARDecoder()

class TestMETARDecoder:
    def test_get_wind_direction_text(self, decoder):
        # Mapping degrees to cardinal directions
        assert decoder.get_wind_direction_text(180) == 'south'
        assert decoder.get_wind_direction_text(270) == 'west'
        assert decoder.get_wind_direction_text(45) == 'northeast'
        assert decoder.get_wind_direction_text(225) == 'southwest'

    def test_decode_visibility(self, decoder):
        """Test visibility decoding from METAR format."""
        assert decoder.decode_visibility('10SM') == '10+ miles visibility'
        assert decoder.decode_visibility('5SM') == '5 miles visibility'
        assert decoder.decode_visibility('1SM') == '1 mile visibility'

    def test_decode_weather_phenomena(self, decoder):
        assert decoder.decode_weather_phenomena('VCFG') == 'nearby fog'
        assert decoder.decode_weather_phenomena('TSRA') == 'thunderstorm, rain'
        assert decoder.decode_weather_phenomena('SN') == 'snow'
```

Network and HTTP error tests (mocking requests)

```python theme={null}
class TestFetchMETAR:
    @mock.patch('app.requests.get')
    def test_fetch_metar_network_error(self, mock_get):
        mock_get.side_effect = RequestException("Network error")
        result = fetch_metar('KHIO')
        # Adjust assertions to match your function's contract (None, dict with 'error', raise, etc.)
        assert result is None or 'error' in result

    @mock.patch('app.requests.get')
    def test_fetch_metar_http_error(self, mock_get):
        mock_response = mock.Mock()
        mock_response.raise_for_status.side_effect = HTTPError("HTTP 404")
        mock_get.return_value = mock_response

        result = fetch_metar('INVALID')
        assert result is None or 'error' in result
```

Edge-case tests: wind normalization

```python theme={null}
def test_extreme_wind_directions(decoder):
    # Implementation should normalize degrees using modulo 360.
    assert decoder.get_wind_direction_text(361) == 'north'   # 361 % 360 == 1 -> near-north
    assert decoder.get_wind_direction_text(-1) == 'north'    # -1 % 360 == 359 -> near-north
```

Installing test dependencies and running tests

* Add pytest to your test requirements (e.g., `requirements-test.txt` or `requirements.txt`).
* Install and run:

```bash theme={null}
pip3 install -r requirements-test.txt
python3 -m pytest tests/test_app.py -v
```

For coverage reports:

```bash theme={null}
python3 -m pytest tests/test_app.py --cov=app --cov-report=term-missing
```

Troubleshooting failing tests

* Example failing summary from an initial run:

```bash theme={null}
FAILED test_app.py::TestMETARDecoder::test_decode_weather_phenomena - AssertionError: assert 'rain, thunderstorm' == 'thunderstorm, rain'
FAILED test_app.py::TestMETARDecodingIntegration::test_decode_metar_with_weather - AssertionError: expected 'scattered clouds at 800 feet' in 'overcast at 2500 feet'
FAILED test_app.py::TestFetchMETAR::test_fetch_metar_network_error - Exception: Network error
FAILED test_app.py::TestFetchMETAR::test_fetch_metar_http_error - Exception: HTTP 404
FAILED test_app.py::TestEdgeCases::test_extreme_wind_directions - AssertionError: ...
6 failed, 21 passed in 0.38s
```

Common causes and fixes

* Ordering of decoded weather phenomena: produce deterministic ordering in decoder output (e.g., sort the list) or assert using sets when order is not important.
* Cloud parsing: support multiple cloud groups (e.g., `SCT008 BKN015 OVC025`) and return a combined description.
* Mock exception types: use `requests.exceptions.RequestException` and `requests.exceptions.HTTPError` for network tests.
* Wind direction wrapping: normalize degrees with modulo 360 before converting to compass text.
* Assert expectations must reflect the decoder's contract (exact phrasing vs. set membership).

Example fixes applied to tests and mocks

* Use appropriate request exception classes and explicit mock behavior:

```python theme={null}
# tests/test_app.py (excerpt)
from requests.exceptions import RequestException, HTTPError

@mock.patch('app.requests.get')
def test_fetch_metar_network_error(mock_get):
    mock_get.side_effect = RequestException("Network error")
    result = fetch_metar('KHIO')
    assert result is None or 'error' in result  # match your app contract

@mock.patch('app.requests.get')
def test_fetch_metar_http_error(mock_get):
    mock_response = mock.Mock()
    mock_response.raise_for_status.side_effect = HTTPError("HTTP 404")
    mock_get.return_value = mock_response

    result = fetch_metar('INVALID')
    assert result is None or 'error' in result
```

METARDecoder parsing snippet (context)

* Typical decoding flow: iterate parts of the METAR string, call helper methods for visibility, weather, clouds, etc.

```python theme={null}
class METARDecoder:
    def decode_metar(self, metar_string):
        decoded = {}

        for part in metar_string.split():
            # Visibility in statute miles
            if part.endswith('SM'):
                decoded['visibility'] = self.decode_visibility(part)

            # Weather phenomena (rain, snow, fog, etc.)
            elif any(wx in part for wx in ['RA', 'SN', 'DZ', 'FG', 'BR', 'HZ', 'TS', 'SH']):
                weather = self.decode_weather_phenomena(part)
                if weather:
                    decoded['weather'] = weather

            # Cloud coverage and altitude
            elif any(part.startswith(cloud) for cloud in ['CLR', 'SKC', 'FEW', 'SCT', 'BKN', 'OVC']):
                decoded['clouds'] = self.decode_clouds(part)

            # other parsing logic...
        return decoded
```

Re-running tests and coverage

* After making fixes and adjustments, run the full test suite and generate coverage:

```bash theme={null}
python3 -m pytest tests/test_app.py -v
python3 -m pytest tests/test_app.py --cov=app --cov-report=term-missing
```

* Example final results after fixes:

```bash theme={null}
============================= test session starts =============================
platform darwin -- Python 3.12.6, pytest-7.4.3
collected 27 items

tests/test_app.py ...................                              [100%]

27 passed in 0.38s

---------- coverage: platform darwin, python 3.12.6 ----------
Name    Stmts   Miss  Cover   Missing
-------------------------------------
app.py    127      1    99%   250-260
-------------------------------------
```

What the test suite validates

1. Individual METAR components:
   * Wind direction conversion (degrees → compass direction strings).
   * Visibility parsing (e.g., `10SM` → "10+ miles visibility").
   * Cloud layer decoding (`CLR`, `SCT015`, `BKN040`).
   * Weather phenomena decoding (`-RA`, `+TSRA`, `VCFG`).
   * Temperature conversion (Celsius → Fahrenheit).
2. Complete METAR integration strings:
   * Example METARs used in tests:
     * `KHIO 061853Z 27008KT 10SM CLR 22/16 A3012` (clear)
     * `KORD 061851Z 09014KT 3SM -RA SCT008 BKN015 OVC025 18/17 A2992` (rain)
     * `PANC 061853Z 36010KT 10SM CLR M15/M20 A2985` (below zero temps)
     * `KBOS 061854Z 08015KT 2SM +TSRA BKN008 OVC020 20/19 A2995` (thunderstorms)
3. API & network layer:
   * Mocked requests for success and simulated network/HTTP errors.
4. Flask routes:
   * Form validation, responses, and error rendering via the Flask test client.
5. Edge cases:
   * Malformed METAR components, extreme wind/temperature values, missing data.

Documentation and repo updates

* Update `requirements-test.txt` or `requirements.txt` to include:
  * `pytest`
  * `pytest-cov` (optional for coverage)
  * any test helpers (e.g., `requests-mock`)
* Update README with a "Testing" section describing:
  * How to install test dependencies,
  * How to run pytest and generate coverage,
  * What the tests cover,
  * Sample mock METAR strings used in tests.

Example git commands after adding tests

```bash theme={null}
git add .
git commit -m "Add pytest suite for METARDecoder, mocks for network tests, and README test docs"
git push
```

<Callout icon="lightbulb" color="#1CB2FE">
  Run tests frequently as you make parsing changes. Use deterministic ordering for lists (like weather phenomena) in decoder output or assert against sets when order is not important.
</Callout>

Summary

* A layered pytest suite (unit → integration → endpoint → network → edge cases) gives comprehensive coverage for METAR decoding.
* Use fixtures, deterministic outputs, and explicit mocks for network behavior to make tests reliable and fast.
* The generated scaffold from an AI assistant accelerates test creation, but always adapt assertions to reflect your decoder's exact output contract (phrasing, ordering, return types).

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/claude-code-for-beginners/module/e36fd287-dee2-4916-a919-953391788143/lesson/cdd241fb-994a-452a-87d4-f78d69704606" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.