Repository: fidals/shopelectro

Issue:
Divide execution of fast and slow tests.
Stop a build process if fast tests fail. This will decrease tests execution time

We already have implemented this in STB.
See this: https://github.com/fidals/stroyprombeton/issues/348 and https://github.com/fidals/stroyprombeton/issues/358


Discussion:

Resolving patch:
diff --git a/shopelectro/settings/base.py b/shopelectro/settings/base.py
--- a/shopelectro/settings/base.py
+++ b/shopelectro/settings/base.py
@@ -219,6 +219,8 @@
     },
 }
 
+TEST_RUNNER = 'shopelectro.tests.runner.CheckedTagsRunner'
+
 SITE_CREATED = datetime(2013, 1, 1)
 
 LOCALHOST = 'http://127.0.0.1:8000/'


Test patch:
diff --git a/shopelectro/tests/runner.py b/shopelectro/tests/runner.py
new file mode 100644
--- /dev/null
+++ b/shopelectro/tests/runner.py
@@ -0,0 +1,33 @@
+from django.test.runner import DiscoverRunner
+
+# @todo #639:15m Move CheckedTagsRunner to refarm-site.
+#  Reuse it in SE and STB.
+
+
+class CheckedTagsRunner(DiscoverRunner):
+    """Every test must be tagged."""
+
+    def build_suite(self, *args, **kwargs):
+        suite = super().build_suite(*args, **kwargs)
+        check_tagged_tests(suite)
+        return suite
+
+
+def check_tagged_tests(suite):
+    # get the tags processing from:
+    # django.test.runner.filter_tests_by_tags
+    # https://github.com/django/django/blob/master/django/test/runner.py#L717
+    suite_class = type(suite)
+    for test in suite:
+        if isinstance(test, suite_class):
+            check_tagged_tests(test)
+        else:
+            test_tags = set(getattr(test, 'tags', set()))
+            test_fn_name = getattr(test, '_testMethodName', str(test))
+            test_fn = getattr(test, test_fn_name, test)
+            test_fn_tags = set(getattr(test_fn, 'tags', set()))
+            if not test_tags.union(test_fn_tags):
+                raise Exception(
+                    f'{test_fn_name} is not tagged. You have to decorate it '
+                    'with tag("slow") or tag("fast").'
+                )
diff --git a/shopelectro/tests/tests_commands.py b/shopelectro/tests/tests_commands.py
--- a/shopelectro/tests/tests_commands.py
+++ b/shopelectro/tests/tests_commands.py
@@ -14,7 +14,7 @@
 
 from django.conf import settings
 from django.core.management import call_command
-from django.test import TestCase
+from django.test import TestCase, tag
 
 from shopelectro.management.commands._update_catalog import (
     update_products, update_tags
@@ -39,6 +39,7 @@ def get_tag_as_dict(group: str, tag: str):
 
 # @todo #603:30m Resurrect update_catalog tests.
 #  Now we have problems with files downloading.
+@tag('slow')
 @unittest.skip
 class UpdateProducts(TestCase):
 
@@ -142,6 +143,7 @@ def test_update_or_create_tags(self):
         self.assertEqual(updated_tags_count + create_count, Tag.objects.count())
 
 
+@tag('fast')
 class GeneratePrices(TestCase):
 
     fixtures = ['dump.json']
diff --git a/shopelectro/tests/tests_models.py b/shopelectro/tests/tests_models.py
--- a/shopelectro/tests/tests_models.py
+++ b/shopelectro/tests/tests_models.py
@@ -3,12 +3,13 @@
 
 from django.conf import settings
 from django.forms.models import model_to_dict
-from django.test import TestCase, TransactionTestCase
+from django.test import TestCase, TransactionTestCase, tag
 
 from shopelectro.models import Product, Tag, TagGroup
 from shopelectro.tests.helpers import create_doubled_tag
 
 
+@tag('fast')
 class ProductModel(TestCase):
 
     fixtures = ['dump.json']
@@ -30,6 +31,7 @@ def test_creation_deactivated_product(self):
     # @todo #589:30m Create test for Order.set_positions
 
 
+@tag('fast')
 class TagModel(TestCase):
 
     fixtures = ['dump.json']
@@ -39,9 +41,9 @@ def get_products(self):
 
     def test_get_brands_content(self):
         brand_group = TagGroup.objects.get(name=settings.BRAND_TAG_GROUP_NAME)
-        for product, tag in Tag.objects.get_brands(self.get_products()).items():
-            self.assertEquals(tag.group, brand_group)
-            self.assertIn(product, tag.products.all())
+        for product, tag_ in Tag.objects.get_brands(self.get_products()).items():
+            self.assertEquals(tag_.group, brand_group)
+            self.assertIn(product, tag_.products.all())
 
     def test_filter_by_products(self):
         sort_by_id = partial(sorted, key=lambda x: x.id)
@@ -60,6 +62,7 @@ def test_double_named_tag_saving(self):
         self.assertNotEqual(tag_from.slug, tag_to.slug)
 
 
+@tag('fast')
 class QueryQuantities(TransactionTestCase):
     """Test quantity of db-queries for different methods."""
 
diff --git a/shopelectro/tests/tests_selenium.py b/shopelectro/tests/tests_selenium.py
--- a/shopelectro/tests/tests_selenium.py
+++ b/shopelectro/tests/tests_selenium.py
@@ -9,7 +9,7 @@
 from django.conf import settings
 from django.core import mail
 from django.db.models import Count
-from django.test import override_settings
+from django.test import override_settings, tag
 from django.urls import reverse  # Ignore CPDBear
 from selenium.webdriver.common.by import By
 from selenium.webdriver.common.keys import Keys
@@ -65,6 +65,7 @@ def wait_page_loading(browser):
     )
 
 
