Files
awesome-copilot/instructions/dataverse-python-testing-debugging.instructions.md
Troy Simeon Taylor a89019fb3b Add Dataverse SDK for Python Collection (#458)
* Add Dataverse SDK for Python: 5 new instruction files (error handling, authentication, performance, testing, use cases) + 4 prompts and updated READMEs

* Delete COLLECTION_STATUS.md

* Delete ENHANCEMENT_SUMMARY.md
2025-12-05 10:38:34 +11:00

487 lines
11 KiB
Markdown

---
applyTo: '**'
---
# Dataverse SDK for Python — Testing & Debugging Strategies
Based on official Azure Functions and pytest testing patterns.
## 1. Testing Overview
### Testing Pyramid for Dataverse SDK
```
Integration Tests <- Test with real Dataverse
/\
/ \
/Unit Tests (Mocked)\
/____________________\
< Framework Tests
```
---
## 2. Unit Testing with Mocking
### Setup Test Environment
```bash
# Install test dependencies
pip install pytest pytest-cov unittest-mock
```
### Mock DataverseClient
```python
# tests/test_operations.py
import pytest
from unittest.mock import Mock, patch, MagicMock
from PowerPlatform.Dataverse.client import DataverseClient
@pytest.fixture
def mock_client():
"""Provide mocked DataverseClient."""
client = Mock(spec=DataverseClient)
return client
def test_create_account(mock_client):
"""Test account creation with mocked client."""
# Setup mock response
mock_client.create.return_value = ["id-123"]
# Call function
from my_app import create_account
result = create_account(mock_client, {"name": "Acme"})
# Verify
assert result == "id-123"
mock_client.create.assert_called_once_with("account", {"name": "Acme"})
def test_create_account_error(mock_client):
"""Test error handling in account creation."""
from PowerPlatform.Dataverse.core.errors import DataverseError
# Setup mock to raise error
mock_client.create.side_effect = DataverseError(
message="Account exists",
code="validation_error",
status_code=400
)
# Verify error is raised
from my_app import create_account
with pytest.raises(DataverseError):
create_account(mock_client, {"name": "Acme"})
```
### Test Data Structures
```python
# tests/fixtures.py
import pytest
@pytest.fixture
def sample_account():
"""Sample account record for testing."""
return {
"accountid": "id-123",
"name": "Acme Inc",
"telephone1": "555-0100",
"statecode": 0,
"createdon": "2025-01-01T00:00:00Z"
}
@pytest.fixture
def sample_accounts(sample_account):
"""Multiple sample accounts."""
return [
sample_account,
{**sample_account, "accountid": "id-124", "name": "Fabrikam"},
{**sample_account, "accountid": "id-125", "name": "Contoso"},
]
# Usage in tests
def test_process_accounts(mock_client, sample_accounts):
mock_client.get.return_value = iter([sample_accounts])
# Test processing
```
---
## 3. Mocking Common Patterns
### Mock Get with Pagination
```python
def test_pagination(mock_client, sample_accounts):
"""Test handling paginated results."""
# Mock returns generator with pages
mock_client.get.return_value = iter([
sample_accounts[:2], # Page 1
sample_accounts[2:] # Page 2
])
from my_app import process_all_accounts
result = process_all_accounts(mock_client)
assert len(result) == 3 # All pages processed
```
### Mock Bulk Operations
```python
def test_bulk_create(mock_client):
"""Test bulk account creation."""
payloads = [
{"name": "Account 1"},
{"name": "Account 2"},
]
# Mock returns list of IDs
mock_client.create.return_value = ["id-1", "id-2"]
from my_app import create_accounts
ids = create_accounts(mock_client, payloads)
assert len(ids) == 2
mock_client.create.assert_called_once_with("account", payloads)
```
### Mock Errors
```python
def test_rate_limiting_retry(mock_client):
"""Test retry logic on rate limiting."""
from PowerPlatform.Dataverse.core.errors import DataverseError
# Mock fails then succeeds
error = DataverseError(
message="Too many requests",
code="http_error",
status_code=429,
is_transient=True
)
mock_client.create.side_effect = [error, ["id-123"]]
from my_app import create_with_retry
result = create_with_retry(mock_client, "account", {})
assert result == "id-123"
assert mock_client.create.call_count == 2 # Retried
```
---
## 4. Integration Testing
### Local Development Testing
```python
# tests/test_integration.py
import pytest
from azure.identity import InteractiveBrowserCredential
from PowerPlatform.Dataverse.client import DataverseClient
@pytest.fixture
def dataverse_client():
"""Real client for integration testing."""
client = DataverseClient(
base_url="https://myorg-dev.crm.dynamics.com",
credential=InteractiveBrowserCredential()
)
return client
@pytest.mark.integration
def test_create_and_retrieve_account(dataverse_client):
"""Test creating and retrieving account (against real Dataverse)."""
# Create
account_id = dataverse_client.create("account", {
"name": "Test Account"
})[0]
# Retrieve
account = dataverse_client.get("account", account_id)
# Verify
assert account["name"] == "Test Account"
# Cleanup
dataverse_client.delete("account", account_id)
```
### Test Isolation
```python
# tests/conftest.py
import pytest
@pytest.fixture(scope="function")
def test_account(dataverse_client):
"""Create test account, cleanup after test."""
account_id = dataverse_client.create("account", {
"name": "Test Account"
})[0]
yield account_id
# Cleanup
try:
dataverse_client.delete("account", account_id)
except:
pass # Already deleted
# Usage
def test_update_account(dataverse_client, test_account):
"""Test updating account."""
dataverse_client.update("account", test_account, {"telephone1": "555-0100"})
account = dataverse_client.get("account", test_account)
assert account["telephone1"] == "555-0100"
```
---
## 5. Pytest Configuration
### pytest.ini
```ini
[pytest]
# Skip integration tests by default
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
integration: marks tests as integration (run with -m integration)
slow: marks tests as slow
unit: marks tests as unit tests
```
### Run Tests
```bash
# Unit tests only
pytest
# Unit + integration
pytest -m "unit or integration"
# Integration only
pytest -m integration
# With coverage
pytest --cov=my_app tests/
# Specific test
pytest tests/test_operations.py::test_create_account
```
---
## 6. Coverage Analysis
### Generate Coverage Report
```bash
# Run tests with coverage
pytest --cov=my_app --cov-report=html tests/
# View coverage
open htmlcov/index.html # macOS
start htmlcov/index.html # Windows
```
### Coverage Configuration (.coveragerc)
```ini
[run]
branch = True
source = my_app
[report]
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
if __name__ == .__main__.:
[html]
directory = htmlcov
```
---
## 7. Debugging with print/logging
### Enable Debug Logging
```python
import logging
import sys
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler('debug.log')
]
)
# Enable SDK logging
logging.getLogger('PowerPlatform').setLevel(logging.DEBUG)
logging.getLogger('azure').setLevel(logging.DEBUG)
# In test
def test_with_logging(mock_client):
logger = logging.getLogger(__name__)
logger.debug("Starting test")
result = my_function(mock_client)
logger.debug(f"Result: {result}")
```
### Pytest Capturing Output
```bash
# Show print/logging output in tests
pytest -s tests/
# Capture and show on failure only
pytest --tb=short tests/
```
---
## 8. Performance Testing
### Measure Operation Duration
```python
import pytest
import time
def test_bulk_create_performance(dataverse_client):
"""Test bulk create performance."""
payloads = [{"name": f"Account {i}"} for i in range(1000)]
start = time.time()
ids = dataverse_client.create("account", payloads)
duration = time.time() - start
assert len(ids) == 1000
assert duration < 10 # Should complete in under 10 seconds
print(f"Created 1000 records in {duration:.2f}s ({1000/duration:.0f} records/s)")
```
### Pytest Benchmark Plugin
```bash
pip install pytest-benchmark
```
```python
def test_query_performance(benchmark, dataverse_client):
"""Benchmark query performance."""
def get_accounts():
return list(dataverse_client.get("account", top=100))
result = benchmark(get_accounts)
assert len(result) <= 100
```
---
## 9. Common Testing Patterns
### Testing Retry Logic
```python
def test_retry_on_transient_error(mock_client):
"""Test retry on transient error."""
from PowerPlatform.Dataverse.core.errors import DataverseError
error = DataverseError(
message="Timeout",
code="http_error",
status_code=408,
is_transient=True
)
# Fail then succeed
mock_client.create.side_effect = [error, ["id-123"]]
from my_app import create_with_retry
result = create_with_retry(mock_client, "account", {})
assert result == "id-123"
```
### Testing Filter Building
```python
def test_filter_builder():
"""Test OData filter generation."""
from my_app import build_account_filter
# Test cases
assert build_account_filter(status="active") == "statecode eq 0"
assert build_account_filter(name="Acme") == "contains(name, 'Acme')"
assert build_account_filter(status="active", name="Acme") \
== "statecode eq 0 and contains(name, 'Acme')"
```
### Testing Error Handling
```python
def test_handles_missing_record(mock_client):
"""Test handling 404 errors."""
from PowerPlatform.Dataverse.core.errors import DataverseError
mock_client.get.side_effect = DataverseError(
message="Not found",
code="http_error",
status_code=404
)
from my_app import get_account_safe
result = get_account_safe(mock_client, "invalid-id")
assert result is None # Returns None instead of raising
```
---
## 10. Debugging Checklist
| Issue | Debug Steps |
|-------|-------------|
| Test fails unexpectedly | Add `-s` flag to see print output |
| Mock not called | Check method name/parameters match exactly |
| Real API failing | Check credentials, URL, permissions |
| Rate limiting in tests | Add delays or use smaller batches |
| Data not found | Verify record created and not cleaned up |
| Assertion errors | Print actual vs expected values |
---
## 11. See Also
- [Pytest Documentation](https://docs.pytest.org/)
- [unittest.mock Reference](https://docs.python.org/3/library/unittest.mock.html)
- [Azure Functions Testing](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-python#unit-testing)
- [Dataverse SDK Examples](https://github.com/microsoft/PowerPlatform-DataverseClient-Python/tree/main/examples)