add new
Some checks failed
K8S Fission Deployment / Deployment fission functions (push) Failing after 12s
Some checks failed
K8S Fission Deployment / Deployment fission functions (push) Failing after 12s
This commit is contained in:
290
.agents/skills/pytest/references/async-testing.md
Normal file
290
.agents/skills/pytest/references/async-testing.md
Normal file
@@ -0,0 +1,290 @@
|
||||
# Async Testing
|
||||
|
||||
## Table of Contents
|
||||
- [Setup](#setup)
|
||||
- [Basic Async Tests](#basic-async-tests)
|
||||
- [Async Fixtures](#async-fixtures)
|
||||
- [Testing Async Generators](#testing-async-generators)
|
||||
- [Timeouts](#timeouts)
|
||||
|
||||
## Setup
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
pip install pytest-asyncio
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```toml
|
||||
# pyproject.toml
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto" # Automatically handle async tests
|
||||
# OR
|
||||
asyncio_mode = "strict" # Require explicit @pytest.mark.asyncio
|
||||
```
|
||||
|
||||
### With pytest.ini
|
||||
|
||||
```ini
|
||||
[pytest]
|
||||
asyncio_mode = auto
|
||||
```
|
||||
|
||||
## Basic Async Tests
|
||||
|
||||
### Simple Async Test
|
||||
|
||||
```python
|
||||
import pytest
|
||||
|
||||
# With asyncio_mode = "auto", decorator is optional
|
||||
async def test_async_function():
|
||||
result = await async_operation()
|
||||
assert result == "success"
|
||||
|
||||
# With asyncio_mode = "strict", decorator is required
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_with_marker():
|
||||
result = await async_operation()
|
||||
assert result == "success"
|
||||
```
|
||||
|
||||
### Async Test Class
|
||||
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
class TestAsyncOperations:
|
||||
async def test_fetch_data(self):
|
||||
data = await fetch_data()
|
||||
assert data is not None
|
||||
|
||||
async def test_process_data(self):
|
||||
result = await process_data({"key": "value"})
|
||||
assert result["processed"] is True
|
||||
```
|
||||
|
||||
### Testing Async Exceptions
|
||||
|
||||
```python
|
||||
import pytest
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_exception():
|
||||
with pytest.raises(ValueError):
|
||||
await async_function_that_raises()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_exception_message():
|
||||
with pytest.raises(ValueError, match="Invalid input"):
|
||||
await validate_input("")
|
||||
```
|
||||
|
||||
## Async Fixtures
|
||||
|
||||
### Basic Async Fixture
|
||||
|
||||
```python
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
async def async_client():
|
||||
"""Async fixture for HTTP client."""
|
||||
async with httpx.AsyncClient() as client:
|
||||
yield client
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_call(async_client):
|
||||
response = await async_client.get("https://api.example.com/data")
|
||||
assert response.status_code == 200
|
||||
```
|
||||
|
||||
### Async Database Fixture
|
||||
|
||||
```python
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop():
|
||||
"""Create event loop for session-scoped async fixtures."""
|
||||
import asyncio
|
||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
async def engine():
|
||||
"""Session-scoped async engine."""
|
||||
engine = create_async_engine("postgresql+asyncpg://...")
|
||||
yield engine
|
||||
await engine.dispose()
|
||||
|
||||
@pytest.fixture
|
||||
async def db_session(engine):
|
||||
"""Function-scoped database session with rollback."""
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
async with AsyncSession(engine) as session:
|
||||
yield session
|
||||
await session.rollback()
|
||||
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.drop_all)
|
||||
```
|
||||
|
||||
### Fixture with Async Setup/Teardown
|
||||
|
||||
```python
|
||||
@pytest.fixture
|
||||
async def resource():
|
||||
# Async setup
|
||||
resource = await create_resource()
|
||||
await resource.initialize()
|
||||
|
||||
yield resource
|
||||
|
||||
# Async teardown
|
||||
await resource.cleanup()
|
||||
await resource.close()
|
||||
```
|
||||
|
||||
## Testing Async Generators
|
||||
|
||||
### Async Generator Function
|
||||
|
||||
```python
|
||||
async def async_data_generator():
|
||||
for i in range(5):
|
||||
await asyncio.sleep(0.1)
|
||||
yield i
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_generator():
|
||||
results = []
|
||||
async for item in async_data_generator():
|
||||
results.append(item)
|
||||
|
||||
assert results == [0, 1, 2, 3, 4]
|
||||
```
|
||||
|
||||
### Async Context Manager
|
||||
|
||||
```python
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
@asynccontextmanager
|
||||
async def async_resource():
|
||||
resource = await create_resource()
|
||||
try:
|
||||
yield resource
|
||||
finally:
|
||||
await resource.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_context_manager():
|
||||
async with async_resource() as resource:
|
||||
result = await resource.operation()
|
||||
assert result is not None
|
||||
```
|
||||
|
||||
## Timeouts
|
||||
|
||||
### Test Timeout
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import pytest
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.timeout(5) # 5 second timeout
|
||||
async def test_slow_operation():
|
||||
result = await potentially_slow_operation()
|
||||
assert result is not None
|
||||
```
|
||||
|
||||
### Using asyncio.wait_for
|
||||
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_with_timeout():
|
||||
try:
|
||||
result = await asyncio.wait_for(
|
||||
slow_operation(),
|
||||
timeout=2.0
|
||||
)
|
||||
assert result is not None
|
||||
except asyncio.TimeoutError:
|
||||
pytest.fail("Operation timed out")
|
||||
```
|
||||
|
||||
### Testing Timeout Behavior
|
||||
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_timeout_is_raised():
|
||||
"""Verify function properly times out."""
|
||||
with pytest.raises(asyncio.TimeoutError):
|
||||
await asyncio.wait_for(
|
||||
never_completes(),
|
||||
timeout=0.1
|
||||
)
|
||||
```
|
||||
|
||||
## Concurrent Tests
|
||||
|
||||
### Testing Concurrent Operations
|
||||
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_concurrent_requests():
|
||||
async with httpx.AsyncClient() as client:
|
||||
# Run 10 requests concurrently
|
||||
tasks = [
|
||||
client.get(f"https://api.example.com/items/{i}")
|
||||
for i in range(10)
|
||||
]
|
||||
responses = await asyncio.gather(*tasks)
|
||||
|
||||
assert all(r.status_code == 200 for r in responses)
|
||||
```
|
||||
|
||||
### Testing Race Conditions
|
||||
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_counter_thread_safety():
|
||||
counter = AsyncCounter()
|
||||
|
||||
async def increment():
|
||||
for _ in range(100):
|
||||
await counter.increment()
|
||||
|
||||
# Run 10 concurrent incrementers
|
||||
await asyncio.gather(*[increment() for _ in range(10)])
|
||||
|
||||
assert counter.value == 1000
|
||||
```
|
||||
|
||||
## Event Loop Configuration
|
||||
|
||||
### Custom Event Loop
|
||||
|
||||
```python
|
||||
import pytest
|
||||
import asyncio
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop_policy():
|
||||
"""Use uvloop for faster async tests."""
|
||||
import uvloop
|
||||
return uvloop.EventLoopPolicy()
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop(event_loop_policy):
|
||||
asyncio.set_event_loop_policy(event_loop_policy)
|
||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
```
|
||||
Reference in New Issue
Block a user