Skip to content

feat: Add comprehensive Python testing infrastructure with Poetry #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Virtual Environments
venv/
env/
ENV/
.venv/
.env

# Testing
.pytest_cache/
.coverage
htmlcov/
coverage.xml
*.cover
*.py,cover
.hypothesis/
.tox/
nosetests.xml
coverage/
*.log

# Claude
.claude/*

# IDE
.idea/
.vscode/
*.swp
*.swo
*~
.project
.pydevproject
.settings/
*.sublime-project
*.sublime-workspace

# OS
.DS_Store
Thumbs.db
ehthumbs.db
Desktop.ini

# Package managers
pip-log.txt
pip-delete-this-directory.txt

# Documentation
docs/_build/
.doctrees/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# Local development
local/
.local/
tmp/
1,473 changes: 1,473 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

87 changes: 87 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
[tool.poetry]
name = "pygad-cnn"
version = "0.1.0"
description = "A Python library for building Convolutional Neural Networks from scratch using NumPy"
authors = ["Your Name <[email protected]>"]
readme = "README.md"
packages = [{include = "*.py"}]

[tool.poetry.dependencies]
python = "^3.8"
pygad = "*"
numpy = "*"

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
pytest-cov = "^4.1.0"
pytest-mock = "^3.11.0"

[tool.poetry.scripts]
test = "pytest:main"
tests = "pytest:main"

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--verbose",
"-ra",
"--cov=.",
"--cov-report=html",
"--cov-report=xml",
"--cov-report=term-missing",
"--cov-fail-under=80",
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow tests",
]
filterwarnings = [
"error",
"ignore::UserWarning",
"ignore::DeprecationWarning",
]

[tool.coverage.run]
source = ["."]
omit = [
"*/tests/*",
"*/test_*",
"*/__pycache__/*",
"*/venv/*",
"*/env/*",
"*/.venv/*",
"*/setup.py",
"*/conftest.py",
"example.py",
"TutorialProject/*",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if __name__ == .__main__.:",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if False:",
"pass",
]
show_missing = true
precision = 2

[tool.coverage.html]
directory = "htmlcov"

[tool.coverage.xml]
output = "coverage.xml"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
Empty file added tests/__init__.py
Empty file.
91 changes: 91 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import pytest
import tempfile
import shutil
import numpy as np
from pathlib import Path


@pytest.fixture
def temp_dir():
"""Create a temporary directory for test files."""
temp_path = tempfile.mkdtemp()
yield Path(temp_path)
shutil.rmtree(temp_path)


@pytest.fixture
def sample_input_data():
"""Create sample input data for CNN testing."""
return np.random.rand(10, 28, 28, 1).astype(np.float32)


@pytest.fixture
def sample_output_data():
"""Create sample output labels for CNN testing."""
return np.eye(10)[np.random.randint(0, 10, 10)]


@pytest.fixture
def mock_config():
"""Provide a mock configuration dictionary."""
return {
"learning_rate": 0.01,
"batch_size": 32,
"epochs": 10,
"activation": "relu",
}


@pytest.fixture
def sample_conv_layer():
"""Create a sample convolutional layer configuration."""
return {
"num_filters": 32,
"filter_size": 3,
"stride": 1,
"padding": 1,
}


@pytest.fixture
def sample_pool_layer():
"""Create a sample pooling layer configuration."""
return {
"pool_size": 2,
"stride": 2,
"mode": "max",
}


@pytest.fixture(autouse=True)
def reset_random_seed():
"""Reset random seed before each test for reproducibility."""
np.random.seed(42)


@pytest.fixture
def capture_stdout(monkeypatch):
"""Capture stdout for testing print statements."""
import io
import sys

captured_output = io.StringIO()
monkeypatch.setattr(sys, 'stdout', captured_output)

yield captured_output

captured_output.close()


@pytest.fixture
def mock_file_operations(mocker):
"""Mock file I/O operations."""
mock_open = mocker.mock_open()
mocker.patch('builtins.open', mock_open)
return mock_open


@pytest.fixture(scope="session")
def test_data_dir():
"""Path to test data directory."""
return Path(__file__).parent / "test_data"
Empty file added tests/integration/__init__.py
Empty file.
88 changes: 88 additions & 0 deletions tests/test_setup_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import pytest
import numpy as np
from pathlib import Path


class TestSetupValidation:
"""Validation tests to ensure the testing infrastructure is properly configured."""

def test_pytest_is_installed(self):
"""Verify pytest is installed and accessible."""
assert pytest.__version__

def test_fixtures_are_available(self, temp_dir, sample_input_data, mock_config):
"""Verify that custom fixtures from conftest.py are available."""
assert isinstance(temp_dir, Path)
assert temp_dir.exists()

assert isinstance(sample_input_data, np.ndarray)
assert sample_input_data.shape == (10, 28, 28, 1)

assert isinstance(mock_config, dict)
assert "learning_rate" in mock_config

@pytest.mark.unit
def test_unit_marker_works(self):
"""Verify that the unit test marker is properly configured."""
assert True

@pytest.mark.integration
def test_integration_marker_works(self):
"""Verify that the integration test marker is properly configured."""
assert True

@pytest.mark.slow
def test_slow_marker_works(self):
"""Verify that the slow test marker is properly configured."""
import time
time.sleep(0.1)
assert True

def test_coverage_is_tracked(self):
"""Verify that coverage tracking is working."""
def dummy_function(x):
if x > 0:
return x * 2
else:
return 0

assert dummy_function(5) == 10
assert dummy_function(-5) == 0

def test_mocking_works(self, mocker):
"""Verify that pytest-mock is properly installed and working."""
mock_func = mocker.Mock(return_value=42)
assert mock_func() == 42
mock_func.assert_called_once()

def test_numpy_random_seed_is_reset(self):
"""Verify that the random seed fixture is working."""
random_value_1 = np.random.rand()

# This should be the same value if the seed is properly reset
expected_value = 0.3745401188473625
assert abs(random_value_1 - expected_value) < 1e-10

def test_project_structure(self):
"""Verify the expected project structure exists."""
project_root = Path(__file__).parent.parent

assert (project_root / "pyproject.toml").exists()
assert (project_root / "tests").is_dir()
assert (project_root / "tests" / "__init__.py").exists()
assert (project_root / "tests" / "unit").is_dir()
assert (project_root / "tests" / "integration").is_dir()
assert (project_root / "tests" / "conftest.py").exists()

def test_main_module_is_importable(self):
"""Verify that the main CNN module can be imported."""
import sys
sys.path.insert(0, str(Path(__file__).parent.parent))

try:
import cnn
assert hasattr(cnn, 'sigmoid')
assert hasattr(cnn, 'relu')
assert hasattr(cnn, 'softmax')
finally:
sys.path.pop(0)
Empty file added tests/unit/__init__.py
Empty file.