Добавлены тесты для CoreCommand

This commit is contained in:
2026-07-20 12:05:57 +03:00
parent 05ce5b7cf4
commit 781e885ffb
9 changed files with 823 additions and 7 deletions

View File

@@ -68,7 +68,14 @@ class CoreCommand(BaseCommand):
:param length Общая длина
"""
p_len = len(str(value)) + len(name) + 2
points = "." * (length - p_len)
if not name:
p_len = length - len(str(value)) - 1
else:
p_len = length - len(str(value)) - len(name) - 2
if p_len <= 0:
points = ""
else:
points = " " + ("." * p_len) + " "
print(f"{name}{points}{value}")

View 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

View File

@@ -5,7 +5,7 @@
# Все права защищены. Лицензия: MIT
"""
Описание mock-классов для CoreEnumChoicesTests
Описание mock-классов для тестов CoreEnum
"""
from net.xeaf.rack.core import CoreEnum

View File

@@ -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)

View 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

View 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)

View 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)

View File

@@ -42,11 +42,21 @@
```
Для конструктора паттерн аналогичен: `Тестовый класс для DemoClass.__init__()`.
- Оформление тестовых методов: Докстринг внутри каждого тестового метода должен быть многострочным, оформленным с пустыми строками до и после текста.
- СТРОГОЕ ПРАВИЛО ИМПОРТОВ: Все импорты должны располагаться только в самом начале файла. Импорты внутри классов или методов не допускаются.
- Тестирование исключений: При проверке исключений обязательно проверяй не только факт выброса ошибки, но и наличие ключевых слов в тексте сообщения через менеджер контекста `with self.assertRaises(ValueError) as context:`.
- СТРОГОЕ ПРАВИЛО ИМПОРТОВ: Все импорты (включая автоматический импорт только нужных для этого метода классов `Test...` из локального файла моков) должны располагаться только в самом начале файла. Импорты внутри классов или методов не допускаются.
- МЕТОДОЛОГИЯ 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. Без вводных фраз и пояснений после кода.
### АЛГОРИТМ ТВОЕЙ РАБОТЫ
Пользователь пришлет тебе команду.
- Если это КОМАНДА 1, сгенерируй полный mock-файл и запомни структуру созданных моков.
- Если это КОМАНДА 2, проанализируй целевой метод (с учетом исключения для `__init__`), вспомни структуру моков из первого шага, автоматически выбери подходящие классы `Test...`, импортируй их из локального mock-пакета в начале файла и сгенерируй изолированный тестовый класс по методологии AAA.
- Если это КОМАНДА 2, проанализируй целевой метод (с учетом исключения для `__init__`), вспомни структуру моков из первого шага, автоматически выбери подходящие классы `Test...`, импортируй их из локального mock-пакета в начале файла и сгенерируй изолированный тестовый класс, строго следуя методологии AAA и правилам проверки исключений.

View File

@@ -49,6 +49,17 @@
- Оформление вспомогательных/служебных классов (моков): Все вспомогательные классы для теста должны быть описаны в начале файла, строго ПОСЛЕ импортов, но ДО основного класса тестов. Описывать их внутри методов запрещено.
- Именование вспомогательных классов: Все они обязаны иметь префикс `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
with self.assertRaises(ValueError) as context: