Repository: pytest-dev/pytest-django

Issue:
Is Django Testing Tags Supported?
This is a question rather than a real issue. The documentation does not have anything about this and I couldn't find any regarding issue.

One can use [tags](https://docs.djangoproject.com/en/dev/topics/testing/tools/#tagging-tests) for Django tests, is this supported?


Discussion:
This seems related https://github.com/pytest-dev/pytest-django/issues/516
@erayerdin you can use `@pytest.mark.some_tag_here` instead
It would be nice if pytest-django translated one to the other. There are usecases where it's convenient that pytest is a no code change drop in replacement.
looks like a tiny little collect hook to upgrade it to a mark

https://github.com/django/django/blob/8bcca47e8356521f52f0738d9633befd53007cae/django/test/utils.py#L844-L853

```python
def pytest_collection_modifyitems(items):
    for item in items:
        for tag in getattr(item.obj, "tags", ()):
            item.add_marker(pytest.mark.django_tag(tag))
```
I was able to get this to work (somewhat) by setting the following in `conftest.py` based on @graingert 's suggestion:

```
def pytest_collection_modifyitems(items):
    for item in items:
        for tag in getattr(item.obj, "tags", ()):
            item.add_marker(tag)
```

However, this only works for `@tag` applied to functions - it will not work with tags on an entire `TestCase` class. I suspect this is because pytest markers don't work on an entire class?
we can add the following block of code to [pytest_collection_modifyitems](https://github.com/pytest-dev/pytest-django/blob/cb914220e0a9843376cd4126e457e02e0ae53847/pytest_django/plugin.py#L367) to add `@tag` valued as pytest markers but it raises an error if a tag is not registered in markers.
Is it a good way to go?
@bluetech do you have any idea?

Resolving patch:
diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py
--- a/pytest_django/plugin.py
+++ b/pytest_django/plugin.py
@@ -377,6 +377,48 @@ def pytest_report_header(config: pytest.Config) -> Optional[List[str]]:
     return None
 
 
+# Convert Django test tags on test classes to pytest marks.
+def pytest_collectstart(collector: pytest.Collector) -> None:
+    if "django" not in sys.modules:
+        return
+
+    if not isinstance(collector, pytest.Class):
+        return
+
+    tags = getattr(collector.obj, "tags", ())
+    if not tags:
+        return
+
+    from django.test import TransactionTestCase
+
+    if not issubclass(collector.obj, TransactionTestCase):
+        return
+
+    for tag in tags:
+        collector.add_marker(tag)
+
+
+# Convert Django test tags on test methods to pytest marks.
+def pytest_itemcollected(item: pytest.Item) -> None:
+    if "django" not in sys.modules:
+        return
+
+    if not isinstance(item, pytest.Function):
+        return
+
+    tags = getattr(item.obj, "tags", ())
+    if not tags:
+        return
+
+    from django.test import TransactionTestCase
+
+    if not issubclass(item.cls, TransactionTestCase):
+        return
+
+    for tag in tags:
+        item.add_marker(tag)
+
+
 @pytest.hookimpl(tryfirst=True)
 def pytest_collection_modifyitems(items: List[pytest.Item]) -> None:
     # If Django is not configured we don't need to bother


Test patch:
diff --git a/tests/test_unittest.py b/tests/test_unittest.py
--- a/tests/test_unittest.py
+++ b/tests/test_unittest.py
@@ -1,5 +1,5 @@
 import pytest
-from django.test import TestCase
+from django.test import TestCase, tag
 
 from .helpers import DjangoPytester
 
@@ -57,6 +57,41 @@ def tearDown(self) -> None:
         assert Item.objects.count() == 3
 
 
+@tag("tag1", "tag2")
+class TestDjangoTagsToPytestMarkers(TestCase):
+    """Django test tags are converted to Pytest markers, at the class & method
+    levels."""
+
+    @pytest.fixture(autouse=True)
+    def gimme_my_markers(self, request: pytest.FixtureRequest) -> None:
+        self.markers = {m.name for m in request.node.iter_markers()}
+
+    @tag("tag3", "tag4")  # type: ignore[misc]
+    def test_1(self) -> None:
+        assert self.markers == {"tag1", "tag2", "tag3", "tag4"}
+
+    def test_2(self) -> None:
+        assert self.markers == {"tag1", "tag2"}
+
+    @tag("tag5")  # type: ignore[misc]
+    def test_3(self) -> None:
+        assert self.markers == {"tag1", "tag2", "tag5"}
+
+
+@tag("tag1")
+class TestNonDjangoClassWithTags:
+    """Django test tags are only converted to Pytest markers if actually
+    Django tests. Use pytest markers directly for pytest tests."""
+
+    @pytest.fixture(autouse=True)
+    def gimme_my_markers(self, request: pytest.FixtureRequest) -> None:
+        self.markers = {m.name for m in request.node.iter_markers()}
+
+    @tag("tag2")  # type: ignore[misc]
+    def test_1(self) -> None:
+        assert not self.markers
+
+
 def test_sole_test(django_pytester: DjangoPytester) -> None:
     """
     Make sure the database is configured when only Django TestCase classes