Skip to main content
Glama
02-contributing-and-testing.md15.2 kB
# Contributing and Testing Guide This guide covers how to contribute to BioMCP and run the comprehensive test suite. ## Getting Started ### Prerequisites - Python 3.10 or higher - [uv](https://docs.astral.sh/uv/) package manager - Git - Node.js (for MCP Inspector) ### Initial Setup 1. **Fork and clone the repository:** ```bash git clone https://github.com/YOUR_USERNAME/biomcp.git cd biomcp ``` 2. **Install dependencies and setup:** ```bash # Recommended: Use make for complete setup make install # Alternative: Manual setup uv sync --all-extras uv run pre-commit install ``` 3. **Verify installation:** ```bash # Run server biomcp run # Run tests make test-offline ``` ## Development Workflow ### 1. Create Feature Branch ```bash git checkout -b feature/your-feature-name ``` ### 2. Make Changes Follow these principles: - **Keep changes minimal and focused** - **Follow existing code patterns** - **Add tests for new functionality** - **Update documentation as needed** ### 3. Quality Checks **MANDATORY: Run these before considering work complete:** ```bash # Step 1: Code quality checks make check # This runs: # - ruff check (linting) # - ruff format (code formatting) # - mypy (type checking) # - pre-commit hooks # - deptry (dependency analysis) ``` ### 4. Run Tests ```bash # Step 2: Run appropriate test suite make test # Full suite (requires network) # OR make test-offline # Unit tests only (no network) ``` **Both quality checks and tests MUST pass before submitting changes.** ## Testing Strategy ### Test Categories #### Unit Tests - Fast, reliable tests without external dependencies - Mock all external API calls - Always run in CI/CD ```python # Example unit test @patch('httpx.AsyncClient.get') async def test_article_search(mock_get): mock_get.return_value.json.return_value = {"results": [...]} result = await article_searcher(genes=["BRAF"]) assert len(result) > 0 ``` #### Integration Tests - Test real API interactions - May fail due to network/API issues - Run separately in CI with `continue-on-error` ```python # Example integration test @pytest.mark.integration async def test_real_pubmed_search(): result = await article_searcher(genes=["TP53"], limit=5) assert len(result) == 5 assert all("TP53" in r.text for r in result) ``` ### Running Tests #### Command Options ```bash # Run all tests make test uv run python -m pytest # Run only unit tests (fast, offline) make test-offline uv run python -m pytest -m "not integration" # Run only integration tests uv run python -m pytest -m "integration" # Run specific test file uv run python -m pytest tests/tdd/test_article_search.py # Run with coverage make cov uv run python -m pytest --cov --cov-report=html # Run tests verbosely uv run python -m pytest -v # Run tests and stop on first failure uv run python -m pytest -x ``` #### Test Discovery Tests are organized in: - `tests/tdd/` - Unit and integration tests - `tests/bdd/` - Behavior-driven development tests - `tests/data/` - Test fixtures and sample data ### Writing Tests #### Test Structure ```python import pytest from unittest.mock import patch, AsyncMock from biomcp.articles import article_searcher class TestArticleSearch: """Test article search functionality""" @pytest.fixture def mock_response(self): """Sample API response""" return { "results": [ {"pmid": "12345", "title": "BRAF in melanoma"} ] } @patch('httpx.AsyncClient.get') async def test_basic_search(self, mock_get, mock_response): """Test basic article search""" # Setup mock_get.return_value = AsyncMock() mock_get.return_value.json.return_value = mock_response # Execute result = await article_searcher(genes=["BRAF"]) # Assert assert len(result) == 1 assert "BRAF" in result[0].title ``` #### Async Testing ```python import pytest import asyncio @pytest.mark.asyncio async def test_async_function(): """Test async functionality""" result = await some_async_function() assert result is not None # Or use pytest-asyncio fixtures @pytest.fixture async def async_client(): async with AsyncClient() as client: yield client ``` #### Mocking External APIs ```python from unittest.mock import patch, MagicMock @patch('biomcp.integrations.pubmed.search') def test_with_mock(mock_search): # Configure mock mock_search.return_value = [{ "pmid": "12345", "title": "Test Article" }] # Test code that uses the mocked function result = search_articles("BRAF") # Verify mock was called correctly mock_search.assert_called_once_with("BRAF") ``` ## MCP Inspector Testing The MCP Inspector provides an interactive way to test MCP tools. ### Setup ```bash # Install inspector npm install -g @modelcontextprotocol/inspector # Run BioMCP with inspector make inspector # OR npx @modelcontextprotocol/inspector uv run --with biomcp-python biomcp run ``` ### Testing Tools 1. **Connect to server** in the inspector UI 2. **View available tools** in the tools panel 3. **Test individual tools** with sample inputs #### Example Tool Tests ```javascript // Test article search { "tool": "article_searcher", "arguments": { "genes": ["BRAF"], "diseases": ["melanoma"], "limit": 5 } } // Test trial search { "tool": "trial_searcher", "arguments": { "conditions": ["lung cancer"], "recruiting_status": "OPEN", "limit": 10 } } // Test think tool (ALWAYS first!) { "tool": "think", "arguments": { "thought": "Planning to search for BRAF mutations", "thoughtNumber": 1, "nextThoughtNeeded": true } } ``` ### Debugging with Inspector 1. **Check request/response**: View raw MCP messages 2. **Verify parameters**: Ensure correct argument format 3. **Test error handling**: Try invalid inputs 4. **Monitor performance**: Check response times ## Code Style and Standards ### Python Style - **Formatter**: ruff (line length: 79) - **Type hints**: Required for all functions - **Docstrings**: Google style for all public functions ```python def search_articles( genes: list[str], limit: int = 10 ) -> list[Article]: """Search for articles by gene names. Args: genes: List of gene symbols to search limit: Maximum number of results Returns: List of Article objects Raises: ValueError: If genes list is empty """ if not genes: raise ValueError("Genes list cannot be empty") # Implementation... ``` ### Pre-commit Hooks Automatically run on commit: - ruff formatting - ruff linting - mypy type checking - File checks (YAML, TOML, merge conflicts) Manual run: ```bash uv run pre-commit run --all-files ``` ## Continuous Integration ### GitHub Actions Workflow The CI pipeline runs: 1. **Linting and Formatting** 2. **Type Checking** 3. **Unit Tests** (required to pass) 4. **Integration Tests** (allowed to fail) 5. **Coverage Report** ### CI Configuration ```yaml # .github/workflows/test.yml structure jobs: test: strategy: matrix: python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v2 - run: make check - run: make test-offline ``` ## Debugging and Troubleshooting ### Common Issues #### Test Failures ```bash # Run failed test with more details uv run python -m pytest -vvs tests/path/to/test.py::test_name # Debug with print statements uv run python -m pytest -s # Don't capture stdout # Use debugger uv run python -m pytest --pdb # Drop to debugger on failure ``` #### Integration Test Issues Common causes: - **Rate limiting**: Add delays or use mocks - **API changes**: Update test expectations - **Network issues**: Check connectivity - **API keys**: Ensure valid keys for NCI tests ## Integration Testing ### Overview BioMCP includes integration tests that make real API calls to external services. These tests verify that our integrations work correctly with live data but can be affected by API availability, rate limits, and data changes. ### Running Integration Tests ```bash # Run all tests including integration make test # Run only integration tests pytest -m integration # Skip integration tests pytest -m "not integration" ``` ### Handling Flaky Tests Integration tests may fail or skip for various reasons: 1. **API Unavailability** - **Symptom**: Tests skip with "API returned no data" message - **Cause**: The external service is down or experiencing issues - **Action**: Re-run tests later or check service status 2. **Rate Limiting** - **Symptom**: Multiple test failures after initial successes - **Cause**: Too many requests in a short time - **Action**: Run tests with delays between them or use API tokens 3. **Data Changes** - **Symptom**: Assertions about specific data fail - **Cause**: The external data has changed (e.g., new mutations discovered) - **Action**: Update tests to use more flexible assertions ### Integration Test Design Principles #### 1. Graceful Skipping Tests should skip rather than fail when: - API returns no data - Service is unavailable - Rate limits are hit ```python if not data or data.total_count == 0: pytest.skip("API returned no data - possible service issue") ``` #### 2. Flexible Assertions Avoid assertions on specific data values that might change: ❌ **Bad**: Expecting exact mutation counts ```python assert summary.total_mutations == 1234 ``` ✅ **Good**: Checking data exists and has reasonable structure ```python assert summary.total_mutations > 0 assert hasattr(summary, 'hotspots') ``` #### 3. Retry Logic For critical tests, implement retry with delay: ```python async def fetch_with_retry(client, resource, max_attempts=2, delay=1.0): for attempt in range(max_attempts): result = await client.get(resource) if result and result.data: return result if attempt < max_attempts - 1: await asyncio.sleep(delay) return None ``` #### 4. Cache Management Clear caches before tests to ensure fresh data: ```python from biomcp.utils.request_cache import clear_cache await clear_cache() ``` ### Common Integration Test Patterns #### Testing Search Functionality ```python @pytest.mark.integration async def test_gene_search(self): client = SearchClient() results = await client.search("BRAF") # Flexible assertions assert results is not None if results.count > 0: assert results.items[0].gene_symbol == "BRAF" else: pytest.skip("No results returned - API may be unavailable") ``` #### Testing Data Retrieval ```python @pytest.mark.integration async def test_variant_details(self): client = VariantClient() variant = await client.get_variant("rs121913529") if not variant: pytest.skip("Variant not found - may have been removed from database") # Check structure, not specific values assert hasattr(variant, 'chromosome') assert hasattr(variant, 'position') ``` ### Debugging Failed Integration Tests 1. **Enable Debug Logging** ```bash BIOMCP_LOG_LEVEL=DEBUG pytest tests/integration/test_failing.py -v ``` 2. **Check API Status** - PubMed: https://www.ncbi.nlm.nih.gov/home/about/website-updates/ - ClinicalTrials.gov: https://clinicaltrials.gov/about/announcements - cBioPortal: https://www.cbioportal.org/ 3. **Inspect Response Data** ```python if not expected_data: print(f"Unexpected response: {response}") pytest.skip("Data structure changed") ``` ### Environment Variables for Testing #### API Tokens Some services provide higher rate limits with authentication: ```bash export CBIO_TOKEN="your-token-here" export PUBMED_API_KEY="your-key-here" ``` #### Offline Mode Test offline behavior: ```bash export BIOMCP_OFFLINE=true pytest tests/ ``` #### Custom Timeouts Adjust timeouts for slow connections: ```bash export BIOMCP_REQUEST_TIMEOUT=60 pytest tests/integration/ ``` ### CI/CD Considerations 1. **Separate Test Runs** ```yaml - name: Unit Tests run: pytest -m "not integration" - name: Integration Tests run: pytest -m integration continue-on-error: true ``` 2. **Scheduled Runs** ```yaml on: schedule: - cron: "0 6 * * *" # Daily at 6 AM ``` 3. **Result Monitoring**: Track integration test success rates over time to identify patterns. ### Integration Testing Best Practices 1. **Keep integration tests focused** - Test integration points, not business logic 2. **Use reasonable timeouts** - Don't wait forever for slow APIs 3. **Document expected failures** - Add comments explaining why tests might skip 4. **Monitor external changes** - Subscribe to API change notifications 5. **Provide escape hatches** - Allow skipping integration tests when needed #### Type Checking Errors ```bash # Check specific file uv run mypy src/biomcp/specific_file.py # Ignore specific error # type: ignore[error-code] # Show error codes uv run mypy --show-error-codes ``` ### Performance Testing ```python import time import pytest @pytest.mark.performance def test_search_performance(): """Ensure search completes within time limit""" start = time.time() result = search_articles("TP53", limit=100) duration = time.time() - start assert duration < 5.0 # Should complete in 5 seconds assert len(result) == 100 ``` ## Submitting Changes ### Pull Request Process 1. **Ensure all checks pass:** ```bash make check && make test ``` 2. **Update documentation** if needed 3. **Commit with clear message:** ```bash git add . git commit -m "feat: add support for variant batch queries - Add batch_variant_search function - Update tests for batch functionality - Document batch size limits" ``` 4. **Push to your fork:** ```bash git push origin feature/your-feature-name ``` 5. **Create Pull Request** with: - Clear description of changes - Link to related issues - Test results summary ### Code Review Guidelines Your PR will be reviewed for: - **Code quality** and style consistency - **Test coverage** for new features - **Documentation** updates - **Performance** impact - **Security** considerations ## Best Practices ### DO: - Write tests for new functionality - Follow existing patterns - Keep PRs focused and small - Update documentation - Run full test suite locally ### DON'T: - Skip tests to "save time" - Mix unrelated changes in one PR - Ignore linting warnings - Commit sensitive data - Break existing functionality ## Additional Resources - [MCP Documentation](https://modelcontextprotocol.org) - [pytest Documentation](https://docs.pytest.org) - [Type Hints Guide](https://mypy.readthedocs.io) - [Ruff Documentation](https://docs.astral.sh/ruff) ## Getting Help - **GitHub Issues**: Report bugs or request features - **Issues**: Ask questions or share ideas - **Pull Requests**: Submit contributions - **Documentation**: Check existing docs first Remember: Quality over speed. Take time to write good tests and clean code!

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/genomoncology/biomcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server