Аналізатор файлової системи на Python з використанням TDD покроково (Частина 3)

Це передостанній етап нашого посібника та реалізація інструменту CMD, хоча останній в циклі тестування-код-тест. Сподіваюсь, на цей момент ви вже отримали уявлення про TDD з Частини 1 та Частини 2 (і, сподіваюся, вам це сподобалось), і тепер ви знаєте, чого очікувати. Далі, використовуючи набуті навички, ми застосуємо ті ж самі техніки та підхід, але цього разу для інших функцій нашого додатку. Наприклад, для обчислення розмірів файлів та фільтрації за типом файлів.

І ви правильно здогадались, спочатку ми пишемо нашу функцію calculate_size, щоб вона просто існувала і не повертала нічого:

# file_system_analyzer/analyzer/size_analysis.py  
def calculate_size(directory):  
 pass

Знову ж таки, набір тестів служить визначенням модуля і наших чітких очікувань від нього, як для успішного випадку, так і для випадку помилки валідації.

# tests/test_size_analysis.py  
import pytest  
from file_system_analyzer.analyzer.size_analysis import calculate_size  
from file_system_analyzer.analyzer.errors import DirectoryNotFoundError  

def test_calculate_size():  
 result = calculate_size('/path/to/directory')  
 assert result == {'text': 100, 'image': 200}  

def test_calculate_size_directory_not_found():  
 with pytest.raises(DirectoryNotFoundError):  
 calculate_size('/invalid/directory')

Очевидно, що зараз обидва тести не проходять. Наша задача — створити модуль так, щоб він відповідав вимогам.
Щоб заощадити час і не набридати вам, я надам фінальну реалізацію функції:

# file_system_analyzer/analyzer/size_analysis.py  
import os  
from collections import defaultdict  
from analyzer.traversal import traverse_directory  
from analyzer.file_categorization import categorize_file  
from analyzer.errors import DirectoryNotFoundError  

def calculate_size(directory):  
 if not os.path.isdir(directory):  
 raise DirectoryNotFoundError(  
 f"The specified directory '{directory}' does not exist or is not a directory."  
 )  

 sizes = defaultdict(int)  
 for file_path in traverse_directory(directory):  
 category = categorize_file(file_path)  
 sizes[category] += os.path.getsize(file_path)  
 return dict(sizes)

Тепер рефакторимо тести, щоб замокати те, що ми не тестуємо:

# tests/test_size_analysis.py  
from unittest.mock import patch  
import pytest  
from file_system_analyzer.analyzer.size_analysis import calculate_size  
from file_system_analyzer.analyzer.errors import DirectoryNotFoundError  

@patch('file_system_analyzer.analyzer.size_analysis.os.path.isdir')  
@patch('file_system_analyzer.analyzer.size_analysis.traverse_directory')  
@patch('file_system_analyzer.analyzer.size_analysis.categorize_file')  
@patch('file_system_analyzer.analyzer.size_analysis.os.path.getsize')  
def test_calculate_size(mock_getsize, mock_categorize_file, mock_traverse_directory, mock_isdir):  
 mock_isdir.return_value = True  
 mock_traverse_directory.return_value = ['/path/to/file1.txt', '/path/to/file2.jpg']  
 mock_categorize_file.side_effect = ['text', 'image']  
 mock_getsize.side_effect = [100, 200]  

 result = calculate_size('/path/to/directory')  

 assert result == {'text': 100, 'image': 200}  

def test_calculate_size_directory_not_found():  
 with pytest.raises(DirectoryNotFoundError):  
 calculate_size('/invalid/directory')

Переміщаємось до останнього методу нашого додатку — перевірка незвичних дозволів. Знову базове тіло та тестові випадки:

# file_system_analyzer/analyzer/permissions.py  
def check_permissions(directory):  
 pass
# tests/test_size_permissions.py  
from unittest.mock import patch  
import pytest  
from file_system_analyzer.analyzer.permissions import check_permissions  
from file_system_analyzer.analyzer.errors import DirectoryNotFoundError  

def test_check_permissions():  
 result = check_permissions('/path/to/directory')  
 assert result == ['/path/to/file1.txt', '/path/to/file2.sh']  

def test_check_permissions_directory_not_found():  
 with pytest.raises(DirectoryNotFoundError):  
 check_permissions('/invalid/directory')

Ось кінцевий вигляд функції check_permissions:

# file_system_analyzer/analyzer/permissions.py  
import os  
import stat  
from file_system_analyzer.analyzer.traversal import traverse_directory  
from file_system_analyzer.analyzer.errors import DirectoryNotFoundError  

def check_permissions(directory):  
 if not os.path.isdir(directory):  
 raise DirectoryNotFoundError(  
 f"The specified directory '{directory}' does not exist or is not a directory."  
 )  

 unusual_permissions = []  
 for file_path in traverse_directory(directory):  
 st = os.stat(file_path)  
 if bool(st.st_mode & stat.S_IWOTH):  
 unusual_permissions.append(file_path)  
 return unusual_permissions

І ось як виглядає фінальний тест, знову замоканий для того, що ми не тестуємо:

# tests/test_size_permissions.py  
from unittest.mock import patch  
import pytest  
from file_system_analyzer.analyzer.permissions import check_permissions  
from file_system_analyzer.analyzer.errors import DirectoryNotFoundError  

@patch('file_system_analyzer.analyzer.permissions.os.path.isdir')  
@patch('file_system_analyzer.analyzer.permissions.traverse_directory')  
@patch('file_system_analyzer.analyzer.permissions.os.stat')
def test_check_permissions(mock_stat, mock_traverse_directory, mock_isdir):  
 mock_isdir.return_value = True  
 mock_traverse_directory.return_value = ['/path/to/file1.txt', '/path/to/file2.sh']  
 mock_stat.return_value.st_mode = 0o777  

 result = check_permissions('/path/to/directory')  

 assert result == ['/path/to/file1.txt', '/path/to/file2.sh']  

def test_check_permissions_directory_not_found():  
 with pytest.raises(DirectoryNotFoundError):  
 check_permissions('/invalid/directory')

Зачекайте, майже готово.
Тепер, коли всі робочі частини розроблено, ми більш ніж готові об'єднати все в один модуль і зробити це справжнім інструментом командного рядка. Побачимося в Частині 4, де ми нарешті побачимо мету всього, над чим ми працювали. До зустрічі там!

Перекладено з: File System Analyzer on Python with TDD step by step (Part 3)

Leave a Reply

Your email address will not be published. Required fields are marked *