Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 104 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,104 @@
# qa_python
# qa_python_4
# Тестирование класса BooksCollector

## Общая информация
- **Всего реализовано тестов:** 38
- **Параметризированные тесты:** 2 (проверка граничных значений длины названия)
- **Покрытие методов:** 100% (все 10 методов класса протестированы)
- **Использование фикстур:** 4 фикстуры в conftest.py
- **Библиотека для тестирования:** pytest


## Структура проекта


---

## Фикстуры (conftest.py)

| Фикстура | Описание |
|----------|----------|
| `collector` | Пустой экземпляр BooksCollector для каждого теста |
| `collector_with_books` | Экземпляр с 3 книгами без жанров |
| `collector_with_genres` | Экземпляр с книгами и установленными жанрами |
| `collector_with_favorites` | Экземпляр с книгами, жанрами и избранным |

---

## Группы тестов

### Группа 1: Тестирование добавления книг (4 теста, включая 2 параметризированных)

| № | Название теста | Тип | Что проверяет | Ожидаемый результат |
|---|----------------|-----|----------------|---------------------|
| 1 | `test_add_new_book_add_two_books` | Обычный | Добавление нескольких книг | В словаре 2 книги |
| 2 | `test_add_new_book_with_invalid_length_not_added` | **Параметризированный** | Невалидные значения длины (пустая строка, >40 символов) | Книга не добавляется |
| 3 | `test_add_new_book_with_valid_length_added` | **Параметризированный** | Граничные значения валидной длины (1, 20, 40 символов) | Книга успешно добавляется |
| 4 | `test_add_new_book_duplicate_not_added` | Обычный | Добавление дубликата | Дубликат игнорируется |

### Группа 2: Тестирование установки жанра (3 теста)

| № | Название теста | Что проверяет | Ожидаемый результат |
|---|----------------|----------------|---------------------|
| 5 | `test_set_book_genre` | Установка валидного жанра | Жанр успешно устанавливается |
| 6 | `test_set_book_genre_for_nonexistent_book` | Установка жанра несуществующей книге | Жанр не устанавливается |
| 7 | `test_set_book_genre_with_invalid_genre` | Установка невалидного жанра | Жанр не меняется (остаётся '') |

### Группа 3: Тестирование получения жанра (3 теста)

| № | Название теста | Что проверяет | Ожидаемый результат |
|---|----------------|----------------|---------------------|
| 8 | `test_get_book_genre_for_nonexistent_book` | Получение жанра несуществующей книги | Возвращается None |
| 9 | `test_get_book_genre_for_existing_book` | Получение жанра существующей книги | Возвращается установленный жанр |
| 10 | `test_get_book_genre_for_book_without_genre` | Получение жанра книги без жанра | Возвращается '' |

### Группа 4: Тестирование получения книг по жанрам (4 теста)

| № | Название теста | Что проверяет | Ожидаемый результат |
|---|----------------|----------------|---------------------|
| 11 | `test_get_books_with_specific_genre` | Получение книг конкретного жанра | Возвращаются только книги нужного жанра |
| 12 | `test_get_books_with_specific_genre_no_books` | Получение книг жанра, которого нет | Возвращается пустой список |
| 13 | `test_get_books_with_specific_genre_empty_collection` | Получение книг из пустой коллекции | Возвращается пустой список |
| 14 | `test_get_books_with_invalid_genre` | Получение книг с несуществующим жанром | Возвращается пустой список |

### Группа 5: Тестирование метода get_books_genre (4 теста)

| № | Название теста | Что проверяет | Ожидаемый результат |
|---|----------------|----------------|---------------------|
| 15 | `test_get_books_genre_returns_dict` | Проверка типа возвращаемых данных | Возвращается словарь |
| 16 | `test_get_books_genre_empty_dict` | Получение пустого словаря | Возвращается {} |
| 17 | `test_get_books_genre_with_one_book` | Получение словаря с одной книгой | Словарь содержит 1 книгу без жанра |
| 18 | `test_get_books_genre_with_multiple_books` | Получение словаря с несколькими книгами | Словарь содержит все книги с их жанрами |

