mirror of
https://github.com/aserper/masto-rss.git
synced 2025-12-17 13:25:25 +00:00
Add comprehensive test suite with GitHub Actions CI/CD
- Refactor code into testable bot.py module with MastodonRSSBot class - Create 20+ unit tests covering core functionality and edge cases - Create 10+ integration tests for RSS parsing and Mastodon posting - Add GitHub Actions workflow for automated testing - Unit tests on Python 3.10, 3.11, 3.12 - Integration tests with mocked external services - Code quality checks (flake8, black, mypy) - Docker build validation - Configure pytest with 80% minimum coverage requirement - Add test dependencies in requirements-test.txt - Update .gitignore to exclude test artifacts - Add comprehensive TESTING.md documentation - Add test status badge to README - Maintain full backward compatibility with existing setup
This commit is contained in:
235
TESTING.md
Normal file
235
TESTING.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# Testing Guide for Masto-RSS
|
||||
|
||||
This document describes the testing strategy and how to run tests for the Masto-RSS bot.
|
||||
|
||||
## Test Architecture
|
||||
|
||||
The test suite is organized into two main categories:
|
||||
|
||||
### Unit Tests ([test_bot.py](test_bot.py))
|
||||
- Test individual functions and methods in isolation
|
||||
- Use mocks and stubs for external dependencies
|
||||
- Fast execution time
|
||||
- High code coverage
|
||||
- Test edge cases and error handling
|
||||
|
||||
### Integration Tests ([test_integration.py](test_integration.py))
|
||||
- Test interactions between components
|
||||
- Mock external services (RSS feeds, Mastodon API)
|
||||
- Test end-to-end workflows
|
||||
- Verify data persistence
|
||||
- Test error recovery
|
||||
|
||||
## Running Tests Locally
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```bash
|
||||
# Install test dependencies
|
||||
pip install -r requirements-test.txt
|
||||
```
|
||||
|
||||
### Run All Tests
|
||||
|
||||
```bash
|
||||
# Run all tests with coverage
|
||||
pytest
|
||||
|
||||
# Run with verbose output
|
||||
pytest -v
|
||||
|
||||
# Run with coverage report
|
||||
pytest --cov=bot --cov=main --cov-report=html
|
||||
```
|
||||
|
||||
### Run Specific Test Categories
|
||||
|
||||
```bash
|
||||
# Run only unit tests
|
||||
pytest test_bot.py
|
||||
|
||||
# Run only integration tests
|
||||
pytest test_integration.py
|
||||
|
||||
# Run tests matching a pattern
|
||||
pytest -k "test_parse_feed"
|
||||
```
|
||||
|
||||
### Run with Markers
|
||||
|
||||
```bash
|
||||
# Run only unit tests (using markers)
|
||||
pytest -m unit
|
||||
|
||||
# Run only integration tests
|
||||
pytest -m integration
|
||||
|
||||
# Skip slow tests
|
||||
pytest -m "not slow"
|
||||
```
|
||||
|
||||
### Coverage Reports
|
||||
|
||||
```bash
|
||||
# Generate HTML coverage report
|
||||
pytest --cov=bot --cov=main --cov-report=html
|
||||
|
||||
# View report
|
||||
open htmlcov/index.html # macOS
|
||||
xdg-open htmlcov/index.html # Linux
|
||||
```
|
||||
|
||||
## GitHub Actions CI/CD
|
||||
|
||||
Tests run automatically on every push to `main` and on all pull requests via [.github/workflows/test.yml](.github/workflows/test.yml).
|
||||
|
||||
### Test Jobs
|
||||
|
||||
1. **Unit Tests**
|
||||
- Runs on Python 3.10, 3.11, 3.12
|
||||
- Executes all unit tests
|
||||
- Uploads coverage to Codecov
|
||||
|
||||
2. **Integration Tests**
|
||||
- Runs on Python 3.10, 3.11, 3.12
|
||||
- Executes all integration tests with mocked external services
|
||||
- Uploads coverage to Codecov
|
||||
|
||||
3. **Code Quality**
|
||||
- Runs flake8 for linting
|
||||
- Runs black for code formatting checks
|
||||
- Runs mypy for type checking
|
||||
|
||||
4. **Docker Build Test**
|
||||
- Builds the Docker image
|
||||
- Verifies Python and dependencies are installed
|
||||
- Ensures the image can run
|
||||
|
||||
5. **All Tests Pass**
|
||||
- Final job that requires all previous jobs to succeed
|
||||
- Provides a single status check for PR requirements
|
||||
|
||||
## Test Coverage Requirements
|
||||
|
||||
- **Minimum coverage**: 80%
|
||||
- Coverage is measured for `bot.py` and `main.py`
|
||||
- Test files are excluded from coverage metrics
|
||||
|
||||
## Code Quality Standards
|
||||
|
||||
### Flake8
|
||||
- Maximum line length: 127 characters
|
||||
- Maximum cyclomatic complexity: 10
|
||||
- Critical error codes checked: E9, F63, F7, F82
|
||||
|
||||
### Black
|
||||
- Line length: 88 characters (default)
|
||||
- All Python files must pass black formatting
|
||||
|
||||
### Mypy
|
||||
- Type hints encouraged but not required
|
||||
- Runs in non-strict mode with missing imports ignored
|
||||
|
||||
## Test Data and Fixtures
|
||||
|
||||
### Mock RSS Feeds
|
||||
Integration tests use realistic RSS 2.0 and Atom feed XML for testing feed parsing.
|
||||
|
||||
### Mock Mastodon API
|
||||
The Mastodon API is mocked using `unittest.mock` to avoid making real API calls.
|
||||
|
||||
### Temporary State Files
|
||||
Tests use `tempfile.mktemp()` to create temporary state files that are cleaned up after each test.
|
||||
|
||||
## Writing New Tests
|
||||
|
||||
### Unit Test Template
|
||||
|
||||
```python
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
from bot import MastodonRSSBot
|
||||
|
||||
class TestNewFeature(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Set up test fixtures"""
|
||||
self.bot = MastodonRSSBot(
|
||||
client_id='test',
|
||||
client_secret='test',
|
||||
access_token='test',
|
||||
instance_url='https://test.com',
|
||||
feed_url='https://feed.test/rss.xml',
|
||||
state_file='/tmp/test_state.txt'
|
||||
)
|
||||
|
||||
def test_feature(self):
|
||||
"""Test description"""
|
||||
result = self.bot.some_method()
|
||||
self.assertEqual(result, expected_value)
|
||||
```
|
||||
|
||||
### Integration Test Template
|
||||
|
||||
```python
|
||||
import unittest
|
||||
import responses
|
||||
from bot import MastodonRSSBot
|
||||
|
||||
class TestNewIntegration(unittest.TestCase):
|
||||
@responses.activate
|
||||
@patch('bot.Mastodon')
|
||||
def test_integration(self, mock_mastodon):
|
||||
"""Test description"""
|
||||
# Mock HTTP responses
|
||||
responses.add(
|
||||
responses.GET,
|
||||
'https://example.com/feed.xml',
|
||||
body=rss_xml,
|
||||
status=200
|
||||
)
|
||||
|
||||
# Run test
|
||||
bot = MastodonRSSBot(...)
|
||||
result = bot.process_new_entries()
|
||||
|
||||
self.assertEqual(result, expected)
|
||||
```
|
||||
|
||||
## Continuous Integration Status
|
||||
|
||||
[](https://github.com/aserper/masto-rss/actions/workflows/test.yml)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Tests Fail Locally But Pass in CI
|
||||
- Ensure you're using the same Python version
|
||||
- Check that all dependencies are installed: `pip install -r requirements-test.txt`
|
||||
- Clear pytest cache: `pytest --cache-clear`
|
||||
|
||||
### Coverage Below 80%
|
||||
- Identify untested code: `pytest --cov=bot --cov-report=term-missing`
|
||||
- Add tests for the missing lines
|
||||
- Some error handling paths may be acceptable to skip
|
||||
|
||||
### Import Errors
|
||||
- Ensure the project root is in PYTHONPATH
|
||||
- Run tests from the project root directory
|
||||
- Check virtual environment is activated
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Test One Thing**: Each test should verify one specific behavior
|
||||
2. **Clear Names**: Test names should describe what they're testing
|
||||
3. **Arrange-Act-Assert**: Structure tests with setup, execution, and verification
|
||||
4. **Mock External Services**: Never make real HTTP requests or API calls
|
||||
5. **Clean Up**: Always clean up temporary files and state
|
||||
6. **Test Edge Cases**: Test both happy paths and error conditions
|
||||
7. **Keep Tests Fast**: Unit tests should run in milliseconds
|
||||
8. **Document Complex Tests**: Add comments explaining non-obvious test logic
|
||||
|
||||
## Resources
|
||||
|
||||
- [pytest documentation](https://docs.pytest.org/)
|
||||
- [unittest.mock documentation](https://docs.python.org/3/library/unittest.mock.html)
|
||||
- [responses library](https://github.com/getsentry/responses)
|
||||
- [Coverage.py documentation](https://coverage.readthedocs.io/)
|
||||
Reference in New Issue
Block a user