Skip to content

Commit e58a669

Browse files
authored
added project example for Robot Framework (#108)
1 parent 8319a1f commit e58a669

File tree

11 files changed

+1045
-0
lines changed

11 files changed

+1045
-0
lines changed

robot-framework-python/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Library Management System
2+
3+
A comprehensive library management system for testing Robot Framework integration with Testomat.io
4+
5+
## Installation
6+
```bash
7+
pip install robot-framework-reporter
8+
```
9+
10+
## Running Tests
11+
```bash
12+
# Run all tests
13+
robot tests/
14+
15+
# Run specific test suite
16+
robot tests/test_book_management.robot
17+
robot tests/test_borrowing.robot
18+
19+
# Run tests with specific tags
20+
robot --include positive tests/
21+
robot --include borrow tests/
22+
robot --include fine tests/
23+
```
24+
## Load Tests To Testomat.io
25+
1. Create empty project in Testomat.io
26+
2. Obtain API key from Testomat.io
27+
```bash
28+
robot --listener reporter.listener.ImportListener tests/
29+
```
30+
31+
## Reporting Tests To Testomat.io
32+
```bash
33+
robot --listener reporter.listener.ReportListener tests/
34+
```
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from .book import Book
2+
from .member import Member
3+
from .library_system import LibrarySystem
4+
from .exceptions import (
5+
BookNotAvailableError,
6+
BookNotFoundError,
7+
MemberNotFoundError,
8+
MaxBooksExceededError,
9+
InvalidDateError
10+
)
11+
12+
__all__ = [
13+
'Book',
14+
'Member',
15+
'LibrarySystem',
16+
'BookNotAvailableError',
17+
'BookNotFoundError',
18+
'MemberNotFoundError',
19+
'MaxBooksExceededError',
20+
'InvalidDateError'
21+
]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
class Book:
3+
"""Represents a book in the library."""
4+
5+
def __init__(self, isbn, title, author, year, genre, copies=1):
6+
self.isbn = isbn
7+
self.title = title
8+
self.author = author
9+
self.year = year
10+
self.genre = genre
11+
self.total_copies = int(copies)
12+
self.available_copies = int(copies)
13+
self.borrowed_by = [] # List of (member_id, borrow_date) tuples
14+
15+
def is_available(self):
16+
"""Check if book is available for borrowing."""
17+
return self.available_copies > 0
18+
19+
def borrow(self, member_id, date):
20+
"""Mark book as borrowed."""
21+
if not self.is_available():
22+
return False
23+
self.available_copies -= 1
24+
self.borrowed_by.append((member_id, date))
25+
return True
26+
27+
def return_book(self, member_id):
28+
"""Mark book as returned."""
29+
for i, (mid, _) in enumerate(self.borrowed_by):
30+
if mid == member_id:
31+
self.borrowed_by.pop(i)
32+
self.available_copies += 1
33+
return True
34+
return False
35+
36+
def get_info(self):
37+
"""Get book information as dictionary."""
38+
return {
39+
'isbn': self.isbn,
40+
'title': self.title,
41+
'author': self.author,
42+
'year': self.year,
43+
'genre': self.genre,
44+
'total_copies': self.total_copies,
45+
'available_copies': self.available_copies,
46+
'borrowed_count': len(self.borrowed_by)
47+
}
48+
49+
def __str__(self):
50+
return f"{self.title} by {self.author} ({self.year})"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
3+
class LibraryException(Exception):
4+
"""Base exception for library system."""
5+
pass
6+
7+
8+
class BookNotFoundError(LibraryException):
9+
"""Raised when a book is not found."""
10+
pass
11+
12+
13+
class BookNotAvailableError(LibraryException):
14+
"""Raised when a book is not available for borrowing."""
15+
pass
16+
17+
18+
class MemberNotFoundError(LibraryException):
19+
"""Raised when a member is not found."""
20+
pass
21+
22+
23+
class MaxBooksExceededError(LibraryException):
24+
"""Raised when member exceeds maximum borrowed books limit."""
25+
pass
26+
27+
28+
class InvalidDateError(LibraryException):
29+
"""Raised when an invalid date is provided."""
30+
pass
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
from .book import Book
2+
from .member import Member
3+
from .exceptions import (
4+
BookNotAvailableError,
5+
BookNotFoundError,
6+
MemberNotFoundError,
7+
MaxBooksExceededError
8+
)
9+
10+
11+
class LibrarySystem:
12+
"""Main library management system."""
13+
14+
def __init__(self, name="City Library"):
15+
self.name = name
16+
self.books = {} # {isbn: Book}
17+
self.members = {} # {member_id: Member}
18+
self.transaction_history = []
19+
20+
# Book Management
21+
def add_book(self, isbn, title, author, year, genre, copies=1):
22+
"""Add a new book to the library."""
23+
if isbn in self.books:
24+
# If book exists, just increase copies
25+
self.books[isbn].total_copies += int(copies)
26+
self.books[isbn].available_copies += int(copies)
27+
else:
28+
self.books[isbn] = Book(isbn, title, author, year, genre, copies)
29+
return f"Added {copies} copy/copies of '{title}'"
30+
31+
def remove_book(self, isbn):
32+
"""Remove a book from the library."""
33+
if isbn not in self.books:
34+
raise BookNotFoundError(f"Book with ISBN {isbn} not found")
35+
36+
if self.books[isbn].available_copies < self.books[isbn].total_copies:
37+
return "Cannot remove book: some copies are borrowed"
38+
39+
del self.books[isbn]
40+
return "Book removed successfully"
41+
42+
def get_book(self, isbn):
43+
"""Get book by ISBN."""
44+
if isbn not in self.books:
45+
raise BookNotFoundError(f"Book with ISBN {isbn} not found")
46+
return self.books[isbn]
47+
48+
def search_books_by_title(self, title):
49+
"""Search books by title (case-insensitive partial match)."""
50+
results = []
51+
for book in self.books.values():
52+
if title.lower() in book.title.lower():
53+
results.append(book)
54+
return results
55+
56+
def search_books_by_author(self, author):
57+
"""Search books by author (case-insensitive partial match)."""
58+
results = []
59+
for book in self.books.values():
60+
if author.lower() in book.author.lower():
61+
results.append(book)
62+
return results
63+
64+
def get_books_by_genre(self, genre):
65+
"""Get all books of a specific genre."""
66+
results = []
67+
for book in self.books.values():
68+
if book.genre.lower() == genre.lower():
69+
results.append(book)
70+
return results
71+
72+
def get_available_books(self):
73+
"""Get all available books."""
74+
return [book for book in self.books.values() if book.is_available()]
75+
76+
def get_total_books_count(self):
77+
"""Get total number of books in library."""
78+
return sum(book.total_copies for book in self.books.values())
79+
80+
def get_available_books_count(self):
81+
"""Get number of available books."""
82+
return sum(book.available_copies for book in self.books.values())
83+
84+
# Member Management
85+
def register_member(self, member_id, name, email, membership_type='regular'):
86+
"""Register a new member."""
87+
if member_id in self.members:
88+
return "Member ID already exists"
89+
90+
self.members[member_id] = Member(member_id, name, email, membership_type)
91+
return f"Member {name} registered successfully"
92+
93+
def remove_member(self, member_id):
94+
"""Remove a member."""
95+
if member_id not in self.members:
96+
raise MemberNotFoundError(f"Member {member_id} not found")
97+
98+
if len(self.members[member_id].borrowed_books) > 0:
99+
return "Cannot remove member: has borrowed books"
100+
101+
del self.members[member_id]
102+
return "Member removed successfully"
103+
104+
def get_member(self, member_id):
105+
"""Get member by ID."""
106+
if member_id not in self.members:
107+
raise MemberNotFoundError(f"Member {member_id} not found")
108+
return self.members[member_id]
109+
110+
def suspend_member(self, member_id):
111+
"""Suspend a member."""
112+
member = self.get_member(member_id)
113+
member.suspend()
114+
return f"Member {member_id} suspended"
115+
116+
def reactivate_member(self, member_id):
117+
"""Reactivate a suspended member."""
118+
member = self.get_member(member_id)
119+
member.reactivate()
120+
return f"Member {member_id} reactivated"
121+
122+
# Borrowing and Returning
123+
def borrow_book(self, member_id, isbn, date):
124+
"""Process book borrowing."""
125+
member = self.get_member(member_id)
126+
book = self.get_book(isbn)
127+
128+
if not member.is_active:
129+
raise MaxBooksExceededError("Member account is suspended")
130+
131+
if not book.is_available():
132+
raise BookNotAvailableError(f"Book '{book.title}' is not available")
133+
134+
if not member.can_borrow():
135+
raise MaxBooksExceededError("Member has reached maximum borrowed books limit")
136+
137+
book.borrow(member_id, date)
138+
member.borrow_book(isbn, date)
139+
140+
self.transaction_history.append({
141+
'type': 'borrow',
142+
'member_id': member_id,
143+
'isbn': isbn,
144+
'date': date
145+
})
146+
147+
return f"Book '{book.title}' borrowed by {member.name}"
148+
149+
def return_book(self, member_id, isbn, return_date):
150+
"""Process book return."""
151+
member = self.get_member(member_id)
152+
book = self.get_book(isbn)
153+
154+
if isbn not in member.borrowed_books:
155+
return "This book was not borrowed by this member"
156+
157+
# Calculate and add fine if overdue
158+
fine = member.calculate_fine(isbn, return_date)
159+
if fine > 0:
160+
member.add_fine(fine)
161+
162+
book.return_book(member_id)
163+
member.return_book(isbn)
164+
165+
self.transaction_history.append({
166+
'type': 'return',
167+
'member_id': member_id,
168+
'isbn': isbn,
169+
'date': return_date,
170+
'fine': fine
171+
})
172+
173+
return f"Book '{book.title}' returned. Fine: ${fine:.2f}"
174+
175+
# Statistics
176+
def get_most_borrowed_books(self, limit=5):
177+
"""Get most borrowed books."""
178+
books_with_count = [(book, len(book.borrowed_by)) for book in self.books.values()]
179+
books_with_count.sort(key=lambda x: x[1], reverse=True)
180+
return [book for book, _ in books_with_count[:limit]]
181+
182+
def get_members_with_fines(self):
183+
"""Get all members who have outstanding fines."""
184+
return [member for member in self.members.values() if member.fine_amount > 0]
185+
186+
def get_overdue_books(self):
187+
"""Get all overdue books."""
188+
from datetime import datetime, timedelta
189+
overdue = []
190+
191+
for member in self.members.values():
192+
for isbn, borrow_date in member.borrowed_books.items():
193+
borrow_datetime = datetime.strptime(borrow_date, '%Y-%m-%d')
194+
due_date = borrow_datetime + timedelta(days=Member.LOAN_PERIOD_DAYS)
195+
if datetime.now() > due_date:
196+
overdue.append({
197+
'member': member,
198+
'book': self.books[isbn],
199+
'due_date': due_date.strftime('%Y-%m-%d')
200+
})
201+
202+
return overdue
203+
204+
def get_statistics(self):
205+
"""Get library statistics."""
206+
return {
207+
'total_books': len(self.books),
208+
'total_copies': self.get_total_books_count(),
209+
'available_copies': self.get_available_books_count(),
210+
'borrowed_copies': self.get_total_books_count() - self.get_available_books_count(),
211+
'total_members': len(self.members),
212+
'active_members': len([m for m in self.members.values() if m.is_active]),
213+
'members_with_fines': len(self.get_members_with_fines()),
214+
'total_transactions': len(self.transaction_history)
215+
}

0 commit comments

Comments
 (0)