### Группа 6: Тестирование книг для детей (3 теста)

| № | Название теста | Что проверяет | Ожидаемый результат |
|---|----------------|----------------|---------------------|
| 19 | `test_get_books_for_children` | Книги с возрастным рейтингом не попадают в список для детей | В списке только книги без возрастных ограничений |
| 20 | `test_get_books_for_children_without_genre` | Книги без жанра не попадают в список для детей | Книга без жанра отсутствует в списке |
| 21 | `test_get_books_for_children_empty` | Получение списка из пустой коллекции | Возвращается пустой список |

### Группа 7: Тестирование избранного (9 тестов)

| № | Название теста | Что проверяет | Ожидаемый результат |
|---|----------------|----------------|---------------------|
| 22 | `test_add_book_in_favorites` | Добавление книги в избранное | Книга появляется в избранном |
| 23 | `test_add_nonexistent_book_to_favorites` | Добавление несуществующей книги в избранное | Книга не добавляется |
| 24 | `test_add_duplicate_book_to_favorites` | Добавление дубликата в избранное | Дубликат игнорируется |
| 25 | `test_delete_book_from_favorites` | Удаление книги из избранного | Книга удаляется |
| 26 | `test_delete_nonexistent_book_from_favorites` | Удаление несуществующей книги из избранного | Ничего не происходит |
| 27 | `test_delete_book_from_favorites_not_in_favorites` | Удаление книги, которой нет в избранном | Ничего не происходит |
| 28 | `test_get_list_of_favorites_books_empty` | Получение пустого списка избранного | Возвращается пустой список |
| 29 | `test_get_list_of_favorites_books_returns_list` | Проверка типа возвращаемых данных | Возвращается список |
| 30 | `test_get_list_of_favorites_books_with_one_book` | Получение списка с одной книгой | Список содержит 1 книгу |
| 31 | `test_get_list_of_favorites_books_with_multiple_books` | Получение списка с несколькими книгами | Список содержит все добавленные книги |



---

### Тест 1: Невалидные значения длины названия

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

нужно исправить: почему в ридми лежит код тестов?

test_add_new_book_with_invalid_length_not_added
### Тест 2: Валидные значения длины названия (граничные значения)
test_add_new_book_with_valid_length_added

32 changes: 32 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import pytest
from books_collector import BooksCollector

@pytest.fixture
def collector():
"""Фикстура, возвращающая новый экземпляр BooksCollector для каждого теста"""
return BooksCollector()

@pytest.fixture
def collector_with_books(collector):
"""Фикстура с предустановленными книгами"""
books = ['Книга 1', 'Книга 2', 'Книга 3']
for book in books:
collector.add_new_book(book)
return collector

@pytest.fixture
def collector_with_genres(collector_with_books):
"""Фикстура с книгами и жанрами"""
collector = collector_with_books
collector.set_book_genre('Книга 1', 'Фантастика')
collector.set_book_genre('Книга 2', 'Ужасы')
collector.set_book_genre('Книга 3', 'Детективы')
return collector

@pytest.fixture
def collector_with_favorites(collector_with_genres):
"""Фикстура с книгами, жанрами и избранным"""
collector = collector_with_genres
collector.add_book_in_favorites('Книга 1')
collector.add_book_in_favorites('Книга 2')
return collector
170 changes: 154 additions & 16 deletions tests.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,162 @@
import pytest
from main import BooksCollector

# класс TestBooksCollector объединяет набор тестов, которыми мы покрываем наше приложение BooksCollector
# обязательно указывать префикс Test
class TestBooksCollector:

# пример теста:
# обязательно указывать префикс test_
# дальше идет название метода, который тестируем add_new_book_
# затем, что тестируем add_two_books - добавление двух книг
def test_add_new_book_add_two_books(self):
# создаем экземпляр (объект) класса BooksCollector
collector = BooksCollector()

# добавляем две книги
# Тесты добавления книг
def test_add_new_book_add_two_books(self, collector):
collector.add_new_book('Гордость и предубеждение и зомби')
collector.add_new_book('Что делать, если ваш кот хочет вас убить')
assert len(collector.get_books_genre()) == 2

@pytest.mark.parametrize('invalid_name', [
'',
'А' * 41,
'А' * 100,
])
def test_add_new_book_with_invalid_length_not_added(self, collector, invalid_name):
collector.add_new_book(invalid_name)
assert len(collector.get_books_genre()) == 0

@pytest.mark.parametrize('valid_name', [
'А',
'А' * 20,
'А' * 40,
'Книга с пробелами и знаками препинания!',
])
def test_add_new_book_with_valid_length_added(self, collector, valid_name):
collector.add_new_book(valid_name)
assert len(collector.get_books_genre()) == 1
assert valid_name in collector.get_books_genre()

def test_add_new_book_duplicate_not_added(self, collector):
collector.add_new_book('Книга')
collector.add_new_book('Книга')
assert len(collector.get_books_genre()) == 1

# Тесты установки и получения жанра
def test_set_book_genre(self, collector):
book_name = 'Книга'
collector.add_new_book(book_name)
collector.set_book_genre(book_name, 'Ужасы')
assert collector.get_book_genre(book_name) == 'Ужасы'

def test_set_book_genre_for_nonexistent_book(self, collector):
collector.set_book_genre('Несуществующая книга', 'Фантастика')
assert collector.get_book_genre('Несуществующая книга') is None

def test_set_book_genre_with_invalid_genre(self, collector):
book_name = 'Книга'
collector.add_new_book(book_name)
collector.set_book_genre(book_name, 'Неверный жанр')
assert collector.get_book_genre(book_name) == ''

def test_get_book_genre_for_nonexistent_book(self, collector):
assert collector.get_book_genre('Несуществующая книга') is None

def test_get_book_genre_for_existing_book(self, collector):
book_name = 'Книга'
collector.add_new_book(book_name)
collector.set_book_genre(book_name, 'Фантастика')
assert collector.get_book_genre(book_name) == 'Фантастика'

def test_get_book_genre_for_book_without_genre(self, collector):
book_name = 'Книга без жанра'
collector.add_new_book(book_name)
assert collector.get_book_genre(book_name) == ''

# Тесты получения книг по жанрам
def test_get_books_with_specific_genre(self, collector_with_genres):
horror_books = collector_with_genres.get_books_with_specific_genre('Ужасы')
assert len(horror_books) == 1
assert 'Книга 2' in horror_books

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно лучше: не стоит хранить одно и то же тестовое значение отдельно - если что-то было создано в фикстуре, то тут стоит вызвать это значение, а не дублировать его. Иначе тесты станут неподдерживаемыми - легко изменить что то в фикстуре (например) и не заметить что и в тесте тоже нужно поменять. Так как сейчас эти данные не связаны, но должны быть. Удачное решение - создать файл с тестовыми данными и обращаться к нему и в фикстуре и в тестах


def test_get_books_with_specific_genre_no_books(self, collector):
assert collector.get_books_with_specific_genre('Ужасы') == []

def test_get_books_with_specific_genre_empty_collection(self, collector):
result = collector.get_books_with_specific_genre('Фантастика')
assert result == []

def test_get_books_with_invalid_genre(self, collector_with_genres):
assert collector_with_genres.get_books_with_specific_genre('Неверный жанр') == []

# Тесты метода get_books_genre
def test_get_books_genre_returns_dict(self, collector):
result = collector.get_books_genre()
assert isinstance(result, dict)

def test_get_books_genre_empty_dict(self, collector):
assert collector.get_books_genre() == {}