+@tag('slow')
 @helpers.disable_celery
 @override_settings(DEBUG=True, INTERNAL_IPS=tuple())
 class Header(helpers.SeleniumTestCase):
@@ -134,6 +135,7 @@ def test_product_total_price_in_dropdown(self):
         self.assertTrue(product_price == product_total_price_in_cart)
 
 
+@tag('slow')
 class CategoryPage(helpers.SeleniumTestCase):
 
     PRODUCTS_TO_LOAD = 48
@@ -410,6 +412,7 @@ def test_load_more_after_filtering(self):
         self.assertEqual(new_product_cards, 96)
 
 
+@tag('slow')
 class ProductPage(helpers.SeleniumTestCase):
 
     PRODUCT_ID = 1
@@ -604,6 +607,7 @@ def test_feedback_filter(self):
         self.assertTrue(feedbacks)
 
 
+@tag('slow')
 @helpers.disable_celery
 class OrderPage(helpers.SeleniumTestCase):
 
@@ -823,6 +827,7 @@ def test_change_cart_and_pay_with_yandex_kassa(self):
         self.assertIn(success_page_domain, self.browser.current_url)
 
 
+@tag('slow')
 class SitePage(helpers.SeleniumTestCase):
 
     def setUp(self):
@@ -879,6 +884,7 @@ def test_accordion_minimize_by_double_click(self):
         self.assertFalse(accordion_content.is_displayed())
 
 
+@tag('slow')
 @helpers.disable_celery
 @override_settings(DEBUG=True, INTERNAL_IPS=tuple())
 class YandexMetrika(helpers.SeleniumTestCase):
@@ -1033,6 +1039,7 @@ def test_cart_page_open(self):
         self.assertTrue('CART_OPEN' in self.reached_goals)
 
 
+@tag('slow')
 class Search(helpers.SeleniumTestCase):
 
     QUERY = 'Cate'
diff --git a/shopelectro/tests/tests_selenium_admin.py b/shopelectro/tests/tests_selenium_admin.py
--- a/shopelectro/tests/tests_selenium_admin.py
+++ b/shopelectro/tests/tests_selenium_admin.py
@@ -9,6 +9,7 @@
 
 from django.conf import settings
 from django.urls import reverse
+from django.test import tag
 from selenium.webdriver.common.by import By
 from selenium.webdriver.common.keys import Keys
 from selenium.webdriver.support import expected_conditions as EC
@@ -53,6 +54,7 @@ def setUp(self):
         self.signin()
 
 
+@tag('slow')
 @helpers.enable_russian_language
 class AdminPage(AdminSeleniumTestCase):
     """Selenium-based tests for Admin page UI."""
@@ -330,6 +332,7 @@ def test_yandex_feedback_request(self):
         self.assertTrue('Письмо с отзывом успешно отправлено' in self.browser.page_source)
 
 
+@tag('slow')
 @helpers.enable_russian_language
 class TableEditor(AdminSeleniumTestCase):
     """Selenium-based tests for Table Editor [TE]."""
