From 8064a50df0fbd3bed344d7366964d7968cb57e10 Mon Sep 17 00:00:00 2001 From: Artur Vakhitov Date: Sun, 15 Feb 2026 12:19:52 +0500 Subject: [PATCH 1/5] Initial commit in develop --- __pycache__/main.cpython-314.pyc | Bin 0 -> 3698 bytes .../tests.cpython-314-pytest-9.0.2.pyc | Bin 0 -> 41687 bytes tests.py | 242 +++++++++++++++++- 3 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 __pycache__/main.cpython-314.pyc create mode 100644 __pycache__/tests.cpython-314-pytest-9.0.2.pyc diff --git a/__pycache__/main.cpython-314.pyc b/__pycache__/main.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b264371fa61759769cbfc8ddfdcb872436a45add GIT binary patch literal 3698 zcmb7GU2Gdg5Z<%zQYUtN*CuIc>?BU?xVffv2(?ff+NjVU2~`czsU$oE4%fbaI0CrV^*x;g`Y zIrn3220AI2_aZSkg&3ZfIK#V>6kz@?V(^Lwq@Z{~wkSNvZHfTWr?h}gw;miEoTrq#Lu9_>>n;`F*_sx}u z2&4I#`7Il~{%{9=eqi1O{ay1uv-;Y)J|PQK01a(Hu?@uHPBi?)ddvL5x?x?l-iGC% zXZ~niUksw^WAkV8M_A(yDDExuw*M@;tj=k9s@bixsn-fLYiPPHI3p7KQB!l8N>fHQ zKex=;ysqVDb@VoF_Z=IXd0yA3K2ykFe0}ECl)7-yI9JHe%%`&X#KJ|}r>fa})=*V@ z*G6y?RRuz#$3a{nT86t^zS{w)Yf=?jg6U|FZWlT-_!UvX{a=A+~o;Gi+~8%P)H<2Fm91sd-ID zKjU#K;z%Hs$*6hlf{G!tLk)1&E0H|<2@n9O*ijPuEU~X7MlCUV>v&0&e;4K3sXNh? z%r|l|arEBVQsStYI9eXuvvj-^2wQ>hhiCo>4E*gSBTsXdC{rX~x*k95GsMrcJ*K5* zULOKoCW`n>M<(mz1>@M5e!Hu8hBinSH3F!)|I6Hj>|tW zP}d9wl}qlP2EaC|5n+8l2!PBlmO7$VM|9O6y)As#{Z;qs&{O5Xy&JKS4_p4I15Lei z>(CrzYc(gt)?|zl+5?lTuHucRkAc`gh9K}|EAKX{G0o_RQxih`A6+EYcp>2!Nv0~5EjW|4m$)`!r= z;-;@3>FE!_pXm?X629zS?p}=?D946Mv7{ACmSSU8Y^)eNY)O&#_;N>2u_IpY++XTU zTAj(2GsVuazXdXQ_{W zrXDkWKh8(w>*#6v`W`*pIw$WlW8mZ_DYw}TtK-v{%ctn{2-vhV z7{Di{FV{u}9&(eI#v9s{r;dk*T^@2(@B|0E7)+u86i7Qd2twwa8nDBm;B0}a>2uj! zhC-FDWjj20jdMF|nAh9egPTHMZ-%?-6CfZ0J$6IeEBqHmT>YK0t%bzM<$a;;N^DHcu^fkzW=7ZzBI2fSz0+B+%M z0B=((tSdUw574bEE>N349sg*2Rg}w8=gr{z!ICs+NrSgCMQIqSue29LAhrdDFS8}L zz_5xD*fq9hH>4Rx)mntm*ez6>FTAEbicWV%%V~z@05nSr^K}r(4G?W^dI0v&qaYYg zH^<)}U-kEw1Jb(_*CtAVek;&_D^?8bL0ASJO%DGvbFo-AX08z4qFh!t)WU4d1?HA+ z?`?2o3tO!II$mm)(cZm$?Xv0XVMnI$e1vD8gfC-!;=#+fO;zU$nZ+FT1FHJ!Vk+lq zL{**5Qr*a9^IE>3s#Jo>6f-l0&=QJ?lMbO6Mlpio2^3GFz!YV7sj6<-G41;FR zF2d^`_Svqg>0C`<@kU6&QGI7S( gOV@YU!DE~r0~59-jz92n9QTjsAlJJ_K(Gn_0dZLkGXMYp literal 0 HcmV?d00001 diff --git a/__pycache__/tests.cpython-314-pytest-9.0.2.pyc b/__pycache__/tests.cpython-314-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e24b145af056e1b225137ec4260b3908815a7685 GIT binary patch literal 41687 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 Date: Sun, 15 Feb 2026 12:29:36 +0500 Subject: [PATCH 2/5] Delete .idea directory --- .idea/.gitignore | 3 --- .idea/inspectionProfiles/profiles_settings.xml | 6 ------ .idea/misc.xml | 7 ------- .idea/modules.xml | 8 -------- .idea/qa_python.iml | 8 -------- .idea/vcs.xml | 6 ------ 6 files changed, 38 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/qa_python.iml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 590a59e..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 4b664cf..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/qa_python.iml b/.idea/qa_python.iml deleted file mode 100644 index c03f621..0000000 --- a/.idea/qa_python.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 41338090a960be5da7a91de80b83067203dbb520 Mon Sep 17 00:00:00 2001 From: Artur Vakhitov Date: Sun, 15 Feb 2026 12:41:55 +0500 Subject: [PATCH 3/5] Initial commit in develop --- README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1cc701d..ebe4ea2 100644 --- a/README.md +++ b/README.md @@ -1 +1,62 @@ -# qa_python \ No newline at end of file +# qa_python +## Тестирование + +В проекте реализовано **25 автоматизированных тестов** для класса `BooksCollector`. Тесты покрывают все методы и граничные случаи. + +### Список тестов по методам + +#### 1. `add_new_book` (5 тестов) +- Успешное добавление (имя ≤40 символов). +- Повторное добавление (дубликат). +- Пустое имя. +- Имя длиннее 40 символов. +- Ровно 40 символов. + +#### 2. `set_book_genre` (3 теста) +- Установка жанра для существующей книги. +- Книга отсутствует в словаре. +- Жанр не в списке допустимых. + +#### 3. `get_book_genre` (2 теста) +- Книга есть в словаре. +- Книги нет в словаре (`None`). + + +#### 4. `get_books_with_specific_genre` (3 теста) +- Найдены книги заданного жанра. +- Книг жанра нет (пустой список). +- Жанр не в списке допустимых (пустой список). + + +#### 5. `get_books_genre` (1 тест) +- Возврат полного словаря. + +#### 6. `get_books_for_children` (3 теста) +- Книги без возрастного рейтинга. +- Книги с возрастным рейтингом (не попадают). +- Книга без жанра (не попадает). + + +#### 7. `add_book_in_favorites` (3 теста) +- Успешное добавление. +- Книги нет в словаре. +- Дубликат в избранном. + + +#### 8. `delete_book_from_favorites` (2 теста) +- Успешное удаление. +- Книги нет в избранном (без ошибок). + + +#### 9. `get_list_of_favorites_books` (5 тестов) +- Пустой список. +- Одна книга в избранном. +- Несколько книг. +- После удаления одной книги. +- Без изменений при добавлении не‑избранного. + + +### Как запустить тесты +1. Установите зависимости: + ```bash + pip install pytest From cfd82bf64599a64cb339780f328b12d95b5289c4 Mon Sep 17 00:00:00 2001 From: ArthurLyons Date: Sun, 15 Feb 2026 12:54:04 +0500 Subject: [PATCH 4/5] Update README.md --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index ebe4ea2..e22f4b0 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,3 @@ - Несколько книг. - После удаления одной книги. - Без изменений при добавлении не‑избранного. - - -### Как запустить тесты -1. Установите зависимости: - ```bash - pip install pytest From f699737d48e92b4e2f46c3761949f871c1cb05be Mon Sep 17 00:00:00 2001 From: Artur Vakhitov Date: Sun, 15 Feb 2026 22:23:53 +0500 Subject: [PATCH 5/5] Resolve merge conflicts in README.md and tests.py --- README.md | 8 +- tests.py | 456 +++++++++++++++++++++++++++--------------------------- 2 files changed, 227 insertions(+), 237 deletions(-) diff --git a/README.md b/README.md index ebe4ea2..d354712 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,4 @@ - Одна книга в избранном. - Несколько книг. - После удаления одной книги. -- Без изменений при добавлении не‑избранного. - - -### Как запустить тесты -1. Установите зависимости: - ```bash - pip install pytest +- Без изменений при добавлении не‑избранного. \ No newline at end of file diff --git a/tests.py b/tests.py index d01f0d9..4c38cef 100644 --- a/tests.py +++ b/tests.py @@ -1,3 +1,5 @@ +import pytest + from main import BooksCollector # класс TestBooksCollector объединяет набор тестов, которыми мы покрываем наше приложение BooksCollector @@ -18,247 +20,241 @@ def test_add_new_book_add_two_books(self): # проверяем, что добавилось именно две # словарь books_rating, который нам возвращает метод get_books_rating, имеет длину 2 - assert len(collector.get_books_rating()) == 2 + assert len(collector.books_genre) == 2 # напиши свои тесты ниже # чтобы тесты были независимыми в каждом из них создавай отдельный экземпляр класса BooksCollector() - # 1. Тесты метода add_new_book - - def test_add_new_book_success(self): - """Добавляем книгу с корректным названием (≤40 символов).""" + # 1. Тесты add_new_book + @pytest.mark.parametrize( + "book_name,expected_in_dict", + [ + ("Война и мир", True), # корректное имя + ("", False), # пустое имя + ("a" * 41, False), # >40 символов + ("a" * 40, True), # ровно 40 символов + ("1984", True), # короткое имя + ] + ) + def test_add_new_book_parametrized(self, book_name, expected_in_dict): collector = BooksCollector() - collector.add_new_book("Война и мир") - assert "Война и мир" in collector.books_genre - assert collector.books_genre["Война и мир"] == "" - - def test_add_new_book_duplicate(self): - """Повторное добавление той же книги — не должно добавиться.""" + collector.add_new_book(book_name) + + if expected_in_dict: + assert book_name in collector.books_genre + assert collector.books_genre[book_name] == "" + else: + assert book_name not in collector.books_genre + + # 2. Тесты set_book_genre + @pytest.mark.parametrize( + "book_name,genre,expected_genre", + [ + ("Гарри Поттер", "Фантастика", "Фантастика"), # валидный жанр + ("Оно", "Ужасы", "Ужасы"), # валидный жанр + ("Шерлок Холмс", "Детективы", "Детективы"), # валидный жанр + ("Винни-Пух", "Мультфильмы", "Мультфильмы"), # валидный жанр + ("Алиса", "Романтика", ""), # жанр не в списке genre + ("Неизвестная книга", "Фантастика", None), # книги нет в словаре + ] + ) + def test_set_book_genre_parametrized(self, book_name, genre, expected_genre): collector = BooksCollector() - collector.add_new_book("1984") - collector.add_new_book("1984") - assert len(collector.books_genre) == 1 - assert collector.books_genre["1984"] == "" - - def test_add_new_book_empty_name(self): - """Попытка добавить книгу с пустым названием — не должно добавиться.""" + # Предварительно добавляем книгу (если она должна быть в словаре) + if book_name != "Неизвестная книга": + collector.add_new_book(book_name) + + collector.set_book_genre(book_name, genre) + + if expected_genre is None: + assert book_name not in collector.books_genre # книга не добавлена + else: + assert collector.get_book_genre(book_name) == expected_genre + + # 3. Тесты get_books_with_specific_genre + @pytest.mark.parametrize( + "genre,expected_books", + [ + ("Фантастика", ["Гарри Поттер", "Дюна"]), + ("Ужасы", ["Оно", "Сияние"]), + ("Детективы", ["Шерлок Холмс"]), + ("Мультфильмы", ["Винни-Пух"]), + ("Романтика", []), # жанр не в списке + ("Комедии", []), # нет книг в жанре + ] + ) + def test_get_books_with_specific_genre_parametrized(self, genre, expected_books): collector = BooksCollector() - collector.add_new_book("") - assert "" not in collector.books_genre - - def test_add_new_book_long_name(self): - """Название длиннее 40 символов — не должно добавиться.""" - collector = BooksCollector() - long_name = "a" * 41 - collector.add_new_book(long_name) - assert long_name not in collector.books_genre - - def test_add_new_book_max_length(self): - """Название ровно 40 символов — должно добавиться.""" - collector = BooksCollector() - max_name = "a" * 40 - collector.add_new_book(max_name) - assert max_name in collector.books_genre - - # 2. Тесты метода set_book_genre - - def test_set_book_genre_success(self): - """Устанавливаем жанр для существующей книги из списка genre.""" - collector = BooksCollector() - collector.add_new_book("Гарри Поттер") - collector.set_book_genre("Гарри Поттер", "Фантастика") - assert collector.books_genre["Гарри Поттер"] == "Фантастика" - - def test_set_book_genre_book_not_exists(self): - """Пытаемся установить жанр для книги, которой нет в словаре.""" - collector = BooksCollector() - collector.set_book_genre("Неизвестная книга", "Детективы") - assert "Неизвестная книга" not in collector.books_genre - - def test_set_book_genre_invalid_genre(self): - """Жанр не входит в список genre — не должен установиться.""" - collector = BooksCollector() - collector.add_new_book("Дюна") - collector.set_book_genre("Дюна", "Романтика") # Романтика не в self.genre - assert collector.books_genre["Дюна"] == "" - - # 3. Тесты метода get_book_genre - - def test_get_book_genre_existing(self): - """Получаем жанр существующей книги.""" + # Добавляем книги с разными жанрами + books_data = [ + ("Гарри Поттер", "Фантастика"), + ("Дюна", "Фантастика"), + ("Оно", "Ужасы"), + ("Сияние", "Ужасы"), + ("Шерлок Холмс", "Детективы"), + ("Винни-Пух", "Мультфильмы"), + ] + for name, genre_val in books_data: + collector.add_new_book(name) + collector.set_book_genre(name, genre_val) + + result = collector.get_books_with_specific_genre(genre) + assert sorted(result) == sorted(expected_books) + + # 4. Тесты get_books_for_children + @pytest.mark.parametrize( + "books_data,expected_children_books", + [ + ( + [("Винни-Пух", "Мультфильмы"), ("Алиса", "Комедии")], + ["Винни-Пух", "Алиса"] + ), + ( + [("Оно", "Ужасы"), ("Сияние", "Ужасы")], + [] + ), + ( + [("Дюна", "Фантастика"), ("Шерлок", "Детективы")], + ["Дюна"] # Детективы — с возрастным рейтингом + ), + ( + [("Без жанра", "")], + [] # книга без жанра не подходит + ), + ] + ) + def test_get_books_for_children_parametrized(self, books_data, expected_children_books): collector = BooksCollector() - collector.add_new_book("Властелин колец") - collector.set_book_genre("Властелин колец", "Фантастика") - assert collector.get_book_genre("Властелин колец") == "Фантастика" + for name, genre in books_data: + collector.add_new_book(name) + if genre: # если жанр указан + collector.set_book_genre(name, genre) - def test_get_book_genre_not_exists(self): - """Книга не существует — должен вернуться None.""" - collector = BooksCollector() - assert collector.get_book_genre("Нет такой книги") is None - - # 4. Тесты метода get_books_with_specific_genre - - def test_get_books_with_specific_genre_found(self): - """Находим книги заданного жанра.""" - collector = BooksCollector() - collector.add_new_book("Оно") - collector.add_new_book("Сияние") - collector.set_book_genre("Оно", "Ужасы") - collector.set_book_genre("Сияние", "Ужасы") - result = collector.get_books_with_specific_genre("Ужасы") - assert "Оно" in result - assert "Сияние" in result - assert len(result) == 2 - - def test_get_books_with_specific_genre_no_books(self): - """Жанр существует, но книг с ним нет.""" - collector = BooksCollector() - result = collector.get_books_with_specific_genre("Комедии") - assert result == [] - - def test_get_books_with_specific_genre_invalid_genre(self): - """Жанр не входит в self.genre — должен вернуть пустой список.""" - collector = BooksCollector() - result = collector.get_books_with_specific_genre("Романтика") - assert result == [] - - # 5. Тесты метода get_books_genre - - def test_get_books_genre(self): - """Проверяем возврат полного словаря books_genre.""" - collector = BooksCollector() - collector.add_new_book("Шерлок Холмс") - collector.set_book_genre("Шерлок Холмс", "Детективы") - expected = {"Шерлок Холмс": "Детективы"} - assert collector.get_books_genre() == expected - - # 6. Тесты метода get_books_for_children - - def test_get_books_for_children_valid(self): - """Книги без возрастного рейтинга (не в genre_age_rating).""" - collector = BooksCollector() - collector.add_new_book("Винни-Пух") - collector.set_book_genre("Винни-Пух", "Мультфильмы") - collector.add_new_book("Алиса в Стране чудес") - collector.set_book_genre("Алиса в Стране чудес", "Комедии") result = collector.get_books_for_children() - assert "Винни-Пух" in result - assert "Алиса в Стране чудес" in result - - def test_get_books_for_children_age_rated(self): - """Книги с возрастным рейтингом (в genre_age_rating) — не должны попасть.""" + assert sorted(result) == sorted(expected_children_books) + + # 5. Тесты add_book_in_favorites + @pytest.mark.parametrize( + "book_name,is_in_books_genre,expected_in_favorites", + [ + ("Преступление и наказание", True, True), # книга есть в словаре + ("Мастер и Маргарита", True, True), # книга есть в словаре + ("Неизвестная книга", False, False), # книги нет в словаре + ("Преступление и наказание", True, False), # дубликат (уже в избранном) + ] + ) + def test_add_book_in_favorites_parametrized( + self, book_name, is_in_books_genre, expected_in_favorites + ): collector = BooksCollector() - collector.add_new_book("Кладбище домашних животных") - collector.set_book_genre("Кладбище домашних животных", "Ужасы") - result = collector.get_books_for_children() - assert "Кладбище домашних животных" not in result - - def test_get_books_for_children_no_genre(self): - """Книга без жанра — не должна попасть в список для детей.""" - collector = BooksCollector() - collector.add_new_book("Без жанра") - result = collector.get_books_for_children() - assert "Без жанра" not in result - - # 7. Тесты метода add_book_in_favorites - - def test_add_book_in_favorites_success(self): - """Добавляем книгу в избранное (книга есть в books_genre).""" - collector = BooksCollector() - collector.add_new_book("Преступление и наказание") - collector.add_book_in_favorites("Преступление и наказание") - assert "Преступление и наказание" in collector.favorites - - def test_add_book_in_favorites_not_in_books_genre(self): - """Книга отсутствует в books_genre — не должна добавиться в избранное.""" - collector = BooksCollector() - collector.add_book_in_favorites("Неизвестная книга") - assert "Неизвестная книга" not in collector.favorites - - def test_add_book_in_favorites_duplicate(self): - """Повторное добавление в избранное — не должно дублироваться.""" + # Предварительно добавляем книгу, если нужно + if is_in_books_genre: + collector.add_new_book(book_name) + # Для случая дубликата — сразу добавляем в избранное + if book_name == "Преступление и наказание": + collector.add_book_in_favorites(book_name) + + collector.add_book_in_favorites(book_name) + + if expected_in_favorites: + assert book_name in collector.favorites + else: + assert book_name not in collector.favorites + + # 6. Тесты delete_book_from_favorites + @pytest.mark.parametrize( + "book_name,is_in_favorites,expected_removed", + [ + ("Идиот", True, True), # книга в избранном + ("Герой нашего времени", False, False), # книги нет в избранном + ] + ) + def test_delete_book_from_favorites_parametrized( + self, book_name, is_in_favorites, expected_removed + ): collector = BooksCollector() - collector.add_new_book("Мастер и Маргарита") - collector.add_book_in_favorites("Мастер и Маргарита") - collector.add_book_in_favorites("Мастер и Маргарита") - assert collector.favorites.count("Мастер и Маргарита") == 1 - - # 8. Тесты метода delete_book_from_favorites - - def test_delete_book_from_favorites_success(self): - """Удаляем книгу из избранного.""" + if is_in_favorites: + collector.add_new_book(book_name) + collector.add_book_in_favorites(book_name) + + collector.delete_book_from_favorites(book_name) + + if expected_removed: + assert book_name not in collector.favorites + else: + assert book_name not in collector.favorites # или в словаре, или нигде + + # 7. Тесты get_list_of_favorites_books + @pytest.mark.parametrize( + "favorites_data,expected_list", + [ + ([], []), # пустое избранное + (["Преступление и наказание"], ["Преступление и наказание"]), # одна книга + (["Мастер и Маргарита", "Идиот"], ["Мастер и Маргарита", "Идиот"]), # две книги + (["Война и мир"], ["Война и мир"]), # одна книга (проверка порядка) + ] + ) + def test_get_list_of_favorites_books_parametrized(self, favorites_data, expected_list): collector = BooksCollector() - collector.add_new_book("Идиот") - collector.add_book_in_favorites("Идиот") - collector.delete_book_from_favorites("Идиот") - assert "Идиот" not in collector.favorites - - # 9. Тесты метода get_list_of_favorites_books - - def test_get_list_of_favorites_books_empty(self): - """Проверяем, что метод возвращает пустой список, если избранное пусто.""" - collector = BooksCollector() - result = collector.get_list_of_favorites_books() - assert result == [] - assert isinstance(result, list) - - def test_get_list_of_favorites_books_one_book(self): - """Проверяем возврат списка с одной книгой в избранном.""" - collector = BooksCollector() - collector.add_new_book("Преступление и наказание") - collector.add_book_in_favorites("Преступление и наказание") - - result = collector.get_list_of_favorites_books() - - assert result == ["Преступление и наказание"] - assert len(result) == 1 - - def test_get_list_of_favorites_books_multiple_books(self): - """Проверяем возврат списка с несколькими книгами в избранном.""" - collector = BooksCollector() - # Добавляем книги в словарь - collector.add_new_book("Мастер и Маргарита") - collector.add_new_book("Идиот") - collector.add_new_book("Война и мир") - - # Добавляем в избранное - collector.add_book_in_favorites("Мастер и Маргарита") - collector.add_book_in_favorites("Идиот") - collector.add_book_in_favorites("Война и мир") - - result = collector.get_list_of_favorites_books() - - expected = ["Мастер и Маргарита", "Идиот", "Война и мир"] - assert result == expected - assert len(result) == 3 - - def test_get_list_of_favorites_books_after_deletion(self): - """Проверяем список избранного после удаления одной книги.""" + # Добавляем книги в словарь и в избранное + for book in favorites_data: + collector.add_new_book(book) + collector.add_book_in_favorites(book) + + result = collector.get_list_of_favorites_books() + assert sorted(result) == sorted(expected_list) + + # 8. Тесты get_books_genre + + @pytest.mark.parametrize( + "books_data,expected_dict", + [ + ( + [], + {} # пустой словарь + ), + ( + [("Война и мир", "Классика"), ("1984", "Фантастика")], + {"Война и мир": "Классика", "1984": "Фантастика"} # две книги с жанрами + ), + ( + [("Без жанра", "")], + {"Без жанра": ""} # книга без жанра + ), + ( + [("Дубровский", "Приключения"), ("Мёртвые души", "Сатира")], + {"Дубровский": "Приключения", "Мёртвые души": "Сатира"} # разные жанры + ), + ] + ) + def test_get_books_genre_parametrized(self, books_data, expected_dict): collector = BooksCollector() - collector.add_new_book("Гарри Поттер") - collector.add_new_book("Хоббит") - - collector.add_book_in_favorites("Гарри Поттер") - collector.add_book_in_favorites("Хоббит") - - # Удаляем одну книгу из избранного - collector.delete_book_from_favorites("Хоббит") - - result = collector.get_list_of_favorites_books() - - assert "Хоббит" not in result - assert "Гарри Поттер" in result - assert len(result) == 1 - - def test_get_list_of_favorites_books_unchanged_after_adding_non_favorite(self): - """Проверяем, что список избранного не меняется при добавлении книги, не помеченной как избранное.""" + # Добавляем книги и задаём жанры + for name, genre in books_data: + collector.add_new_book(name) + if genre: # если жанр указан + collector.set_book_genre(name, genre) + + result = collector.get_books_genre() + assert result == expected_dict + + # 9. Тесты get_book_genre + @pytest.mark.parametrize( + "book_name,books_data,expected_genre", + [ + ("Война и мир", [("Война и мир", "Классика")], "Классика"), # книга есть, жанр задан + ("1984", [("1984", "Фантастика")], "Фантастика"), # книга есть, жанр задан + ("Неизвестная книга", [], None), # книги нет в словаре + ("Без жанра", [("Без жанра", "")], ""), # книга есть, но жанр не задан + ("Дубровский", [("Дубровский", "Приключения")], "Приключения"), # другой жанр + ] + ) + def test_get_book_genre_parametrized(self, book_name, books_data, expected_genre): collector = BooksCollector() - collector.add_new_book("Анна Каренина") - collector.add_book_in_favorites("Анна Каренина") - - # Добавляем ещё одну книгу, но не в избранное - collector.add_new_book("Братья Карамазовы") - - result = collector.get_list_of_favorites_books() - - assert result == ["Анна Каренина"] - assert "Братья Карамазовы" not in result + # Предварительно добавляем книги из books_data + for name, genre in books_data: + collector.add_new_book(name) + if genre: + collector.set_book_genre(name, genre) + + result = collector.get_book_genre(book_name) + assert result == expected_genre