Skip to content

Commit 747761f

Browse files
authored
✨ Release/v1.7.5.2
2 parents 46d737c + f99d5d4 commit 747761f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+4986
-1338
lines changed
Lines changed: 34 additions & 265 deletions
Original file line numberDiff line numberDiff line change
@@ -1,283 +1,52 @@
11
---
2-
description:
3-
globs: test/*.py
4-
alwaysApply: false
2+
globs: test/**/*.py
3+
description: Pytest Unit Test Rules for this repository
54
---
6-
# Pytest Unit Test Rules
75

8-
## Framework Requirements
9-
- **MANDATORY: Use pytest exclusively** - Do not use unittest framework
10-
- All tests must be written using pytest syntax and features
11-
- Use pytest fixtures instead of unittest setUp/tearDown methods
12-
- Use pytest assertions instead of unittest assert methods
6+
## Pytest Unit Test Rules (Concise)
137

14-
## File Naming Conventions
15-
- Test files must start with `test_`
16-
- Test class names start with `Test`, test method names start with `test_`
17-
- File names should reflect the module or functionality being tested, e.g., `test_user_service.py`
8+
### Framework
9+
- Use pytest exclusively; prefer fixtures; use pytest `assert` statements.
1810

19-
## File Structure Standards
11+
### Naming
12+
- Files `test_*`; classes `Test*`; functions `test_*`.
2013

21-
### Directory Organization
22-
```
23-
test/
24-
├── backend/ # Backend service tests
25-
│ ├── apps/ # Application layer tests
26-
│ │ ├── test_app_layer.py
27-
│ │ ├── test_app_layer_contract.py
28-
│ │ ├── test_app_layer_validation.py
29-
│ │ └── test_app_layer_errors.py
30-
│ ├── services/ # Service layer tests
31-
│ │ ├── test_user_service.py
32-
│ │ └── test_auth_service.py
33-
│ └── database/ # Database layer tests
34-
│ ├── test_models.py
35-
│ └── test_crud.py
36-
├── sdk/ # SDK tests
37-
│ ├── test_embedding_models.py
38-
│ └── test_client.py
39-
├── web_test/ # Web application tests
40-
├── assets/ # Test assets and fixtures
41-
├── pytest.ini # Pytest configuration
42-
├── .coveragerc # Coverage configuration
43-
└── requirements.txt # Test dependencies
44-
```
14+
### Imports
15+
- Order: standard library, third‑party, project.
16+
- Import only the unit under test; mock collaborators using `pytest-mock`.
4517

46-
### File Splitting Guidelines
47-
- **When a test file exceeds 500 lines or 50 test methods**, split it into multiple files
48-
- Split by functionality or feature area, not by test type
49-
- Create a dedicated subdirectory for the split test files:
50-
```
51-
test/backend/services/
52-
├── test_auth_service.py # Single file for smaller services
53-
├── test_user_service/ # Directory for split user service tests
54-
│ ├── __init__.py # Required for Python package
55-
│ ├── test_user_service_core.py # Core user operations
56-
│ ├── test_user_service_auth.py # User authentication
57-
│ ├── test_user_service_permissions.py # User permissions
58-
│ └── test_user_service_validation.py # Input validation
59-
└── test_order_service/ # Directory for split order service tests
60-
├── __init__.py
61-
├── test_order_service_core.py
62-
├── test_order_service_payment.py
63-
└── test_order_service_shipping.py
64-
```
65-
- Use consistent naming pattern: `test_<module>_<feature>.py`
66-
- Each subdirectory must contain an `__init__.py` file
67-
- Maintain logical grouping within the same directory
68-
- Keep the original module name as the directory name for clarity
18+
### Import and Mock Rules
19+
- Do not directly import external interfaces/clients/services into tests to exercise collaborators.
20+
- Patch where the dependency is imported (lookup site) using a fully‑qualified path.
21+
- Use `side_effect` to cover error paths when appropriate.
6922

