This guide provides detailed instructions for contributing to the Django Iranian Cities project, specifically focusing on writing tests using Pytest. The project follows a specific structure and style for its test files to ensure consistency, readability, and maintainability. Please follow the guidelines below when creating new test files or contributing to existing ones.
- File Structure
- Class and Function Naming Conventions
- Markers
- Docstrings
- Type Annotations
- Using Fixtures
- Example Test Files
- Running Tests
Each test file should follow a structured format:
- Class-based Tests: Organize your tests within classes. Each class should typically represent a single unit or aspect of the feature you are testing.
- Test Functions: Each test function should test a single behavior or outcome.
- Pytest Markers: At the top of each test file, include a
pytestmark
variable that is a list of markers relevant to that file. - Imports: Group imports logically, starting with standard library imports, followed by third-party imports, and finally local application imports.
import pytest
from django.http import HttpRequest
from django.contrib.auth import get_user_model
from unittest.mock import patch, MagicMock
from my_app.admin import MyModelAdmin
from my_app.models import MyModel
import sys
pytestmark = [
pytest.mark.django_db,
pytest.mark.admin,
pytest.mark.skipif(sys.version_info < (3, 8), reason="Python 3.8 or higher required")
]
User = get_user_model()
class TestMyModelAdmin:
"""Class for testing MyModelAdmin functionality."""
def test_some_functionality(self) -> None:
# Test logic goes here
pass
-
Class Names: Use the
Test
prefix followed by the name of the class or module being tested. For example, if you're testing theMyModelAdmin
class, name your test classTestMyModelAdmin
. -
Function Names: Start function names with
test_
followed by a description of the functionality being tested. For example,test_formfield_for_foreignkey_sets_queryset
.
Each test file should include a pytestmark
variable at the top, which is a list of markers. These markers help categorize tests and can include Django-specific markers (e.g., django_db
), custom markers (e.g., admin
, settings_checks
), and conditional markers (e.g., skipif
).
pytestmark = [
pytest.mark.django_db,
pytest.mark.admin,
pytest.mark.skipif(sys.version_info < (3, 8), reason="Python 3.8 or higher required")
]
Every test function should include a docstring that describes the purpose of the test. The docstring should explain what the test is verifying, why it's important, and any relevant details about the setup or expected outcome.
def test_formfield_for_foreignkey_sets_queryset(self) -> None:
"""
Test that the formfield for the ForeignKey correctly sets the queryset.
This ensures that the correct queryset is used in the admin interface
when selecting a ForeignKey relationship.
"""
pass
All test functions and methods should include type annotations. This improves code clarity and helps with static analysis tools.
def test_formfield_for_foreignkey_sets_queryset(self) -> None:
pass
Fixtures in Pytest are a powerful way to manage test dependencies. Use fixtures to set up the state your tests need, such as database records, user authentication, or Django settings.
def test_sage_iranian_cities_settings(self, settings) -> None:
"""
Test the SageIranianCitiesSettings class with correct settings.
This test verifies that the settings class reads and applies the correct
settings values.
"""
settings.MY_SETTING = True
# Test logic goes here
pass
import pytest
from django.contrib.admin import AdminSite
from django.http import HttpRequest
from django.contrib.auth import get_user_model
from unittest.mock import patch, MagicMock
from my_app.admin import MyModelAdmin
from my_app.models import MyModel
import sys
pytestmark = [
pytest.mark.django_db,
pytest.mark.admin,
pytest.mark.skipif(sys.version_info < (3, 8), reason="Python 3.8 or higher required")
]
User = get_user_model()
class TestMyModelAdmin:
"""Class for testing MyModelAdmin functionality."""
def test_formfield_for_foreignkey_sets_queryset(self) -> None:
"""
Test that the formfield for the ForeignKey correctly sets the queryset.
This ensures that the correct queryset is used in the admin interface
when selecting a ForeignKey relationship.
"""
admin_site = AdminSite()
request = HttpRequest()
request.user = User.objects.create_superuser('admin', '[email protected]', 'password')
model_admin = MyModelAdmin(model=MyModel, admin_site=admin_site)
db_field = MyModel._meta.get_field('related_model')
with patch.object(MyModelAdmin, 'get_field_queryset', return_value=MyModel.objects.filter(name="Test")) as mock_get_field_queryset:
formfield = model_admin.formfield_for_foreignkey(db_field, request)
mock_get_field_queryset.assert_called_once_with(None, db_field, request)
assert isinstance(formfield.widget, widgets.ForeignKeyRawIdWidget)
assert list(formfield.queryset) == [MyModel.objects.get(name="Test")]
import pytest
from my_app.checks import check_my_app_config
from my_app.exceptions import MyAppConfigurationError
from my_app.conf import MyAppSettings
import sys
pytestmark = [
pytest.mark.settings_checks,
pytest.mark.skipif(sys.version_info < (3, 8), reason="Python 3.8 or higher required")
]
class TestMyAppConfig:
"""Test MyApp configuration and settings."""
def test_check_my_app_config_correct_settings(self, settings) -> None:
"""
Test the MyApp configuration checker with correct settings.
This test verifies that no errors are returned when all required settings
are correctly configured.
"""
settings.MY_SETTING_ENABLED = True
errors = check_my_app_config({})
assert len(errors) == 0
def test_check_my_app_config_invalid_type(self, settings) -> None:
"""
Test the MyApp configuration checker with invalid type settings.
This test ensures that a MyAppConfigurationError is raised when
settings are of the wrong type.
"""
settings.MY_SETTING_ENABLED = "true"
with pytest.raises(MyAppConfigurationError):
MyAppSettings()
To run the tests in the Django Iranian Cities project, use the following command:
pytest
This will automatically discover and run all the test files in the project.
By following the structure and guidelines outlined in this document, you can contribute high-quality, consistent tests to the Django Iranian Cities project. These practices ensure that tests are easy to read, maintain, and extend, fostering a robust and reliable codebase.
Please make sure to review your tests for compliance with this guide before submitting any pull requests. Thank you for contributing to Django Iranian Cities!