nose2
This rule file provides opinionated best practices for writing, organizing, and configuring tests using nose2, emphasizing modern Python development workflows and common pitfalls.
خود اسکیل
# nose2 Best Practices
`nose2` is the modern successor to `nose`, built upon Python's `unittest` module. It offers powerful test discovery, a plugin architecture, and helpers for writing clean, maintainable test suites. This guide outlines essential best practices for leveraging `nose2` effectively in your projects.
## 1. Project Layout & Naming
Always organize your tests in a dedicated `tests/` directory at the root of your project. Name test files consistently using the `test_*.py` pattern. This ensures `nose2`'s default discovery works reliably.
**❌ BAD: Scattered tests or inconsistent naming**
```
# Project root
my_module/
feature_a.py
feature_a_tests.py # Inconsistent naming
test_feature_b.py # Mixed with source
```
**✅ GOOD: Dedicated `tests/` directory and `test_*.py` naming**
```
# Project root
my_module/
__init__.py
feature_a.py
feature_b.py
tests/
__init__.py
test_feature_a.py
test_feature_b.py
```
## 2. Configuration (`pyproject.toml`)
Centralize all `nose2` configuration within your `pyproject.toml` file under the `[tool.nose2]` table. This keeps your project configuration tidy, version-controlled, and compatible with modern Python tooling. Avoid `unittest.cfg` or `nose2.cfg` for new projects.
**❌ BAD: Using legacy `.cfg` files**
```ini
# unittest.cfg
[unittest]
start-dir = tests
```
**✅ GOOD: `pyproject.toml` for all configuration**
```toml
# pyproject.toml
[tool.nose2]
start-dir = "tests"
code-directories = ["src"] # Add your source code directories to sys.path if needed
plugins = [
"nose2.plugins.coverage",
"nose2.plugins.params",
"nose2.plugins.junitxml",
]
exclude-plugins = [
"nose2.plugins.loader.functions", # Avoid loading plain functions as tests
]
[tool.nose2.coverage]
always-on = true
coverage-combine = true # Essential for multiprocessing
coverage-report = ["term", "html"]
coverage-config = ".coveragerc"
```
## 3. Test Structure & Assertions
All tests must inherit from `unittest.TestCase`. Inside your test methods, use `self.assert*` methods provided by `unittest.TestCase` for detailed failure reports. Avoid raw `assert` statements, as they offer less diagnostic information on failure.
**❌ BAD: Raw `assert` statements**
```python
import unittest
class MyFeatureTest(unittest.TestCase):
def test_addition(self):
result = 1 + 1
assert result == 3, "Addition failed" # Vague error message
```
**✅ GOOD: `unittest.TestCase` and `self.assert*` methods**
```python
import unittest
class MyFeatureTest(unittest.TestCase):
def test_addition(self0):
result = 1 + 1
self.assertEqual(result, 2, "Addition should be 2") # Specific error message
self.assertNotEqual(result, 3)
```
## 4. Setup/Teardown
Utilize `setUp` and `tearDown` methods within your `unittest.TestCase` classes for per-test setup and cleanup. For class-level setup/teardown, use `setUpClass` and `tearDownClass`. Avoid module-level `setup()`/`teardown()` functions unless maintaining legacy `nose` suites.
**❌ BAD: Module-level setup/teardown (legacy `nose` style)**
```python
# test_db_operations.py
DB_CONNECTION = None
def setup_module():
global DB_CONNECTION
DB_CONNECTION = connect_to_db()
def teardown_module():
DB_CONNECTION.close()
class DatabaseTest(unittest.TestCase):
def test_query(self):
# ... use DB_CONNECTION ...
pass
```
**✅ GOOD: Class-level `setUp`/`tearDown` for fixtures**
```python
import unittest
from my_module.db import connect_to_db, disconnect_from_db
class DatabaseTest(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
"""Connect to the database once for all tests in this class."""
cls.db_connection = connect_to_db()
@classmethod
def tearDownClass(cls) -> None:
"""Disconnect from the database after all tests in this class."""
disconnect_from_db(cls.db_connection)
def setUp(self) -> None:
"""Reset database state before each test."""
self.db_connection.clear_data()
def test_insert_user(self) -> None:
self.db_connection.insert("users", {"name": "Alice"})
user = self.db_connection.get("users", "Alice")
self.assertIsNotNone(user)
def test_delete_user(self) -> None:
self.db_connection.insert("users", {"name": "Bob"})
self.db_connection.delete("users", "Bob")
user = self.db_connection.get("users", "Bob")
self.assertIsNone(user)
```
## 5. Parameterized Tests
Leverage `nose2.tools.params` for writing data-driven tests. This significantly reduces boilerplate when you need to run the same test logic with different input data. Ensure the `nose2.plugins.params` plugin is enabled in `pyproject.toml`.
**❌ BAD: Duplicating test logic for different inputs**
```python
import unittest
class CalculatorTest(unittest.TestCase):
def test_add_positive(self):
self.assertEqual(add(1, 2), 3)
def test_add_negative(self):
self.assertEqual(add(-1, -2), -3)
def test_add_zero(self):
self.assertEqual(add(0, 5), 5)
```
**✅ GOOD: Using `nose2.tools.params`**
```python
import unittest
from nose2.tools import params
class CalculatorTest(unittest.TestCase):
@params(
(1, 2, 3),
(-1, -2, -3),
(0, 5, 5),
(10, -3, 7)
)
def test_add(self, a: int, b: int, expected: int) -> None:
self.assertEqual(add(a, b), expected)
```
## 6. Coverage & CI Integration
Enable the `coverage` plugin and set `coverage-combine = true` in `pyproject.toml` to ensure accurate coverage reporting, especially in environments using multiprocessing. Run `nose2 -v` in your CI pipeline and enforce a coverage threshold to prevent regressions.
**Actionable Steps:**
1. Install `coverage`: `pip install coverage`
2. Configure `pyproject.toml` (as shown in section 2).
3. Run tests with coverage in CI: `nose2 -v`
4. Use `coverage report` or `coverage html` to view results.
5. Enforce a minimum coverage percentage in your CI/CD pipeline.
## 7. Mocking Strategies
Use Python's built-in `unittest.mock` library for isolating units under test from their dependencies. Prefer `mock.patch` as a decorator or context manager for clear and scoped mocking.
**❌ BAD: Manually replacing dependencies or global state**
```python
# my_module.py
import external_service
def process_data(data):
return external_service.send(data)
# test_my_module.py
import unittest
import my_module
import external_service # This will be the real service
class MyModuleTest(unittest.TestCase):
def test_process_data(self):
# This is bad, it affects the actual external_service module
original_send = external_service.send
external_service.send = lambda x: "mocked_response"
result = my_module.process_data("test_data")
self.assertEqual(result, "mocked_response")
external_service.send = original_send # Must remember to clean up
```
**✅ GOOD: Using `unittest.mock.patch`**
```python
import unittest
from unittest import mock
from my_module import process_data # Assuming process_data is in my_module.py
class MyModuleTest(unittest.TestCase):
@mock.patch('my_module.external_service.send') # Patch the dependency where it's looked up
def test_process_data_decorator(self, mock_send):
mock_send.return_value = "mocked_response_from_decorator"
result = process_data("test_data")
mock_send.assert_called_once_with("test_data")
self.assertEqual(result, "mocked_response_from_decorator")
def test_process_data_context_manager(self):
with mock.patch('my_module.external_service.send') as mock_send:
mock_send.return_value = "mocked_response_from_context"
result = process_data("another_data")
mock_send.assert_called_once_with("another_data")
self.assertEqual(result, "mocked_response_from_context")
```
## 8. Type Hints
Apply type hints consistently to your test code, especially for method signatures (`setUp`, `test_*`, `tearDown`). This improves readability, enables static analysis, and makes your tests easier to understand and maintain.
**❌ BAD: Untyped test methods**
```python
class MyTest(unittest.TestCase):
def setUp(self):
self.client = create_test_client()
def test_endpoint(self):
response = self.client.get('/api/data')
self.assertEqual(response.status_code, 200)
```
**✅ GOOD: Type-hinted test methods**
```python
from typing import Any
import unittest
class MyTest(unittest.TestCase):
client: Any # Define attributes used across methods
def setUp(self) -> None:
self.client = create_test_client()
def test_endpoint(self) -> None:
response = self.client.get('/api/data')
self.assertEqual(response.status_code, 200)
```
## 9. Common Pitfalls & Gotchas
* **Not staying current:** `nose2` is actively maintained. Always use the latest stable release (0.15.x as of May 2024) to benefit from bug fixes, new features, and Python version compatibility.
* **Overly complex `setUp`:** Keep `setUp` methods focused on preparing the immediate test context. If setup becomes too complex, consider breaking tests into smaller, more focused classes or using factories.
* **Mixing `nose` and `nose2` idioms:** `nose2` is a distinct project. Avoid relying on `nose`-specific features that `nose2` explicitly does not support (e.g., certain module-level functions or specific test discovery quirks).
* **When to consider `pytest`:** If `nose2`'s `unittest`-centric approach feels restrictive, or you find yourself fighting its conventions, consider migrating to `pytest`. `pytest` offers a more "Pythonic" test syntax (raw `assert`), powerful fixtures, and a vast plugin ecosystem. `pytest` can run `nose`-style tests, making migration easier.
نصبش چطور است
1. Open the tool and start a new conversation, project or template. 2. Paste the text below wherever the tool keeps standing instructions — usually called system prompt, instructions, or persona. 3. Save it so it applies to every run rather than a single message.