70-
### Import Organization
7123
```python
72-
# 1. Standard library imports first
73-
import sys
74-
import os
75-
import types
76-
from typing import Any, Dict, List
77-
78-
# 2. Third-party library imports
79-
import pytest
80-
from pytest_mock import MockFixture # Use pytest-mock instead of unittest.mock
81-
82-
# 3. Project internal imports (after mocking dependencies if needed)
83-
from sdk.nexent.core.models.embedding_model import OpenAICompatibleEmbedding
84-
```
85-
86-
### Test Class Organization
87-
- Each test class corresponds to one class or module being tested
88-
- Use pytest fixtures instead of `setUp` and `tearDown` methods
89-
- Group test methods by functionality with descriptive method names
90-
91-
### Test Method Structure
92-
- Each test method tests only one functionality point
93-
- Use pytest assertions (`assert` statements)
94-
- Test method names should describe the test scenario, e.g., `test_create_user_success`
95-
96-
## Test Content Standards
97-
98-
### Coverage Requirements
99-
- Test normal flow and exception flow
100-
- Test boundary conditions and error handling
101-
- Use `@pytest.mark.parametrize` for parameterized testing
102-
103-
### Mocking Guidelines
104-
- Use `pytest-mock` plugin instead of `unittest.mock`
105-
- Mock database operations, API calls, and other external services
106-
- Use `side_effect` to simulate exception scenarios
107-
108-
### Assertion Standards
109-
- Use pytest assertions with clear error messages
110-
- Use `assert` statements with descriptive messages: `assert result.status == "success", f"Expected success, got {result.status}"`
111-
- Tests should fail fast and provide clear error location
112-
113-
## Code Examples
114-
115-
### Basic Test Structure (pytest-only)
116-
```python
117-
import pytest
11824
from pytest_mock import MockFixture
25+
from backend.apps.some_app import create_resource
11926

120-
# ---
121-
# Fixtures
122-
# ---
123-
124-
@pytest.fixture()
125-
def sample_instance():
126-
"""Return a sample instance with minimal viable attributes for tests."""
127-
return SampleClass(
128-
param1="value1",
129-
param2="value2"
130-
)
131-
132-
# ---
133-
# Tests for method_name
134-
# ---
135-
136-
@pytest.mark.asyncio
137-
async def test_method_success(sample_instance, mocker: MockFixture):
138-
"""method_name should return expected result when no exception is raised."""
139-
140-
expected_result = {"status": "success"}
141-
mock_dependency = mocker.patch(
142-
"module.path.external_dependency",
143-
return_value=expected_result,
144-
)
145-
146-
result = await sample_instance.method_name()
147-
148-
assert result == expected_result
149-
mock_dependency.assert_called_once()
150-
151-
@pytest.mark.asyncio
152-
async def test_method_failure(sample_instance, mocker: MockFixture):
153-
"""method_name should handle exceptions gracefully."""
154-
27+
def test_create_resource_success(mocker: MockFixture):
15528
mocker.patch(
156-
"module.path.external_dependency",
157-
side_effect=Exception("connection error"),
29+
"backend.services.model_provider_service.ModelProviderService.create",
30+
return_value={"id": "res-1"},
15831
)
159-
160-
result = await sample_instance.method_name()
161-
162-
assert result is None # or expected error handling
32+
assert create_resource({...})["id"] == "res-1"
16333
```
16434

