Skip to main content

Testing Infrastructure

starward maintains a comprehensive test suite with 872 tests covering all astronomical calculations. The tests serve dual purposes: ensuring correctness and providing educational documentation of astronomical concepts.

Test Suite Overview

tests/
├── conftest.py # Shared fixtures (observers, coordinates, stars)
├── allure/ # Allure helpers and step functions
│ ├── helpers.py # Reusable step decorators
│ └── categories.json # Test categorization rules
├── core/ # Core module tests
│ ├── test_angles.py # Angular measurements (84 tests)
│ ├── test_coords.py # Coordinate systems (38 tests)
│ ├── test_time.py # Julian Date, sidereal time (46 tests)
│ ├── test_sun.py # Solar calculations (28 tests)
│ ├── test_moon.py # Lunar calculations (26 tests)
│ ├── test_planets.py # Planetary ephemerides (37 tests)
│ ├── test_visibility.py # Observability (31 tests)
│ ├── test_constants.py # Astronomical constants (28 tests)
│ ├── test_messier.py # Messier catalog (43 tests)
│ ├── test_ngc.py # NGC catalog (44 tests)
│ ├── test_ic.py # IC catalog (43 tests)
│ ├── test_caldwell.py # Caldwell catalog (40 tests)
│ └── test_hipparcos.py # Hipparcos catalog (50 tests)
├── cli/ # CLI integration tests
│ └── test_commands.py # Command-line interface (34 tests)
├── integration/ # End-to-end workflows
│ └── test_workflows.py # Multi-module integration (8 tests)
└── output/ # Formatter tests
└── test_formatters.py # Rich output formatting (10 tests)

Running Tests

Basic Commands

# Run all tests (quiet mode)
pytest

# Run with verbose output
pytest -v

# Run specific test file
pytest tests/core/test_sun.py

# Run tests matching a pattern
pytest -k "moon"

# Run with coverage report
pytest --cov=starward --cov-report=html

Using the Makefile

# Run tests with Allure results
make test

# Generate and open Allure report
make report

# Clean test artifacts
make clean

Test Markers

Tests are organized using pytest markers for selective execution:

MarkerDescriptionExample
@pytest.mark.goldenReference value tests (high-accuracy validation)Sun position at J2000.0
@pytest.mark.slowTests that take longer to executeMonte Carlo validation
@pytest.mark.edgeEdge case and boundary testsPoles, date line
@pytest.mark.skipTemporarily skipped (with reason)Unimplemented features
# Run only golden tests
pytest -m golden

# Skip slow tests
pytest -m "not slow"

# Run edge case tests
pytest -m edge

Allure Reporting

The test suite uses Allure for rich, interactive test reports.

Report Structure

Tests are organized into a hierarchy:

  • Epic: Top-level category (e.g., "Core Calculations")
  • Feature: Module being tested (e.g., "Sun Module")
  • Story: Test class grouping (e.g., "Solar Position")
  • Test: Individual test case with steps

Allure Decorators

import allure
import pytest

@allure.epic("Core Calculations")
@allure.feature("Sun Module")
@allure.story("Solar Position")
class TestSunPosition:
"""Tests for solar position calculations."""

@pytest.mark.golden
@allure.title("Sun position at J2000.0")
@allure.description("""
At J2000.0 (Jan 1, 2000 12:00 TT), the Sun should be at:
- RA: ~281° (in Sagittarius)
- Dec: ~-23° (near winter solstice)
""")
def test_sun_at_j2000(self):
"""Sun position at the J2000.0 epoch."""
with allure.step("Set reference date: J2000.0"):
jd = JulianDate(2451545.0)

with allure.step("Calculate sun position"):
pos = sun_position(jd)

with allure.step(f"Verify RA = {pos.ra.degrees:.2f}°"):
assert 270 < pos.ra.degrees < 290

Generating Reports

# Run tests (generates allure-results/)
pytest --clean-alluredir

# Serve report locally
allure serve allure-results

# Generate static HTML report
allure generate allure-results -o allure-report

Report Features

The Allure report includes:

  • Test execution timeline — Visual overview of test runs
  • Step-by-step breakdown — Each with allure.step() shown in detail
  • Attachments — Screenshots, logs, computed values
  • Categories — Automatic grouping of failures by type
  • History — Trends across multiple test runs

Test Fixtures

Common test fixtures are defined in conftest.py:

Observer Locations

@pytest.fixture
def greenwich():
"""Royal Observatory Greenwich (51.4772°N, 0.0005°W)"""
return Observer.from_degrees("Greenwich", 51.4772, -0.0005, elevation=62.0)

@pytest.fixture
def mauna_kea():
"""Mauna Kea Observatory (19.82°N, 155.47°W, 4207m)"""
return Observer.from_degrees("Mauna Kea", 19.82, -155.47, elevation=4207.0)

@pytest.fixture
def paranal():
"""ESO Paranal Observatory (-24.63°S, 70.40°W, 2635m)"""
return Observer.from_degrees("Paranal", -24.63, -70.40, elevation=2635.0)

Famous Stars

@pytest.fixture
def famous_stars():
"""Dictionary of bright stars with ICRS coordinates."""
return {
'sirius': ICRSCoord.parse("06h45m08.9s -16d42m58s"),
'vega': ICRSCoord.parse("18h36m56.3s +38d47m01s"),
'polaris': ICRSCoord.parse("02h31m49.1s +89d15m51s"),
'betelgeuse': ICRSCoord.parse("05h55m10.3s +07d24m25s"),
}

Reference Times

@pytest.fixture
def j2000():
"""J2000.0 epoch (JD 2451545.0)"""
return JulianDate(2451545.0)

@pytest.fixture
def vernal_equinox_2024():
"""2024 March Equinox (JD 2460390.3)"""
return JulianDate(2460390.3)

Writing Tests

Educational Docstrings

Tests should include educational docstrings explaining the astronomical concepts:

class TestAirmass:
"""
Tests for airmass calculations.

Airmass quantifies the amount of atmosphere light must traverse
to reach an observer. At zenith (directly overhead), airmass = 1.0.
As objects approach the horizon, airmass increases dramatically:

- 90° altitude: X = 1.0 (zenith)
- 45° altitude: X ≈ 1.41 (√2)
- 30° altitude: X ≈ 2.0 (practical observation limit)
- 10° altitude: X ≈ 5.8
- 0° altitude: X → ∞ (horizon)

The secant formula X = sec(z) = 1/cos(z) works well above 15°.
Near the horizon, atmospheric refraction requires corrections.
"""

Step Annotations

Use with allure.step() for meaningful test steps:

def test_julian_century(self):
"""Julian century = 36525 days (100 Julian years)."""
with allure.step("Define Julian year = 365.25 days"):
julian_year = 365.25

with allure.step("Calculate Julian century"):
julian_century = 100 * julian_year

with allure.step(f"Verify: {julian_century} = 36525"):
assert julian_century == 36525.0

Golden Tests

Mark tests that validate against known reference values:

@pytest.mark.golden
@allure.title("Speed of light = 299,792,458 m/s (exact)")
def test_speed_of_light(self):
"""Speed of light is exact by SI definition since 2019."""
with allure.step("Verify c = 299,792,458 m/s"):
assert CONSTANTS.c.value == 299792458.0

Coverage

Current test coverage by module:

ModuleTestsCoverage
core.angles8498%
core.coords3895%
core.time4697%
core.sun2894%
core.moon2692%
core.planets3791%
core.visibility3193%
core.constants28100%
cli3488%

Generate a detailed coverage report:

pytest --cov=starward --cov-report=html
open htmlcov/index.html