From 017b9f3d83d7b057f9720e1e004213effa85a0ce Mon Sep 17 00:00:00 2001 From: Brandon Rodriguez <brodriguez8774@gmail.com> Date: Wed, 8 Nov 2023 10:49:26 -0500 Subject: [PATCH] Rework internal class inheritance to be more pythonically standardized --- .../mixins/core_mixin.py | 104 +++++++++++------- .../mixins/live_server_mixin.py | 26 ++--- .../mixins/response_mixin.py | 11 +- .../test_cases/base_test_case.py | 82 +++++++++++--- .../channels_live_server_test_case.py | 79 +++++++++---- .../test_cases/integration_test_case.py | 67 ++++++++++- .../test_cases/live_server_test_case.py | 79 +++++++++---- tests/test_cases/test_integration_case.py | 5 +- 8 files changed, 322 insertions(+), 131 deletions(-) diff --git a/django_expanded_test_cases/mixins/core_mixin.py b/django_expanded_test_cases/mixins/core_mixin.py index 7c7ad26..3712457 100644 --- a/django_expanded_test_cases/mixins/core_mixin.py +++ b/django_expanded_test_cases/mixins/core_mixin.py @@ -64,25 +64,67 @@ class DebugPrintMetaClass(type): class BaseMixin: + """Base mixin that all other project classes should inherit from in some way. + + Basically only exists to guarantee certain expected functions exist during inheritance chain, unconditionally. + Helps prevent MethodNotFound errors and make inheritance/super() calls easier to handle. + """ + @classmethod - def set_up_class(cls, *args, **kwargs): + def setUpClass(cls, *args, **kwargs): + """Test logic setup run at the start of class creation. + + Function is empty, but provided to prevent MethodNotFound errors + in case super() is called from an inheriting child. + """ + print('BaseMixin setUpClass()') pass @classmethod - def set_up_test_data(cls, *args, **kwargs): + def setUpTestData(cls, *args, **kwargs): + """Test logic setup run at the start of class creation, specifically for data setup. + + Function is empty, but provided to prevent MethodNotFound errors + in case super() is called from an inheriting child. + """ + print('BaseMixin setUpTestData()') pass - def set_up(self, *args, **kwargs): + def setUp(self, *args, **kwargs): + """Test logic setup run at the start of function/method execution. + + Function is empty, but provided to prevent MethodNotFound errors + in case super() is called from an inheriting child. + """ + print('BaseMixin setUp()') pass - def sub_test(self, *args, **kwargs): + def subTest(self, *args, **kwargs): + """Test logic setup run every time we enter a subtest. + + Function is empty, but provided to prevent MethodNotFound errors + in case super() is called from an inheriting child. + """ + print('BaseMixin subTest()') pass @classmethod - def tear_down_class(cls, *args, **kwargs): + def tearDownClass(cls, *args, **kwargs): + """Test logic setup run at the end of class execution, as part of termination/clean up. + + Function is empty, but provided to prevent MethodNotFound errors + in case super() is called from an inheriting child. + """ + print('BaseMixin tearDownClass()') pass - def tear_down(self, *args, **kwargs): + def tearDown(self, *args, **kwargs): + """Test logic setup run at the end of function/method execution, as part of termination/clean up. + + Function is empty, but provided to prevent MethodNotFound errors + in case super() is called from an inheriting child. + """ + print('BaseMixin tearDown()') pass @@ -100,18 +142,18 @@ class CoreTestCaseMixin(BaseMixin): # region Class Functions @classmethod - def set_up_class(cls, *args, debug_print=None, **kwargs): - """ - Acts as the equivalent of the UnitTesting "setUpClass()" function. - - However, since this is not inheriting from a given TestCase, calling the literal function - here would override instead. + def setUpClass(cls, *args, debug_print=None, **kwargs): + """Test logic setup run at the start of class creation. :param debug_print: Optional bool that indicates if debug output should print to console. Param overrides setting value if both param and setting are set. """ + print('CoreMixin setUpClass()') + # Call parent logic. - super().set_up_class(*args, **kwargs) + super().setUpClass(*args, **kwargs) + + print('Setting up debug_print class variable') # Check user debug_print option. if debug_print is not None: @@ -119,21 +161,20 @@ class CoreTestCaseMixin(BaseMixin): else: cls._debug_print_bool = ETC_DEBUG_PRINT + print('setting up site_root_url class variable') cls._site_root_url = None @classmethod - def set_up_test_data(cls, *args, extra_usergen_kwargs=None, **kwargs): - """ - Acts as the equivalent of the UnitTesting "setUpTestData()" function. - - However, since this is not inheriting from a given TestCase, calling the literal function - here would override instead. + def setUpTestData(cls, *args, extra_usergen_kwargs=None, **kwargs): + """Test logic setup run at the start of class creation, specifically for data setup. :param extra_usergen_kwargs: Optional extra kwargs to pass into the get_user_model().objects.create_user() function. """ + print('CoreMixin setUpTestData()') + # Call parent logic. - super().set_up_test_data(*args, **kwargs) + super().setUpTestData(*args, **kwargs) if ETC_AUTO_GENERATE_USERS: # Run logic to auto-generate test users. Setting is on by default. @@ -147,32 +188,17 @@ class CoreTestCaseMixin(BaseMixin): cls._auto_generate_test_users(extra_usergen_kwargs=extra_usergen_kwargs) - def set_up(self, *args, **kwargs): - # Call parent logic. - super().set_up(*args, **kwargs) - - def sub_test(self, *args, **kwargs): + def subTest(self, *args, **kwargs): + """Test logic setup run every time we enter a subtest. """ - Acts as the equivalent of the UnitTesting "subtTest()" function. + print('CoreMixin subTest()') - However, since this is not inheriting from a given TestCase, calling the literal function - here would override instead. - """ # Call parent logic. - super().sub_test(*args, **kwargs) + super().subTest(*args, **kwargs) # Reset display error, in case multiple subtests run and fail in a given test. self._error_displayed = False - @classmethod - def tear_down_class(cls, *args, **kwargs): - # Call parent logic. - super().tear_down_class(*args, **kwargs) - - def tear_down(self, *args, **kwargs): - # Call parent logic. - super().tear_down(*args, **kwargs) - @classmethod def _auto_generate_test_users(cls, extra_usergen_kwargs=None): """Logic to automatically generate test users. diff --git a/django_expanded_test_cases/mixins/live_server_mixin.py b/django_expanded_test_cases/mixins/live_server_mixin.py index 8cf224f..3d69f18 100644 --- a/django_expanded_test_cases/mixins/live_server_mixin.py +++ b/django_expanded_test_cases/mixins/live_server_mixin.py @@ -45,18 +45,14 @@ class LiveServerMixin(ResponseTestCaseMixin): """Universal logic for all selenium LiveServer test cases.""" @classmethod - def set_up_class(cls, debug_print=None): - """ - Acts as the equivalent of the UnitTesting "setUpClass()" function. - - However, since this is not inheriting from a given TestCase, calling the literal function - here would override instead. + def setUpClass(cls, *args, debug_print=None, **kwargs): + """Test logic setup run at the start of class creation. :param debug_print: Optional bool that indicates if debug output should print to console. Param overrides setting value if both param and setting are set. """ # Call CoreMixin setup logic. - super().set_up_class(debug_print=debug_print) + super().setUpClass(*args, debug_print=debug_print, **kwargs) # Populate some initial values. cls._driver_set = [] @@ -152,20 +148,20 @@ class LiveServerMixin(ResponseTestCaseMixin): # Create initial testing driver. cls.driver = cls.create_driver(cls) - def set_up(self): + def setUp(self, *args, **kwargs): + """Test logic setup run at the start of function/method execution.""" + # Call parent logic. - super().set_up() + super().setUp(*args, **kwargs) self._error_displayed = False - def sub_test(self, *args, **kwargs): - # Call parent logic. - super().sub_test(*args, **kwargs) - @classmethod - def tear_down_class(cls): + def tearDownClass(cls, *args, **kwargs): + """Test logic setup run at the end of class execution, as part of termination/clean up.""" + # Call parent teardown logic. - super().tear_down_class() + super().tearDownClass(*args, **kwargs) # Close all remaining driver instances for class. while len(cls._driver_set) > 0: diff --git a/django_expanded_test_cases/mixins/response_mixin.py b/django_expanded_test_cases/mixins/response_mixin.py index 2bc86c9..97377f2 100644 --- a/django_expanded_test_cases/mixins/response_mixin.py +++ b/django_expanded_test_cases/mixins/response_mixin.py @@ -27,6 +27,7 @@ from django_expanded_test_cases.constants import ( ETC_RESPONSE_DEBUG_FORM_COLOR, ETC_RESPONSE_DEBUG_USER_INFO_COLOR, ETC_OUTPUT_EMPHASIS_COLOR, + ETC_AUTO_GENERATE_USERS, ) @@ -38,18 +39,14 @@ class ResponseTestCaseMixin(CoreTestCaseMixin): """Includes testing logic used in handling Response objects.""" @classmethod - def set_up_class(cls, debug_print=None): - """ - Acts as the equivalent of the UnitTesting "setUpClass()" function. - - However, since this is not inheriting from a given TestCase, calling the literal function - here would override instead. + def setUpClass(cls, *args, debug_print=None, **kwargs): + """Test logic setup run at the start of class creation. :param debug_print: Optional bool that indicates if debug output should print to console. Param overrides setting value if both param and setting are set. """ # Run parent setup logic. - super().set_up_class(debug_print=debug_print) + super().setUpClass(*args, debug_print=debug_print, **kwargs) # region Debug Output Functions diff --git a/django_expanded_test_cases/test_cases/base_test_case.py b/django_expanded_test_cases/test_cases/base_test_case.py index 49ac925..d04c712 100644 --- a/django_expanded_test_cases/test_cases/base_test_case.py +++ b/django_expanded_test_cases/test_cases/base_test_case.py @@ -14,31 +14,81 @@ class BaseTestCase(TestCase, CoreTestCaseMixin): """Generalized testing functionality. Builds upon Django's default TestCase class.""" @classmethod - def setUpClass(cls, debug_print=None): - # Run parent setup logic. - super().setUpClass() + def setUpClass(cls, *args, debug_print=None, **kwargs): + """Test logic setup run at the start of class creation.""" + print('BaseTestCase setUpClass()') - # Also call CoreMixin setup logic. - cls.set_up_class(debug_print=debug_print) + # Call parent logic. + return_val = super().setUpClass() + CoreTestCaseMixin.setUpClass(*args, debug_print=debug_print, **kwargs) + + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val @classmethod - def setUpTestData(cls): - """""" - # Initialize default data models. - cls.set_up_test_data() + def setUpTestData(cls, *args, **kwargs): + """Test logic setup run at the start of class creation, specifically for data setup.""" + print('BaseTestCase setUpTestData()') + + # Call parent logic. + return_val = super().setUpTestData() + CoreTestCaseMixin.setUpTestData(*args, **kwargs) + + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + + def setUp(self, *args, **kwargs): + """Test logic setup run at the start of function/method execution.""" + print('BaseTestCase setUp()') - def setUp(self): - # Run parent setup logic. - super().setUp() + # Call parent logic. + return_val = super().setUp() + CoreTestCaseMixin.setUp(self, *args, **kwargs) self._error_displayed = False + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + def subTest(self, *args, **kwargs): - # Call CoreMixin logic. - self.sub_test() + """Test logic setup run every time we enter a subtest.""" + print('BaseTestCase subTest()') + + # Call parent logic. + return_val = super().subTest() + CoreTestCaseMixin.subTest(self, *args, **kwargs) + + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + + @classmethod + def tearDownClass(cls, *args, **kwargs): + """Test logic setup run at the end of class execution, as part of termination/clean up.""" + print('BaseTestCase tearDownClass()') + + # Call parent logic. + return_val = super().tearDownClass() + CoreTestCaseMixin.tearDownClass(*args, **kwargs) + + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + + def tearDown(self, *args, **kwargs): + """Test logic setup run at the end of function/method execution, as part of termination/clean up.""" + print('BaseTestCase tearDown()') + + # Call parent logic. + return_val = super().tearDown() + CoreTestCaseMixin.tearDown(self, *args, **kwargs) - # Run parent logic. - return super().subTest(*args, **kwargs) + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val def _handle_test_error(self, err): """ diff --git a/django_expanded_test_cases/test_cases/channels_live_server_test_case.py b/django_expanded_test_cases/test_cases/channels_live_server_test_case.py index 5dba371..fd2ded7 100644 --- a/django_expanded_test_cases/test_cases/channels_live_server_test_case.py +++ b/django_expanded_test_cases/test_cases/channels_live_server_test_case.py @@ -18,44 +18,75 @@ class ChannelsLiveServerTestCase(DjangoChannelsLiveServerTestCase, LiveServerMix """Uses DjangoChannels package to test functionality through selenium. Simulates web browser navigation.""" @classmethod - def setUpClass(cls, debug_print=None): - # Run parent setup logic. - super().setUpClass() + def setUpClass(cls, *args, debug_print=None, **kwargs): + """Test logic setup run at the start of class creation.""" - # Also call Mixin setup logic. - cls.set_up_class(debug_print=debug_print) + # Call parent logic. + return_val = super().setUpClass() + LiveServerMixin.setUpClass(*args, debug_print=debug_print, **kwargs) + + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val @classmethod - def setUpTestData(cls): - # Run parent setup logic. - super().setUpTestData() + def setUpTestData(cls, *args, **kwargs): + """Test logic setup run at the start of class creation, specifically for data setup.""" + + # Call parent logic. + return_val = super().setUpTestData() + LiveServerMixin.setUpTestData(*args, **kwargs) - # Initialize default data models. - cls.set_up_test_data() + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val - def setUp(self): - # Run parent setup logic. - super().setUp() + def setUp(self, *args, **kwargs): + """Test logic setup run at the start of function/method execution.""" - # Also call Mixin setup logic. - self.set_up() + # Call parent logic. + return_val = super().setUp() + LiveServerMixin.setUp(self, *args, **kwargs) self._error_displayed = False + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + def subTest(self, *args, **kwargs): - # Call CoreMixin logic. - self.sub_test() + """Test logic setup run every time we enter a subtest.""" + + # Call parent logic. + return_val = super().subTest() + LiveServerMixin.subTest(self, *args, **kwargs) - # Run parent logic. - return super().subTest(*args, **kwargs) + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val @classmethod - def tearDownClass(cls): - # Call Mixin setup logic. - cls.tear_down_class() + def tearDownClass(cls, *args, **kwargs): + """Test logic setup run at the end of class execution, as part of termination/clean up.""" + + # Call parent logic. + return_val = super().tearDownClass() + LiveServerMixin.tearDownClass(*args, **kwargs) + + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + + def tearDown(self, *args, **kwargs): + """Test logic setup run at the end of function/method execution, as part of termination/clean up.""" + + # Call parent logic. + return_val = super().tearDown() + LiveServerMixin.tearDown(self, *args, **kwargs) - # Call parent teardown logic. - super().tearDownClass() + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val # Define acceptable imports on file. diff --git a/django_expanded_test_cases/test_cases/integration_test_case.py b/django_expanded_test_cases/test_cases/integration_test_case.py index b4e8dc4..63fae7b 100644 --- a/django_expanded_test_cases/test_cases/integration_test_case.py +++ b/django_expanded_test_cases/test_cases/integration_test_case.py @@ -25,10 +25,9 @@ from django_expanded_test_cases.constants import ( ETC_INCLUDE_RESPONSE_DEBUG_USER_INFO, ETC_ALLOW_TITLE_PARTIALS, ETC_ALLOW_MESSAGE_PARTIALS, + ETC_AUTO_GENERATE_USERS, ETC_REQUEST_USER_STRICTNESS, ETC_DEFAULT_STANDARD_USER_IDENTIFIER, - ETC_RESPONSE_DEBUG_URL_COLOR, - ETC_OUTPUT_EMPHASIS_COLOR, ETC_OUTPUT_ERROR_COLOR, ETC_OUTPUT_RESET_COLOR, ETC_OUTPUT_ACTUALS_MATCH_COLOR, @@ -43,8 +42,10 @@ class IntegrationTestCase(BaseTestCase, ResponseTestCaseMixin): @classmethod def setUpClass(cls, *args, debug_print=None, **kwargs): - # Run parent setup logic. - super().setUpClass(debug_print=None) + """Test logic setup run at the start of class creation.""" + + # Call parent logic. + return_val = super().setUpClass() # Initialize url variables. try: @@ -53,6 +54,64 @@ class IntegrationTestCase(BaseTestCase, ResponseTestCaseMixin): # Login url is not defined. cls.login_url = None + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + + @classmethod + def setUpTestData(cls, *args, **kwargs): + """Test logic setup run at the start of class creation, specifically for data setup.""" + + # Call parent logic. + return_val = super().setUpTestData() + + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + + def setUp(self, *args, **kwargs): + """Test logic setup run at the start of function/method execution.""" + + # Call parent logic. + return_val = super().setUp() + + self._error_displayed = False + + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + + def subTest(self, *args, **kwargs): + """Test logic setup run every time we enter a subtest.""" + + # Call parent logic. + return_val = super().subTest() + + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + + @classmethod + def tearDownClass(cls, *args, **kwargs): + """Test logic setup run at the end of class execution, as part of termination/clean up.""" + + # Call parent logic. + return_val = super().tearDownClass() + + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + + def tearDown(self, *args, **kwargs): + """Test logic setup run at the end of function/method execution, as part of termination/clean up.""" + + # Call parent logic. + return_val = super().tearDown() + + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + # region Custom Assertions def assertResponse( diff --git a/django_expanded_test_cases/test_cases/live_server_test_case.py b/django_expanded_test_cases/test_cases/live_server_test_case.py index 020a82f..d4d2deb 100644 --- a/django_expanded_test_cases/test_cases/live_server_test_case.py +++ b/django_expanded_test_cases/test_cases/live_server_test_case.py @@ -19,44 +19,75 @@ class LiveServerTestCase(DjangoLiveServerTestCase, LiveServerMixin): """Uses Django package to test functionality through selenium. Simulates web browser navigation.""" @classmethod - def setUpClass(cls, debug_print=None): - # Run parent setup logic. - super().setUpClass() + def setUpClass(cls, *args, debug_print=None, **kwargs): + """Test logic setup run at the start of class creation.""" - # Also call Mixin setup logic. - cls.set_up_class(debug_print=debug_print) + # Call parent logic. + return_val = super().setUpClass() + LiveServerMixin.setUpClass(*args, debug_print=debug_print, **kwargs) + + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val @classmethod - def setUpTestData(cls): - # Run parent setup logic. - super().setUpTestData() + def setUpTestData(cls, *args, **kwargs): + """Test logic setup run at the start of class creation, specifically for data setup.""" + + # Call parent logic. + return_val = super().setUpTestData() + LiveServerMixin.setUpTestData(*args, **kwargs) - # Initialize default data models. - cls.set_up_test_data() + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val - def setUp(self): - # Run parent setup logic. - super().setUp() + def setUp(self, *args, **kwargs): + """Test logic setup run at the start of function/method execution.""" - # Also call Mixin setup logic. - self.set_up() + # Call parent logic. + return_val = super().setUp() + LiveServerMixin.setUp(self, *args, **kwargs) self._error_displayed = False + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + def subTest(self, *args, **kwargs): - # Call CoreMixin logic. - self.sub_test() + """Test logic setup run every time we enter a subtest.""" + + # Call parent logic. + return_val = super().subTest() + LiveServerMixin.subTest(self, *args, **kwargs) - # Run parent logic. - return super().subTest(*args, **kwargs) + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val @classmethod - def tearDownClass(cls): - # Call Mixin setup logic. - cls.tear_down_class() + def tearDownClass(cls, *args, **kwargs): + """Test logic setup run at the end of class execution, as part of termination/clean up.""" + + # Call parent logic. + return_val = super().tearDownClass() + LiveServerMixin.tearDownClass(*args, **kwargs) + + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val + + def tearDown(self, *args, **kwargs): + """Test logic setup run at the end of function/method execution, as part of termination/clean up.""" + + # Call parent logic. + return_val = super().tearDown() + LiveServerMixin.tearDown(self, *args, **kwargs) - # Call parent teardown logic. - super().tearDownClass() + # Return original python class value, if any. + # ETC setup/teardown functions never contain a return value. + return return_val # Define acceptable imports on file. diff --git a/tests/test_cases/test_integration_case.py b/tests/test_cases/test_integration_case.py index 8178631..d6972b5 100644 --- a/tests/test_cases/test_integration_case.py +++ b/tests/test_cases/test_integration_case.py @@ -12,6 +12,7 @@ from django.contrib.auth.models import AnonymousUser, Group, Permission from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.http import HttpResponse +from django.test import override_settings from django.urls import reverse # Internal Imports. @@ -7043,12 +7044,12 @@ class IntegrationClassTest__NoAutoGeneratedUser(IntegrationTestCase): """Tests for IntegrationTestCase class, specifically with no auto-generated users.""" @classmethod - @patch('django_expanded_test_cases.mixins.core_mixin.ETC_AUTO_GENERATE_USERS', False) + @patch('django_expanded_test_cases.mixins.core_mixin.ETC_AUTO_GENERATE_USERS', 'False') def setUpClass(cls): # Run parent setup logic. super().setUpClass() - @patch('django_expanded_test_cases.mixins.core_mixin.ETC_AUTO_GENERATE_USERS', False) + @patch('django_expanded_test_cases.mixins.core_mixin.ETC_AUTO_GENERATE_USERS', 'False') def test__class_users(self): # Since no users were auto-generated, all of these class attributes should come back as None. self.assertFalse(hasattr(self, 'test_superuser')) -- GitLab