Skip to content

Commit 951282e

Browse files
committed
📝 add cursor rule for test file
1 parent a13116f commit 951282e

File tree

1 file changed

+283
-0
lines changed

1 file changed

+283
-0
lines changed
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
---
2+
description:
3+
globs: test/*.py
4+
alwaysApply: false
5+
---
6+
# Pytest Unit Test Rules
7+
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
13+
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`
18+
19+
## File Structure Standards
20+
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+
```
45+
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
69+
70+
### Import Organization
71+
```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
118+
from pytest_mock import MockFixture
119+
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+
155+
mocker.patch(
156+
"module.path.external_dependency",
157+
side_effect=Exception("connection error"),
158+
)
159+
160+
result = await sample_instance.method_name()
161+
162+
assert result is None # or expected error handling
163+
```
164+
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
259+
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
265+
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()`
272+
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

0 commit comments

Comments
 (0)