Добавлены тесты для CoreCommand
This commit is contained in:
@@ -68,7 +68,14 @@ class CoreCommand(BaseCommand):
|
|||||||
:param length Общая длина
|
:param length Общая длина
|
||||||
"""
|
"""
|
||||||
|
|
||||||
p_len = len(str(value)) + len(name) + 2
|
if not name:
|
||||||
points = "." * (length - p_len)
|
p_len = length - len(str(value)) - 1
|
||||||
|
else:
|
||||||
|
p_len = length - len(str(value)) - len(name) - 2
|
||||||
|
|
||||||
print(f"{name} {points} {value}")
|
if p_len <= 0:
|
||||||
|
points = ""
|
||||||
|
else:
|
||||||
|
points = " " + ("." * p_len) + " "
|
||||||
|
|
||||||
|
print(f"{name}{points}{value}")
|
||||||
|
|||||||
25
net/xeaf/rack/tests/core/_core_command_mocks.py
Normal file
25
net/xeaf/rack/tests/core/_core_command_mocks.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# DRF Rack
|
||||||
|
# Библиотека классов расширений для Django REST Framework
|
||||||
|
#
|
||||||
|
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||||
|
# Все права защищены. Лицензия: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
Описание mock-классов для тестов CoreCommand
|
||||||
|
"""
|
||||||
|
|
||||||
|
from net.xeaf.rack.core import CoreCommand
|
||||||
|
|
||||||
|
|
||||||
|
class TestCommand(CoreCommand):
|
||||||
|
"""
|
||||||
|
Тестовая реализация команды для проверки базовых методов вывода отчетов
|
||||||
|
"""
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
"""
|
||||||
|
Обязательный метод для Django BaseCommand
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
# Все права защищены. Лицензия: MIT
|
# Все права защищены. Лицензия: MIT
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Описание mock-классов для CoreEnumChoicesTests
|
Описание mock-классов для тестов CoreEnum
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from net.xeaf.rack.core import CoreEnum
|
from net.xeaf.rack.core import CoreEnum
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# DRF Rack
|
||||||
|
# Библиотека классов расширений для Django REST Framework
|
||||||
|
#
|
||||||
|
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||||
|
# Все права защищены. Лицензия: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
Описание класса CoreCommandClearDatabaseTests
|
||||||
|
"""
|
||||||
|
|
||||||
|
from net.xeaf.rack.core.testing import CoreUserStoryTestCase
|
||||||
|
from net.xeaf.rack.models.user_story import AccountTestData
|
||||||
|
from ._core_command_mocks import TestCommand
|
||||||
|
|
||||||
|
|
||||||
|
class CoreCommandClearDatabaseTests(CoreUserStoryTestCase):
|
||||||
|
"""
|
||||||
|
Тестовый класс для CoreCommand._clear_database()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_clear_database_removes_all_user_stories_from_db(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет, что после вызова _clear_database известные объекты User Story удаляются из БД
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
command_instance = TestCommand()
|
||||||
|
|
||||||
|
# Проверяем, что перед тестом база наполнена (Иванов существует в БД)
|
||||||
|
ivanov_id = AccountTestData.IVANOV_ID
|
||||||
|
account_model = AccountTestData.get_ivanov_account().__class__
|
||||||
|
self.assertTrue(account_model.objects.filter(id=ivanov_id).exists())
|
||||||
|
|
||||||
|
# Act
|
||||||
|
command_instance._clear_database()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
# Проверяем реальное состояние БД: объектов больше быть не должно
|
||||||
|
self.assertFalse(account_model.objects.filter(id=ivanov_id).exists())
|
||||||
|
self.assertEqual(account_model.objects.count(), 0)
|
||||||
177
net/xeaf/rack/tests/core/core_command_is_debug_or_test_tests.py
Normal file
177
net/xeaf/rack/tests/core/core_command_is_debug_or_test_tests.py
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# DRF Rack
|
||||||
|
# Библиотека классов расширений для Django REST Framework
|
||||||
|
#
|
||||||
|
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||||
|
# Все права защищены. Лицензия: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
Описание класса CoreCommandIsDebugOrTestTests
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from net.xeaf.rack.core import CoreCommand
|
||||||
|
from net.xeaf.rack.core.testing import CoreSimpleTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class CoreCommandIsDebugOrTestTests(CoreSimpleTestCase):
|
||||||
|
"""
|
||||||
|
Тестовый класс для CoreCommand._is_debug_or_test()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_is_debug_or_test_returns_true_when_debug_enabled(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет, что метод возвращает True при включенном DEBUG
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
original_debug = settings.DEBUG
|
||||||
|
settings.DEBUG = True
|
||||||
|
original_argv = sys.argv.copy()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Act
|
||||||
|
result = CoreCommand._is_debug_or_test()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertTrue(result)
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
settings.DEBUG = original_debug
|
||||||
|
sys.argv = original_argv
|
||||||
|
|
||||||
|
def test_is_debug_or_test_returns_true_when_test_in_argv(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет, что метод возвращает True, когда в аргументах командной строки присутствует 'test'
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
original_debug = settings.DEBUG
|
||||||
|
settings.DEBUG = False
|
||||||
|
original_argv = sys.argv.copy()
|
||||||
|
sys.argv = ['manage.py', 'test']
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Act
|
||||||
|
result = CoreCommand._is_debug_or_test()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertTrue(result)
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
settings.DEBUG = original_debug
|
||||||
|
sys.argv = original_argv
|
||||||
|
|
||||||
|
def test_is_debug_or_test_returns_false_when_debug_disabled_and_test_not_in_argv(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет, что метод возвращает False, когда DEBUG выключен и 'test' отсутствует в аргументах
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
original_debug = settings.DEBUG
|
||||||
|
settings.DEBUG = False
|
||||||
|
original_argv = sys.argv.copy()
|
||||||
|
sys.argv = ['manage.py', 'runserver']
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Act
|
||||||
|
result = CoreCommand._is_debug_or_test()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertFalse(result)
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
settings.DEBUG = original_debug
|
||||||
|
sys.argv = original_argv
|
||||||
|
|
||||||
|
def test_is_debug_or_test_returns_true_when_debug_enabled_and_test_in_argv(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет, что метод возвращает True, когда и DEBUG включен, и 'test' присутствует в аргументах
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
original_debug = settings.DEBUG
|
||||||
|
settings.DEBUG = True
|
||||||
|
original_argv = sys.argv.copy()
|
||||||
|
sys.argv = ['manage.py', 'test', '--verbosity=2']
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Act
|
||||||
|
result = CoreCommand._is_debug_or_test()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertTrue(result)
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
settings.DEBUG = original_debug
|
||||||
|
sys.argv = original_argv
|
||||||
|
|
||||||
|
def test_is_debug_or_test_handles_test_substring_correctly(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет, что метод правильно обрабатывает наличие подстроки 'test' в аргументах
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
original_debug = settings.DEBUG
|
||||||
|
settings.DEBUG = False
|
||||||
|
original_argv = sys.argv.copy()
|
||||||
|
sys.argv = ['manage.py', 'testing_tool']
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Act
|
||||||
|
result = CoreCommand._is_debug_or_test()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
# Метод должен проверять точное вхождение 'test', а не подстроку
|
||||||
|
self.assertFalse(result)
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
settings.DEBUG = original_debug
|
||||||
|
sys.argv = original_argv
|
||||||
|
|
||||||
|
def test_is_debug_or_test_returns_false_with_empty_argv(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет поведение метода при пустом списке аргументов
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
original_debug = settings.DEBUG
|
||||||
|
settings.DEBUG = False
|
||||||
|
original_argv = sys.argv.copy()
|
||||||
|
sys.argv = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Act
|
||||||
|
result = CoreCommand._is_debug_or_test()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
self.assertFalse(result)
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
settings.DEBUG = original_debug
|
||||||
|
sys.argv = original_argv
|
||||||
|
|
||||||
|
def test_is_debug_or_test_handles_nonexistent_settings_attribute(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет, что метод корректно обрабатывает отсутствие атрибута DEBUG в настройках
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
original_debug = getattr(settings, 'DEBUG', None)
|
||||||
|
if hasattr(settings, 'DEBUG'):
|
||||||
|
delattr(settings, 'DEBUG')
|
||||||
|
original_argv = sys.argv.copy()
|
||||||
|
sys.argv = ['manage.py', 'runserver']
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Act & Assert
|
||||||
|
# В этом случае должно возникнуть исключение, так как settings.DEBUG отсутствует
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
CoreCommand._is_debug_or_test()
|
||||||
|
finally:
|
||||||
|
# Cleanup
|
||||||
|
if original_debug is not None:
|
||||||
|
settings.DEBUG = original_debug
|
||||||
|
sys.argv = original_argv
|
||||||
317
net/xeaf/rack/tests/core/core_command_report_int_value_tests.py
Normal file
317
net/xeaf/rack/tests/core/core_command_report_int_value_tests.py
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
# DRF Rack
|
||||||
|
# Библиотека классов расширений для Django REST Framework
|
||||||
|
#
|
||||||
|
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||||
|
# Все права защищены. Лицензия: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
Описание класса CoreCommandReportIntValueTests
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
from net.xeaf.rack.core import CoreCommand
|
||||||
|
from net.xeaf.rack.core.testing import CoreSimpleTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class CoreCommandReportIntValueTests(CoreSimpleTestCase):
|
||||||
|
"""
|
||||||
|
Тестовый класс для CoreCommand._report_int_value()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Подготовка тестовой среды: перехват stdout для проверки вывода
|
||||||
|
"""
|
||||||
|
|
||||||
|
super().setUp()
|
||||||
|
self.stdout_capture = StringIO()
|
||||||
|
self.original_stdout = sys.stdout
|
||||||
|
sys.stdout = self.stdout_capture
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""
|
||||||
|
Восстановление stdout после каждого теста
|
||||||
|
"""
|
||||||
|
|
||||||
|
sys.stdout = self.original_stdout
|
||||||
|
super().tearDown()
|
||||||
|
|
||||||
|
def test_report_int_value_with_default_length(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод целочисленного значения с длиной по умолчанию
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
name = "Total records"
|
||||||
|
value = 42
|
||||||
|
expected_length = 70
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_int_value(name, value)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue().strip()
|
||||||
|
self.assertIn(name, output)
|
||||||
|
self.assertIn(str(value), output)
|
||||||
|
self.assertTrue(output.endswith(str(value)))
|
||||||
|
# Проверяем, что общая длина строки соответствует ожидаемой
|
||||||
|
# (без учета возможных пробелов в конце)
|
||||||
|
self.assertEqual(len(output), expected_length)
|
||||||
|
|
||||||
|
def test_report_int_value_with_custom_length(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод целочисленного значения с пользовательской длиной
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
name = "Count"
|
||||||
|
value = 100
|
||||||
|
custom_length = 50
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_int_value(name, value, custom_length)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue().strip()
|
||||||
|
self.assertIn(name, output)
|
||||||
|
self.assertIn(str(value), output)
|
||||||
|
self.assertEqual(len(output), custom_length)
|
||||||
|
|
||||||
|
def test_report_int_value_with_small_value(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод маленького целочисленного значения
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
name = "Errors"
|
||||||
|
value = 0
|
||||||
|
expected_length = 70
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_int_value(name, value)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue().strip()
|
||||||
|
self.assertIn(name, output)
|
||||||
|
self.assertIn("0", output)
|
||||||
|
self.assertEqual(len(output), expected_length)
|
||||||
|
|
||||||
|
def test_report_int_value_with_large_value(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод большого целочисленного значения
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
name = "Processed items"
|
||||||
|
value = 1234567890
|
||||||
|
expected_length = 70
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_int_value(name, value)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue().strip()
|
||||||
|
self.assertIn(name, output)
|
||||||
|
self.assertIn(str(value), output)
|
||||||
|
self.assertEqual(len(output), expected_length)
|
||||||
|
|
||||||
|
def test_report_int_value_with_negative_value(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод отрицательного целочисленного значения
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
name = "Balance"
|
||||||
|
value = -50
|
||||||
|
expected_length = 70
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_int_value(name, value)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue().strip()
|
||||||
|
self.assertIn(name, output)
|
||||||
|
self.assertIn("-50", output)
|
||||||
|
self.assertEqual(len(output), expected_length)
|
||||||
|
|
||||||
|
def test_report_int_value_with_empty_name(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод значения с пустым названием
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
name = ""
|
||||||
|
value = 42
|
||||||
|
expected_length = 70
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_int_value(name, value)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue().strip()
|
||||||
|
self.assertIn(str(value), output)
|
||||||
|
# При пустом имени будут только точки и значение
|
||||||
|
self.assertTrue(output.startswith("."))
|
||||||
|
self.assertEqual(len(output), expected_length)
|
||||||
|
|
||||||
|
def test_report_int_value_with_long_name(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод значения с длинным названием
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
name = "This is a very long name that should be truncated or handled"
|
||||||
|
value = 42
|
||||||
|
expected_length = 70
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_int_value(name, value)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue().strip()
|
||||||
|
self.assertIn(name, output)
|
||||||
|
self.assertIn(str(value), output)
|
||||||
|
self.assertEqual(len(output), expected_length)
|
||||||
|
|
||||||
|
def test_report_int_value_with_minimal_length(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод значения с минимальной длиной
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
name = "X"
|
||||||
|
value = 1
|
||||||
|
minimal_length = 5 # Должно быть достаточно для "X ... 1"
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_int_value(name, value, minimal_length)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue().strip()
|
||||||
|
self.assertIn(name, output)
|
||||||
|
self.assertIn(str(value), output)
|
||||||
|
self.assertEqual(len(output), minimal_length)
|
||||||
|
|
||||||
|
def test_report_int_value_with_length_smaller_than_needed(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет поведение при длине меньше необходимой для отображения
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
name = "Test"
|
||||||
|
value = 12345
|
||||||
|
too_small_length = 10
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_int_value(name, value, too_small_length)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue().strip()
|
||||||
|
# Метод должен обработать ситуацию без ошибок
|
||||||
|
# Возможно, обрежет название или значение
|
||||||
|
self.assertIsInstance(output, str)
|
||||||
|
self.assertLessEqual(len(output), too_small_length)
|
||||||
|
|
||||||
|
def test_report_int_value_with_zero_length(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет поведение при нулевой длине
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
name = "Test"
|
||||||
|
value = 42
|
||||||
|
zero_length = 0
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_int_value(name, value, zero_length)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue().strip()
|
||||||
|
# При нулевой длине вывод должен быть пустым или минимальным
|
||||||
|
self.assertIsInstance(output, str)
|
||||||
|
|
||||||
|
def test_report_int_value_format_with_dots(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет, что между названием и значением выводятся точки
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
name = "Records"
|
||||||
|
value = 100
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_int_value(name, value)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue().strip()
|
||||||
|
# Проверяем, что между названием и значением есть точки
|
||||||
|
name_end_index = output.find(name) + len(name)
|
||||||
|
value_start_index = output.rfind(str(value))
|
||||||
|
middle_part = output[name_end_index:value_start_index]
|
||||||
|
self.assertTrue(all(c == '.' for c in middle_part.replace(' ', '')))
|
||||||
|
|
||||||
|
def test_report_int_value_with_multiple_calls(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет последовательный вывод нескольких значений
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
test_data = [
|
||||||
|
("First", 100),
|
||||||
|
("Second", 200),
|
||||||
|
("Third", 300)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Act
|
||||||
|
for name, value in test_data:
|
||||||
|
CoreCommand._report_int_value(name, value)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue()
|
||||||
|
lines = output.strip().split('\n')
|
||||||
|
self.assertEqual(len(lines), len(test_data))
|
||||||
|
|
||||||
|
for i, (name, value) in enumerate(test_data):
|
||||||
|
self.assertIn(name, lines[i])
|
||||||
|
self.assertIn(str(value), lines[i])
|
||||||
|
self.assertEqual(len(lines[i]), 70)
|
||||||
|
|
||||||
|
def test_report_int_value_with_unicode_characters(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод значения с названием, содержащим Unicode-символы
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
name = "Количество записей"
|
||||||
|
value = 42
|
||||||
|
expected_length = 70
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_int_value(name, value)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue().strip()
|
||||||
|
self.assertIn(name, output)
|
||||||
|
self.assertIn(str(value), output)
|
||||||
|
self.assertEqual(len(output), expected_length)
|
||||||
|
|
||||||
|
def test_report_int_value_with_maximum_integer(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод максимального целочисленного значения
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Arrange
|
||||||
|
name = "Max int"
|
||||||
|
value = sys.maxsize
|
||||||
|
expected_length = 70
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_int_value(name, value)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue().strip()
|
||||||
|
self.assertIn(name, output)
|
||||||
|
self.assertIn(str(value), output)
|
||||||
|
self.assertEqual(len(output), expected_length)
|
||||||
229
net/xeaf/rack/tests/core/core_command_report_separator_tests.py
Normal file
229
net/xeaf/rack/tests/core/core_command_report_separator_tests.py
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
# DRF Rack
|
||||||
|
# Библиотека классов расширений для Django REST Framework
|
||||||
|
#
|
||||||
|
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||||
|
# Все права защищены. Лицензия: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
Описание класса CoreCommandReportSeparatorTests
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
from net.xeaf.rack.core import CoreCommand
|
||||||
|
from net.xeaf.rack.core.testing import CoreSimpleTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class CoreCommandReportSeparatorTests(CoreSimpleTestCase):
|
||||||
|
"""
|
||||||
|
Тестовый класс для CoreCommand._report_separator()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Подготовка тестовой среды: перехват stdout для проверки вывода
|
||||||
|
"""
|
||||||
|
super().setUp()
|
||||||
|
self.stdout_capture = StringIO()
|
||||||
|
self.original_stdout = sys.stdout
|
||||||
|
sys.stdout = self.stdout_capture
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""
|
||||||
|
Восстановление stdout после каждого теста
|
||||||
|
"""
|
||||||
|
sys.stdout = self.original_stdout
|
||||||
|
super().tearDown()
|
||||||
|
|
||||||
|
def test_report_separator_with_default_parameters(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод разделителя с параметрами по умолчанию
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
expected_length = 70
|
||||||
|
expected_separator = '=' * expected_length
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_separator()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue()
|
||||||
|
self.assertIn(expected_separator, output)
|
||||||
|
self.assertEqual(output.count('\n'), 1) # Только одна строка с разделителем
|
||||||
|
|
||||||
|
def test_report_separator_with_custom_length(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод разделителя с пользовательской длиной
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
custom_length = 50
|
||||||
|
expected_separator = '=' * custom_length
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_separator(length=custom_length)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue()
|
||||||
|
self.assertIn(expected_separator, output)
|
||||||
|
self.assertEqual(len(expected_separator), custom_length)
|
||||||
|
|
||||||
|
def test_report_separator_with_single_line(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод одинарного разделителя
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
expected_separator = '-' * 70
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_separator(double=False)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue()
|
||||||
|
self.assertIn(expected_separator, output)
|
||||||
|
self.assertNotIn('=' * 70, output)
|
||||||
|
|
||||||
|
def test_report_separator_with_double_line(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод двойного разделителя (поведение по умолчанию)
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
expected_separator = '=' * 70
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_separator(double=True)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue()
|
||||||
|
self.assertIn(expected_separator, output)
|
||||||
|
|
||||||
|
def test_report_separator_with_first_new_line(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет добавление пустой строки перед разделителем
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
expected_separator = '=' * 70
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_separator(first_new_line=True)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue()
|
||||||
|
self.assertTrue(output.startswith('\n'))
|
||||||
|
self.assertIn(expected_separator, output)
|
||||||
|
|
||||||
|
def test_report_separator_with_last_new_line(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет добавление пустой строки после разделителя
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
expected_separator = '=' * 70
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_separator(last_new_line=True)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue()
|
||||||
|
self.assertIn(expected_separator, output)
|
||||||
|
self.assertTrue(output.endswith('\n'))
|
||||||
|
|
||||||
|
def test_report_separator_with_both_new_lines(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет добавление пустых строк до и после разделителя
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
expected_separator = '=' * 70
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_separator(first_new_line=True, last_new_line=True)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue()
|
||||||
|
self.assertTrue(output.startswith('\n'))
|
||||||
|
self.assertTrue(output.endswith('\n'))
|
||||||
|
self.assertIn(expected_separator, output)
|
||||||
|
|
||||||
|
def test_report_separator_with_minimal_length(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод разделителя минимальной длины
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
minimal_length = 1
|
||||||
|
expected_separator = '=' * minimal_length
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_separator(length=minimal_length)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue()
|
||||||
|
self.assertIn(expected_separator, output)
|
||||||
|
self.assertEqual(len(expected_separator), minimal_length)
|
||||||
|
|
||||||
|
def test_report_separator_with_large_length(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет вывод разделителя большой длины
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
large_length = 200
|
||||||
|
expected_separator = '=' * large_length
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_separator(length=large_length)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue()
|
||||||
|
self.assertIn(expected_separator, output)
|
||||||
|
self.assertEqual(len(expected_separator), large_length)
|
||||||
|
|
||||||
|
def test_report_separator_combination_all_parameters(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет комбинацию всех параметров
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
custom_length = 60
|
||||||
|
expected_separator = '-' * custom_length
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_separator(
|
||||||
|
length=custom_length,
|
||||||
|
double=False,
|
||||||
|
first_new_line=True,
|
||||||
|
last_new_line=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue()
|
||||||
|
self.assertTrue(output.startswith('\n'))
|
||||||
|
self.assertTrue(output.endswith('\n'))
|
||||||
|
self.assertIn(expected_separator, output)
|
||||||
|
self.assertNotIn('=' * custom_length, output)
|
||||||
|
|
||||||
|
def test_report_separator_no_output_with_zero_length(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет поведение при нулевой длине разделителя
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_separator(length=0)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue()
|
||||||
|
# При длине 0 разделитель не выводится, но пустые строки могут быть
|
||||||
|
self.assertNotIn('=' * 70, output)
|
||||||
|
self.assertNotIn('-' * 70, output)
|
||||||
|
|
||||||
|
def test_report_separator_handles_negative_length(self):
|
||||||
|
"""
|
||||||
|
Тест проверяет поведение при отрицательной длине разделителя
|
||||||
|
"""
|
||||||
|
# Arrange
|
||||||
|
negative_length = -10
|
||||||
|
|
||||||
|
# Act
|
||||||
|
CoreCommand._report_separator(length=negative_length)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
output = self.stdout_capture.getvalue()
|
||||||
|
# Отрицательная длина вызовет ошибку или приведет к пустой строке
|
||||||
|
# В зависимости от реализации, но метод должен отработать без ошибок
|
||||||
|
self.assertIsInstance(output, str)
|
||||||
@@ -42,11 +42,21 @@
|
|||||||
```
|
```
|
||||||
Для конструктора паттерн аналогичен: `Тестовый класс для DemoClass.__init__()`.
|
Для конструктора паттерн аналогичен: `Тестовый класс для DemoClass.__init__()`.
|
||||||
- Оформление тестовых методов: Докстринг внутри каждого тестового метода должен быть многострочным, оформленным с пустыми строками до и после текста.
|
- Оформление тестовых методов: Докстринг внутри каждого тестового метода должен быть многострочным, оформленным с пустыми строками до и после текста.
|
||||||
- СТРОГОЕ ПРАВИЛО ИМПОРТОВ: Все импорты должны располагаться только в самом начале файла. Импорты внутри классов или методов не допускаются.
|
- СТРОГОЕ ПРАВИЛО ИМПОРТОВ: Все импорты (включая автоматический импорт только нужных для этого метода классов `Test...` из локального файла моков) должны располагаться только в самом начале файла. Импорты внутри классов или методов не допускаются.
|
||||||
- Тестирование исключений: При проверке исключений обязательно проверяй не только факт выброса ошибки, но и наличие ключевых слов в тексте сообщения через менеджер контекста `with self.assertRaises(ValueError) as context:`.
|
|
||||||
|
- МЕТОДОЛОГИЯ AAA (Arrange, Act, Assert): Внутри тестовых методов код должен быть строго разделен на три логических блока с помощью комментариев `# Arrange`, `# Act` и `# Assert`. Покрывай как позитивные, так и негативные сценарии (invalid data, неавторизованный доступ, отсутствие прав, крайние состояния, деструктивные действия с данными, если применимо).
|
||||||
|
|
||||||
|
- ТЕСТИРОВАНИЕ ИСКЛЮЧЕНИЙ: При проверке исключений обязательно проверяй не только сам факт выброса ошибки, но и наличие ключевых слов в тексте сообщения об ошибке через менеджер контекста. Пример оформления:
|
||||||
|
```python
|
||||||
|
# Act & Assert
|
||||||
|
with self.assertRaises(ValueError) as context:
|
||||||
|
TestEmptyEnum.choices()
|
||||||
|
self.assertIn("error message content", str(context.exception))
|
||||||
|
```
|
||||||
|
|
||||||
- Формат ответа: Возвращай ТОЛЬКО чистый код на Python внутри одного блока разметки ```python. Без вводных фраз и пояснений после кода.
|
- Формат ответа: Возвращай ТОЛЬКО чистый код на Python внутри одного блока разметки ```python. Без вводных фраз и пояснений после кода.
|
||||||
|
|
||||||
### АЛГОРИТМ ТВОЕЙ РАБОТЫ
|
### АЛГОРИТМ ТВОЕЙ РАБОТЫ
|
||||||
Пользователь пришлет тебе команду.
|
Пользователь пришлет тебе команду.
|
||||||
- Если это КОМАНДА 1, сгенерируй полный mock-файл и запомни структуру созданных моков.
|
- Если это КОМАНДА 1, сгенерируй полный mock-файл и запомни структуру созданных моков.
|
||||||
- Если это КОМАНДА 2, проанализируй целевой метод (с учетом исключения для `__init__`), вспомни структуру моков из первого шага, автоматически выбери подходящие классы `Test...`, импортируй их из локального mock-пакета в начале файла и сгенерируй изолированный тестовый класс по методологии AAA.
|
- Если это КОМАНДА 2, проанализируй целевой метод (с учетом исключения для `__init__`), вспомни структуру моков из первого шага, автоматически выбери подходящие классы `Test...`, импортируй их из локального mock-пакета в начале файла и сгенерируй изолированный тестовый класс, строго следуя методологии AAA и правилам проверки исключений.
|
||||||
|
|||||||
@@ -49,6 +49,17 @@
|
|||||||
- Оформление вспомогательных/служебных классов (моков): Все вспомогательные классы для теста должны быть описаны в начале файла, строго ПОСЛЕ импортов, но ДО основного класса тестов. Описывать их внутри методов запрещено.
|
- Оформление вспомогательных/служебных классов (моков): Все вспомогательные классы для теста должны быть описаны в начале файла, строго ПОСЛЕ импортов, но ДО основного класса тестов. Описывать их внутри методов запрещено.
|
||||||
- Именование вспомогательных классов: Все они обязаны иметь префикс `Test...` (например, `TestEnum`, `TestEmptyEnum`, `TestMockSerializer`). Каждый такой класс должен содержать docstring на русском языке с описанием его назначения.
|
- Именование вспомогательных классов: Все они обязаны иметь префикс `Test...` (например, `TestEnum`, `TestEmptyEnum`, `TestMockSerializer`). Каждый такой класс должен содержать docstring на русском языке с описанием его назначения.
|
||||||
- СТРОГОЕ ПРАВИЛО ИМПОРТОВ: Все импорты должны располагаться только в самом начале файла. Импорты внутри классов или методов не допускаются.
|
- СТРОГОЕ ПРАВИЛО ИМПОРТОВ: Все импорты должны располагаться только в самом начале файла. Импорты внутри классов или методов не допускаются.
|
||||||
|
|
||||||
|
- МЕТОДОЛОГИЯ AAA (Arrange, Act, Assert): Внутри тестовых методов код должен быть строго разделен на три логических блока с помощью комментариев `# Arrange`, `# Act` и `# Assert`. Покрывай как позитивные, так и негативные сценарии (invalid data, неавторизованный доступ, отсутствие прав, крайние состояния, деструктивные действия с данными, если применимо).
|
||||||
|
|
||||||
|
- ТЕСТИРОВАНИЕ ИСКЛЮЧЕНИЙ: При проверке исключений обязательно проверяй не только сам факт выброса ошибки, но и наличие ключевых слов в тексте сообщения об ошибке через менеджер контекста. Пример оформления:
|
||||||
|
```python
|
||||||
|
# Act & Assert
|
||||||
|
with self.assertRaises(ValueError) as context:
|
||||||
|
TestEmptyEnum.choices()
|
||||||
|
self.assertIn("error message content", str(context.exception))
|
||||||
|
```
|
||||||
|
|
||||||
- Тестирование исключений: При проверке исключений обязательно проверяй не только сам факт выброса ошибки, но и наличие ключевых слов в тексте сообщения об ошибке через менеджер контекста. Пример:
|
- Тестирование исключений: При проверке исключений обязательно проверяй не только сам факт выброса ошибки, но и наличие ключевых слов в тексте сообщения об ошибке через менеджер контекста. Пример:
|
||||||
```python
|
```python
|
||||||
with self.assertRaises(ValueError) as context:
|
with self.assertRaises(ValueError) as context:
|
||||||
|
|||||||
Reference in New Issue
Block a user