yessir
This commit is contained in:
107
book_stuff/book.py
Normal file
107
book_stuff/book.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
This file is the beginnings of a Book class. However, it isn't very well-coded.
|
||||
We want to harden it. We want to enforce the following rules:
|
||||
|
||||
- Title must not be blank, and cannot be longer than 255 characters.
|
||||
- Authors is a list of names. None of the names should be blank or over 255 characters.
|
||||
Names should not have any characters except for alphabetical ones.
|
||||
- ISBN is a string, but it can only contain numbers and there must be 13 of them.
|
||||
- Cost cannot be negative.
|
||||
|
||||
We are going to practice "test-driven development". This means we will write
|
||||
the test cases for our code before we write our code. Enough of the code is
|
||||
already given to you to show you how to use it. Don't update the code until
|
||||
you have written tests to check for all of the above criteria. When your code passes
|
||||
your tests, have your neighbor email you their tests and see if theirs passes as well.
|
||||
If not, modify your code accordingly.
|
||||
"""
|
||||
class Book:
|
||||
def __init__(self, title: str, authors: list[str], isbn: str, cost: float):
|
||||
self.set_title(title)
|
||||
self.set_authors(authors)
|
||||
self.set_isbn(isbn)
|
||||
self.set_cost(cost)
|
||||
|
||||
# --- Title ---
|
||||
def get_title(self) -> str:
|
||||
return self._title
|
||||
|
||||
def set_title(self, title: str) -> None:
|
||||
# check if title is string
|
||||
if not isinstance(title, str):
|
||||
raise TypeError(f'title must be of type str, not {type(title).__name__}')
|
||||
|
||||
# check if title is blank or if the title length is greater than 255 characters
|
||||
title_len = len(title)
|
||||
if title_len < 1 or title_len > 255:
|
||||
raise TypeError(f'title\'s length must be between 1 and 255, not {title_len}')
|
||||
|
||||
self._title = title
|
||||
|
||||
# --- Authors ---
|
||||
def get_authors(self) -> list[str]:
|
||||
return self._authors
|
||||
|
||||
def set_authors(self, authors: list[str]) -> None:
|
||||
# check if authors is a list
|
||||
if not isinstance(authors, list):
|
||||
raise TypeError(f'authors must be of type list, not {type(authors).__name__}')
|
||||
|
||||
# check if authors is empty
|
||||
authors_len = len(authors)
|
||||
if authors_len == 0:
|
||||
raise ValueError('authors list must not be empty')
|
||||
|
||||
for author in authors:
|
||||
# check if each author is a string
|
||||
if not isinstance(author, str):
|
||||
raise TypeError(f'each author within authors must be of type str, not {type(author).__name__}')
|
||||
|
||||
# check if each author is alphanumeric
|
||||
for author_name_part in author.split():
|
||||
# splitting by space to check each part of the author's name, spaces aren't alpha so no author.isalpha()
|
||||
if not author_name_part.isalpha():
|
||||
raise ValueError(f'each part of the author\'s name within authors must be alphanumeric')
|
||||
|
||||
# check if each author's length is greater than 255
|
||||
author_len = len(author)
|
||||
if author_len > 255:
|
||||
raise ValueError(f'each author\'s length within authors must be less than 256 characters')
|
||||
|
||||
self._authors = authors
|
||||
|
||||
# --- ISBN ---
|
||||
def get_isbn(self) -> str:
|
||||
return self._isbn
|
||||
|
||||
def set_isbn(self, isbn: str) -> None:
|
||||
# check is isbn is 13 characters
|
||||
isbn_len = len(isbn)
|
||||
if isbn_len != 13:
|
||||
raise ValueError(f'isbn\'s length must be 13 digits, not {isbn_len}')
|
||||
|
||||
# check if each character is a number
|
||||
if not isbn.isdigit():
|
||||
raise ValueError('each character in isbn must be a digit')
|
||||
|
||||
self._isbn = isbn
|
||||
|
||||
# --- Cost ---
|
||||
def get_cost(self) -> float:
|
||||
return self._cost
|
||||
|
||||
def set_cost(self, cost: float) -> None:
|
||||
# check if cost is float
|
||||
if not isinstance(cost, float):
|
||||
raise ValueError(f'cost must be of type float, not {type(cost).__name__}')
|
||||
|
||||
# check if cost is negative
|
||||
if cost < 0:
|
||||
raise ValueError("cost cannot be negative")
|
||||
|
||||
self._cost = cost
|
||||
|
||||
# --- String representation ---
|
||||
def __str__(self) -> str:
|
||||
authors = ", ".join(self._authors)
|
||||
return f"'{self._title}' by {authors} (ISBN: {self._isbn}, Cost: ${self._cost:.2f})"
|
||||
96
book_stuff/test_book.py
Normal file
96
book_stuff/test_book.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import pytest
|
||||
|
||||
from book import Book
|
||||
|
||||
_default_title = 'My Boke'
|
||||
_default_authors = ['Eliott Wootton', 'John Smith']
|
||||
_default_isbn = '1234567890123'
|
||||
_default_cost = 14.99
|
||||
|
||||
@pytest.fixture
|
||||
def valid_book():
|
||||
return Book(_default_title, _default_authors, _default_isbn, _default_cost)
|
||||
|
||||
# test if book setter works
|
||||
def test_create_valid_book(valid_book: Book):
|
||||
assert valid_book.get_title() == _default_title
|
||||
assert valid_book.get_authors() == _default_authors
|
||||
assert valid_book.get_isbn() == _default_isbn
|
||||
assert valid_book.get_cost() == _default_cost
|
||||
|
||||
# test if title setter works
|
||||
def test_update_title(valid_book: Book):
|
||||
valid_book.set_title('My Book')
|
||||
assert valid_book.get_title() == 'My Book'
|
||||
|
||||
# test if authors setter works
|
||||
def test_update_authors(valid_book: Book):
|
||||
valid_book.set_authors(['John Smith', 'Jane Smith'])
|
||||
assert valid_book.get_authors() == ['John Smith', 'Jane Smith']
|
||||
|
||||
# test if isbn setter works
|
||||
def test_update_isbn(valid_book: Book):
|
||||
valid_book.set_isbn('3210987654321')
|
||||
assert valid_book.get_isbn() == '3210987654321'
|
||||
|
||||
# test is cost setter works
|
||||
def test_update_cost(valid_book: Book):
|
||||
valid_book.set_cost(9.99)
|
||||
assert valid_book.get_cost() == 9.99
|
||||
|
||||
# test if repr works
|
||||
def test_repr_str(valid_book):
|
||||
rep = str(valid_book)
|
||||
assert _default_title in rep
|
||||
assert ', '.join(_default_authors) in rep
|
||||
assert _default_isbn in rep
|
||||
assert str(_default_cost) in rep
|
||||
|
||||
"""
|
||||
- Title must not be blank, and cannot be longer than 255 characters.
|
||||
- Authors is a list of names. None of the names should be blank or over 255 characters.
|
||||
Names should not have any characters except for alphabetical ones.
|
||||
- ISBN is a string, but it can only contain numbers and there must be 13 of them.
|
||||
- Cost cannot be negative.
|
||||
"""
|
||||
|
||||
# test if empty, length > 255, and if string
|
||||
@pytest.mark.parametrize("invalid_title", [
|
||||
"",
|
||||
"A" * 256,
|
||||
6.7
|
||||
])
|
||||
def test_invalid_title_raises(invalid_title: str):
|
||||
with pytest.raises((ValueError, TypeError)):
|
||||
Book(invalid_title, _default_authors, _default_isbn, _default_cost)
|
||||
|
||||
# test if empty, is array, each author's name is str, each author's name > 255 and each author's name alpha
|
||||
@pytest.mark.parametrize("invalid_authors", [
|
||||
[],
|
||||
'Eliott Wootton',
|
||||
[41, 'Charleston White'],
|
||||
['LeBron James', 'A'*256],
|
||||
['3liott W00tt0n', 'hello world']
|
||||
])
|
||||
def test_invalid_authors_raised(invalid_authors: str):
|
||||
with pytest.raises((ValueError, TypeError)):
|
||||
Book(_default_title, invalid_authors, _default_isbn, _default_cost)
|
||||
|
||||
# test if string, only contains numbers and length is 13
|
||||
@pytest.mark.parametrize("invalid_isbn", [
|
||||
127,
|
||||
'123456789o123',
|
||||
'12345678901234'
|
||||
])
|
||||
def test_invalid_isbn_raises(invalid_isbn: str):
|
||||
with pytest.raises((ValueError, TypeError)):
|
||||
Book(_default_title, _default_authors, invalid_isbn, _default_cost)
|
||||
|
||||
# test if float and not negative
|
||||
@pytest.mark.parametrize("invalid_cost", [
|
||||
'14.99',
|
||||
-14.99
|
||||
])
|
||||
def test_invalid_cost_raises(invalid_cost: str):
|
||||
with pytest.raises((ValueError, TypeError)):
|
||||
Book(_default_title, _default_authors, _default_isbn, invalid_cost)
|
||||
Reference in New Issue
Block a user