165-
### Complex Mocking Example (pytest-mock)
166-
```python
167-
import pytest
168-
from pytest_mock import MockFixture
169-
170-
def test_complex_mocking(mocker: MockFixture):
171-
"""Test with complex dependency mocking."""
172-
# Mock external modules
173-
mock_external_module = mocker.MagicMock()
174-
mock_external_module.ExternalClass = mocker.MagicMock()
175-
176-
# Mock complex dependencies
177-
class DummyExternalClass:
178-
def __init__(self, *args, **kwargs):
179-
pass
180-
181-
def method_needed_by_tests(self, *args, **kwargs):
182-
return {}
183-
184-
mock_external_module.ExternalClass = DummyExternalClass
185-
186-
# Test the actual functionality
187-
# ... test implementation
188-
```
189-
190-
### Parameterized Testing
191-
```python
192-
@pytest.mark.parametrize("input_value,expected_output", [
193-
("valid_input", {"status": "success"}),
194-
("invalid_input", {"status": "error"}),
195-
("", {"status": "error"}),
196-
])
197-
async def test_method_with_different_inputs(sample_instance, input_value, expected_output):
198-
"""Test method with various input scenarios."""
199-
result = await sample_instance.method(input_value)
200-
assert result["status"] == expected_output["status"]
201-
```
202-
203-
### Exception Testing
204-
```python
205-
@pytest.mark.asyncio
206-
async def test_method_raises_exception(sample_instance):
207-
"""Test that method raises appropriate exception."""
208-
with pytest.raises(ValueError, match="Invalid input") as exc_info:
209-
await sample_instance.method("invalid_input")
210-
211-
assert "Invalid input" in str(exc_info.value)
212-
```
213-
214-
### State Management
215-
```python
216-
@pytest.fixture(autouse=True)
217-
def reset_state():
218-
"""Reset global state between tests."""
219-
global_state.clear()
220-
mock_objects.reset_mock()
221-
222-
@pytest.fixture
223-
def test_context():
224-
"""Provide test context with required attributes."""
225-
return TestContext(
226-
request_id="req-1",
227-
tenant_id="tenant-1",
228-
user_id="user-1"
229-
)
230-
```
231-
232-
## Best Practices
233-
234-
### 1. Test Isolation
235-
- Each test should be independent
236-
- Use `autouse=True` fixtures for state reset
237-
- Mock external dependencies completely
238-
239-
### 2. Async Testing
240-
- Use `@pytest.mark.asyncio` for async tests
241-
- Use `mocker.patch` for async operations
242-
- Use `assert_called_once()` for async assertions
243-
244-
### 3. Mock Design
245-
- Create minimal viable mock objects
246-
- Use `DummyClass` pattern for complex dependencies
247-
- Record method calls for verification
248-
249-
### 4. Test Organization
250-
- Group related tests with comment separators
251-
- Use descriptive test names
252-
- Include docstrings explaining test purpose
253-
- Split large test files into logical subdirectories
254-
255-
### 5. Error Handling
256-
- Test both success and failure scenarios
257-
- Use `pytest.raises` for exception testing
258-
- Verify error messages and types with `match` parameter
35+
### Structure and Size
36+
- Keep files under 500 lines or split by feature; include `__init__.py` in split directories; use `test_<module>_<feature>.py` names.
25937

260-
### 6. File Management
261-
- Keep test files under 500 lines or 50 test methods
262-
- Split large files by functionality, not by test type
263-
- Use consistent naming patterns for split files
264-
- Maintain logical grouping within directories
38+
### Coverage and Async
39+
- Cover success/error flows and boundaries; use `@pytest.mark.parametrize` for variants.
40+
- Use `@pytest.mark.asyncio` for async tests.
26541

266-
## Migration from unittest
267-
- Replace `unittest.TestCase` with plain functions and pytest fixtures
268-
- Replace `self.assertTrue()` with `assert` statements
269-
- Replace `unittest.mock` with `pytest-mock` plugin
270-
- Replace `setUp`/`tearDown` with pytest fixtures
271-
- Use `pytest.raises()` instead of `self.assertRaises()`
42+
### Isolation
43+
- Use `autouse=True` fixtures to reset state; fully mock external I/O and APIs.
27244

273-
## Validation Checklist
274-
- [ ] All tests use pytest framework exclusively
275-
- [ ] No unittest imports or usage
276-
- [ ] External dependencies are mocked with pytest-mock
277-
- [ ] Tests cover normal and exception flows
278-
- [ ] Async tests use proper decorators
279-
- [ ] Assertions are specific and descriptive
280-
- [ ] Test names clearly describe scenarios
281-
- [ ] Fixtures provide necessary test data
282-
- [ ] State is properly reset between tests
283-
- [ ] Large test files are split into logical subdirectories
45+
### Checklist
46+
- [ ] pytest only (no unittest)
47+
- [ ] naming conventions followed
48+
- [ ] collaborators mocked with pytest-mock
49+
- [ ] import/mocking rules followed
50+
- [ ] async tests decorated when needed
51+
- [ ] clear, specific assertions
52+
- [ ] adequate coverage (normal and exception paths)

0 commit comments

Comments
 (0)