Глава 23 · Часть V · Проверяем и автоматизируем

Проверяем поиск дубликатов

Пустые файлы и файл в несколько мегабайт проверяют два случая, которые легко пропустить в обычном тесте.

SafeSort · Часть 5 из 6Тесты и CI
Arrangeдва файла содинаковым содержимымActfind_duplicates()Assertодна группа из двухфайлов

Тесты для find_duplicates() проверяют не только «типичный» случай двух одинаковых файлов, но и два крайних случая, которые легко упустить: файлы нулевого размера и файл заметно больше одного блока чтения.

tests/test_duplicates.py
def test_identical_content_files_are_grouped(tmp_path):
    a = tmp_path / "notes.txt"
    b = tmp_path / "copy_of_notes.txt"
    a.write_text("тот же текст")
    b.write_text("тот же текст")

    groups = find_duplicates(scan(tmp_path, Config()))

    assert len(groups) == 1
    assert len(groups[0].files) == 2

def test_empty_files_are_duplicates_of_each_other(tmp_path):
    (tmp_path / "a.txt").write_text("")
    (tmp_path / "b.txt").write_text("")

    groups = find_duplicates(scan(tmp_path, Config()))

    assert len(groups) == 1
    assert groups[0].size == 0
Результат и способ чтения проверяются отдельно
Файл больше одного блока и правильный digest доказывают только результат функции. Такой тест не наблюдает вызовы read(): реализация могла бы прочитать весь файл сразу и всё равно вернуть тот же SHA-256. Контракт bounded reads проверяется отдельным instrumented reader.
tests/test_duplicates.py
def test_sha256_large_file_has_correct_result(tmp_path):
    data = b"x" * (5 * 1024 * 1024)  # 5 МБ — больше одного блока чтения
    path = tmp_path / "bolshoj_fajl.bin"
    path.write_bytes(data)

    assert sha256_file(path) == hashlib.sha256(data).hexdigest()
tests/test_duplicates.py: interaction test
class RecordingReader(io.BytesIO):
    def __init__(self, data: bytes) -> None:
        super().__init__(data)
        self.requested_sizes = []

    def read(self, size: int = -1) -> bytes:
        self.requested_sizes.append(size)
        return super().read(size)

def test_sha256_stream_uses_multiple_bounded_reads():
    reader = RecordingReader(b"abcdefghij")

    digest = sha256_stream(reader, chunk_size=4)

    assert digest == hashlib.sha256(b"abcdefghij").hexdigest()
    assert reader.requested_sizes == [4, 4, 4, 4]
    assert max(reader.requested_sizes) == 4

Первый тест относится к типу result test (black-box): он проверяет, что вернулось. Второй проверяет interaction/implementation contract: зависимость получила несколько ограниченных read(4). Небольшой записывающий поток делает это наблюдаемым без тяжёлой mocking-инфраструктуры.

Практика: тесты дубликатов и пустых файлов
Интерактивный ноутбук в браузере: Python 3.14 через Pyodide, без установки
Открыть практику →
Официальная документация
pytest — How to write and report assertions
unittest.mock — mock object library