def test_get_books_genre_with_one_book(self, collector):
collector.add_new_book('Книга 1')
books = collector.get_books_genre()
assert len(books) == 1
assert 'Книга 1' in books
assert books['Книга 1'] == ''

def test_get_books_genre_with_multiple_books(self, collector_with_genres):
books = collector_with_genres.get_books_genre()
assert len(books) == 3
assert books['Книга 1'] == 'Фантастика'
assert books['Книга 2'] == 'Ужасы'
assert books['Книга 3'] == 'Детективы'

# Тесты книг для детей
def test_get_books_for_children(self, collector_with_genres):
children_books = collector_with_genres.get_books_for_children()
assert len(children_books) == 1
assert 'Книга 1' in children_books # Фантастика - для детей
assert 'Книга 2' not in children_books # Ужасы - не для детей
assert 'Книга 3' not in children_books # Детективы - не для детей

def test_get_books_for_children_without_genre(self, collector):
collector.add_new_book('Книга без жанра')
children_books = collector.get_books_for_children()
assert 'Книга без жанра' not in children_books

def test_get_books_for_children_empty(self, collector):
assert collector.get_books_for_children() == []

# Тесты избранного
def test_add_book_in_favorites(self, collector_with_books):
collector_with_books.add_book_in_favorites('Книга 1')
assert 'Книга 1' in collector_with_books.get_list_of_favorites_books()

def test_add_nonexistent_book_to_favorites(self, collector):
collector.add_book_in_favorites('Несуществующая книга')
assert len(collector.get_list_of_favorites_books()) == 0

def test_add_duplicate_book_to_favorites(self, collector_with_books):
collector_with_books.add_book_in_favorites('Книга 1')
collector_with_books.add_book_in_favorites('Книга 1')
assert len(collector_with_books.get_list_of_favorites_books()) == 1

def test_delete_book_from_favorites(self, collector_with_favorites):
collector_with_favorites.delete_book_from_favorites('Книга 1')
assert 'Книга 1' not in collector_with_favorites.get_list_of_favorites_books()
assert 'Книга 2' in collector_with_favorites.get_list_of_favorites_books()

def test_delete_nonexistent_book_from_favorites(self, collector_with_favorites):
collector_with_favorites.delete_book_from_favorites('Несуществующая книга')
assert len(collector_with_favorites.get_list_of_favorites_books()) == 2

def test_get_list_of_favorites_books_empty(self, collector):
assert collector.get_list_of_favorites_books() == []

def test_get_list_of_favorites_books_returns_list(self, collector):
favorites = collector.get_list_of_favorites_books()
assert isinstance(favorites, list)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нужно исправить: отсутствуют тесты на проверку методов get_books_genre. На каждый метод должен быть отдельный тест, вне зависимости, вызывается он в других тестах или нет


# проверяем, что добавилось именно две
# словарь books_rating, который нам возвращает метод get_books_rating, имеет длину 2
assert len(collector.get_books_rating()) == 2
def test_get_list_of_favorites_books_with_one_book(self, collector_with_books):
collector_with_books.add_book_in_favorites('Книга 1')
favorites = collector_with_books.get_list_of_favorites_books()
assert len(favorites) == 1
assert 'Книга 1' in favorites

# напиши свои тесты ниже
# чтобы тесты были независимыми в каждом из них создавай отдельный экземпляр класса BooksCollector()
def test_get_list_of_favorites_books_with_multiple_books(self, collector_with_favorites):
favorites = collector_with_favorites.get_list_of_favorites_books()
assert len(favorites) == 2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Нужно исправить: согласно заданию необходимо создать как минимум один параметризированный тест. В данном задании для параметризации лучше всего подходят граничные значения в классе длины имени - отдельно невалидные значения и отдельным тестом - ГЗ для валидных

assert 'Книга 1' in favorites
assert 'Книга 2' in favorites