Skip to content

Commit

Permalink
Slightly simplify examples
Browse files Browse the repository at this point in the history
  • Loading branch information
elchupanebrej committed Jan 16, 2024
1 parent bcc1408 commit 7ef30de
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 28 deletions.
2 changes: 1 addition & 1 deletion docs/tutorial/features/books.feature
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Scenario: The catalog can be searched by author name.
| Author | Title |
| Stephen King | The Shining |
| James Baldwin | If Beale Street Could Talk |
When a name search is performed for "Stephen"
When a name search is performed for Stephen
Then only these books will be returned
| Author | Title |
| Stephen King | The Shining |
25 changes: 14 additions & 11 deletions docs/tutorial/src/catalog.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
"""This files represents simple `Application under test`"""
from typing import List
from dataclasses import dataclass, field
from typing import Iterable, List

from attr import Factory, attrib, attrs


@attrs
@dataclass # Easy way to not write redundant __init__ https://docs.python.org/3/library/dataclasses.html
class Book:
author = attrib(type=str)
title = attrib(type=str)
author: str
title: str


@attrs
@dataclass
class Catalog:
storage = attrib(default=Factory(list))
storage: List[Book] = field(default_factory=list)

def add_books_to_catalog(self, books: List[Book]):
def add_books_to_catalog(self, books: Iterable[Book]):
self.storage.extend(books)

def search_by_author(self, term: str):
return filter(lambda book: term in book.author, self.storage)
for book in self.storage:
if term in book.author:
yield book

def search_by_title(self, term: str):
return filter(lambda book: term in book.title, self.storage)
for book in self.storage:
if term in book.title:
yield book
45 changes: 29 additions & 16 deletions docs/tutorial/tests/steps/library_steps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import re
from typing import Literal
from typing import List, Literal

from src.catalog import Book, Catalog

Expand All @@ -8,13 +8,19 @@


def get_books_from_data_table(data_table: DataTable):
# Gherkin data-tables have no title row by default, but we could use if we want.
step_data_table_titles = [cell.value for cell in data_table.rows[0].cells]
# Gherkin data-tables have no title row by default, but we could define them if we want.
title_row, *book_rows = data_table.rows

step_data_table_titles = []
for cell in title_row.cells:
step_data_table_titles.append(cell.value)

assert step_data_table_titles == ["Author", "Title"]

author_and_title_list = [[cell.value for cell in row.cells] for row in data_table.rows[1:]]
books = []
for row in book_rows:
books.append(Book(row.cells[0].value, row.cells[1].value))

books = [Book(author, title) for author, title in author_and_title_list]
return books


Expand All @@ -25,48 +31,55 @@ def get_books_from_data_table(data_table: DataTable):
target_fixture="catalog",
)
def these_books_in_the_catalog(
# `step` fixture is injected by pytest DI mechanism into scope of step by default
# `step` fixture is injected by pytest dependency injection mechanism into scope of step by default;
# So it could be used without extra effort
step: Step,
):
catalog = Catalog()

books = get_books_from_data_table(step.data_table)

catalog = Catalog()
catalog.add_books_to_catalog(books)

yield catalog


@when(
# Step definitions could have parameters. Here could be raw stings, cucumber expressions or regular expressions
re.compile('a (?P<search_type>name|title) search is performed for "(?P<search_term>.+)"'),
re.compile("a (?P<search_type>name|title) search is performed for (?P<search_term>.+)"),
target_fixture="search_results",
)
def a_search_type_is_performed_for_search_term(
# `search_results` is a usual pytest fixture defined somewhere else and injected by pytest DI mechanism.
# `search_results` is a usual pytest fixture defined somewhere else (at conftest.py, plugin or module) and injected by pytest dependency injection mechanism.
# In this case it will be provided by conftest.py
search_results,
search_results: List[Book],
# `search_type` and `search_term` are parameters of this step and are injected by step definition
search_type: Literal["name", "title"],
search_term: str,
# `catalog` is a fixture injected by another step
catalog: Catalog,
):
if search_type == "title":
search_results.extend(catalog.search_by_title(search_term))
search = catalog.search_by_title
elif search_type == "name":
search_results.extend(catalog.search_by_author(search_term))
search = catalog.search_by_author
else:
assert False, "Unknown"

found_books = search(search_term)
search_results.extend(found_books)
yield search_results


@then("only these books will be returned")
def only_these_books_will_be_returned(
# fixtures persist during step execution, so usual context is not required,
# Fixtures persist during step execution, so usual `context` common for behave users is not required,
# so if you define fixture dependencies debugging becomes much easier.
search_results,
search_results: List[Book],
step: Step,
catalog: Catalog,
):
expected_books = get_books_from_data_table(step.data_table)
assert all([book in catalog.storage for book in expected_books])

for book in search_results:
if book not in expected_books:
assert False, f"Book ${book} is not expected"

0 comments on commit 7ef30de

Please sign in to comment.