diff --git a/shopelectro/tests/tests_selenium_mobile.py b/shopelectro/tests/tests_selenium_mobile.py
--- a/shopelectro/tests/tests_selenium_mobile.py
+++ b/shopelectro/tests/tests_selenium_mobile.py
@@ -5,8 +5,7 @@
 Every Selenium-based test suite uses fixture called dump.json.
 """
 from django.conf import settings
-from django.test import LiveServerTestCase
-from django.test import override_settings
+from django.test import LiveServerTestCase, override_settings, tag
 from django.urls import reverse
 from selenium import webdriver
 from selenium.webdriver.common.by import By
@@ -46,6 +45,7 @@ def tearDownClass(cls):
         super().tearDownClass()
 
 
+@tag('slow')
 class Mobile(MobileSeleniumTestCase):
 
     def setUp(self):
diff --git a/shopelectro/tests/tests_templatetags.py b/shopelectro/tests/tests_templatetags.py
--- a/shopelectro/tests/tests_templatetags.py
+++ b/shopelectro/tests/tests_templatetags.py
@@ -1,9 +1,10 @@
-from django.test import TestCase
+from django.test import TestCase, tag
 
 from shopelectro.models import Category
 from shopelectro.templatetags.se_extras import roots
 
 
+@tag('fast')
 class TemplateTags(TestCase):
 
     fixtures = ['dump.json']
diff --git a/shopelectro/tests/tests_views.py b/shopelectro/tests/tests_views.py
--- a/shopelectro/tests/tests_views.py
+++ b/shopelectro/tests/tests_views.py
@@ -15,7 +15,7 @@
 from django.conf import settings
 from django.db.models import Q
 from django.http import HttpResponse
-from django.test import TestCase
+from django.test import TestCase, tag
 from django.urls import reverse
 from django.utils.translation import ugettext as _
 
@@ -75,6 +75,7 @@ def get_category_page(
         ))
 
 
+@tag('fast')
 class CatalogTags(BaseCatalogTestCase):
 
     def test_category_page_contains_all_tags(self):
@@ -223,6 +224,7 @@ def test_non_existing_tags_404(self):
         self.assertEqual(response.status_code, 404)
 
 
+@tag('fast')
 class CatalogPagination(BaseCatalogTestCase):
 
     def test_pagination_numbering(self):
@@ -302,6 +304,7 @@ def test_pagination_canonical(self):
         )
 
 
+@tag('fast')
 class LoadMore(TestCase):
 
     fixtures = ['dump.json']
@@ -349,6 +352,7 @@ def test_pagination_numbering_rest_page(self):
         )
 
 
+@tag('fast')
 class SitemapXML(TestCase):
     """
     Test Sitemap XML.
@@ -379,6 +383,7 @@ def test_models_urls(self):
         self.assertEqual(response.status_code, 200)
 
 
+@tag('fast')
 class RobotsPage(TestCase):
 
     fixtures = ['dump.json']
@@ -390,6 +395,7 @@ def test_robots_success(self):
         self.assertEqual(self.response.status_code, 200)
 
 
+@tag('fast')
 class SitemapPage(TestCase):
 
     fixtures = ['dump.json']
@@ -409,6 +415,7 @@ def test_sitemap_self_link_on_page(self):
         self.assertIn(sitemap_url_slug, self.response.content.decode('utf-8'))
 
 
+@tag('fast')
 class YandexKassa(TestCase):
     """
     Test yandex check order and yandex aviso.
@@ -467,6 +474,7 @@ def test_yandex_aviso_body(self):
         self.assertContains(response, 'code="1"')
 
 
+@tag('fast')
 class ProductPage(TestCase):
 
     fixtures = ['dump.json']
@@ -526,6 +534,7 @@ def test_related_product_images(self):
         self.assertNotIn('logo', img_path)
 
 
+@tag('fast')
 class ProductPageSchema(TestCase):
 
     fixtures = ['dump.json']
@@ -550,6 +559,7 @@ def test_not_available(self):
         )
 
 
+@tag('fast')
 class ProductsWithoutContent(TestCase):
 
     def test_products_without_images(self):
@@ -561,6 +571,7 @@ def test_products_without_text(self):
         self.assertEqual(response.status_code, 200)
 
 
+@tag('fast')
 class TestSearch(TestCase):
     """Test all search methods: search page and autocompletes."""
 
diff --git a/shopelectro/tests/tests_wholesale.py b/shopelectro/tests/tests_wholesale.py
--- a/shopelectro/tests/tests_wholesale.py
+++ b/shopelectro/tests/tests_wholesale.py
@@ -1,12 +1,13 @@
 from math import ceil
 
 from django.conf import settings
-from django.test import TestCase
+from django.test import TestCase, tag
 
 from shopelectro.cart import SECart
 from shopelectro.models import Product
 
 
+@tag('fast')
 class SECartTest(TestCase):
 
     fixtures = ['dump.json']