Проект объектно-ориентированного программирования на python для вашего портфолио на github
Недавно я разработал систему проката велосипедов на Python с использованием объектно-ориентированного программирования (ООП) и хотел бы поделиться своими мыслями со всеми, кто хочет учиться.
Давайте начнем с подробностей проекта и изучим его шаг за шагом. Клонировать проект на
Ссылка на Github: https://github.com/gurupratap-matharu/Bike-Rental-System
Система проката велосипедов
Полноценная система проката велосипедов, реализованная на Python с использованием объектно-ориентированного программирования.
Клиенты могут
- Посмотреть доступные велосипеды в магазине
- Прокат велосипедов на почасовой основе 5 долларов в час.
- Прокат велосипедов посуточно 20 долларов в день.
- Прокат велосипедов на еженедельной основе 60 долларов в неделю.
- Семейная аренда, акция, которая может включать от 3 до 5 аренды (любого типа) со скидкой 30% от общей стоимости.
В магазине проката велосипедов можно
- выставить счет, когда клиент решит вернуть велосипед.
- отображать доступный инвентарь
- принимать запросы ежечасно, ежедневно и еженедельно путем перекрестной проверки запасов
Для простоты мы предполагаем, что
- Любой клиент запрашивает аренду только одного типа, то есть почасово, ежемесячно или еженедельно.
- Свободно выбирает количество велосипедов, которые он / она хочет
- Запрошенных велосипедов должно быть меньше, чем имеется в наличии.
В ООП, чтобы писать методы для классов, подумайте, какой функционал мог бы иметь класс?
Давайте погрузимся в основной код ниже. Смотрите мои комментарии, чтобы лучше понять
import datetime class BikeRental: def __init__(self,stock=0): """ Our constructor class that instantiates bike rental shop. """ self.stock = stock def displaystock(self): """ Displays the bikes currently available for rent in the shop. """ print("We have currently {} bikes available to rent.".format(self.stock)) return self.stock def rentBikeOnHourlyBasis(self, n): """ Rents a bike on hourly basis to a customer. """ # reject invalid input if n <= 0: print("Number of bikes should be positive!") return None # do not rent bike is stock is less than requested bikes elif n > self.stock: print("Sorry! We have currently {} bikes available to rent.".format(self.stock)) return None # rent the bikes else: now = datetime.datetime.now() print("You have rented a {} bike(s) on hourly basis today at {} hours.".format(n,now.hour)) print("You will be charged $5 for each hour per bike.") print("We hope that you enjoy our service.") self.stock -= n return now def rentBikeOnDailyBasis(self, n): """ Rents a bike on daily basis to a customer. """ if n <= 0: print("Number of bikes should be positive!") return None elif n > self.stock: print("Sorry! We have currently {} bikes available to rent.".format(self.stock)) return None else: now = datetime.datetime.now() print("You have rented {} bike(s) on daily basis today at {} hours.".format(n, now.hour)) print("You will be charged $20 for each day per bike.") print("We hope that you enjoy our service.") self.stock -= n return now def rentBikeOnWeeklyBasis(self, n): """ Rents a bike on weekly basis to a customer. """ if n <= 0: print("Number of bikes should be positive!") return None elif n > self.stock: print("Sorry! We have currently {} bikes available to rent.".format(self.stock)) return None else: now = datetime.datetime.now() print("You have rented {} bike(s) on weekly basis today at {} hours.".format(n, now.hour)) print("You will be charged $60 for each week per bike.") print("We hope that you enjoy our service.") self.stock -= n return now def returnBike(self, request): """ 1. Accept a rented bike from a customer 2. Replensihes the inventory 3. Return a bill """ # extract the tuple and initiate bill rentalTime, rentalBasis, numOfBikes = request bill = 0 # issue a bill only if all three parameters are not null! if rentalTime and rentalBasis and numOfBikes: self.stock += numOfBikes now = datetime.datetime.now() rentalPeriod = now - rentalTime # hourly bill calculation if rentalBasis == 1: bill = round(rentalPeriod.seconds / 3600) * 5 * numOfBikes # daily bill calculation elif rentalBasis == 2: bill = round(rentalPeriod.days) * 20 * numOfBikes # weekly bill calculation elif rentalBasis == 3: bill = round(rentalPeriod.days / 7) * 60 * numOfBikes # family discount calculation if (3 <= numOfBikes <= 5): print("You are eligible for Family rental promotion of 30% discount") bill = bill * 0.7 print("Thanks for returning your bike. Hope you enjoyed our service!") print("That would be ${}".format(bill)) return bill else: print("Are you sure you rented a bike with us?") return None class Customer: def __init__(self): """ Our constructor method which instantiates various customer objects. """ self.bikes = 0 self.rentalBasis = 0 self.rentalTime = 0 self.bill = 0 def requestBike(self): """ Takes a request from the customer for the number of bikes. """ bikes = input("How many bikes would you like to rent?") # implement logic for invalid input try: bikes = int(bikes) except ValueError: print("That's not a positive integer!") return -1 if bikes < 1: print("Invalid input. Number of bikes should be greater than zero!") return -1 else: self.bikes = bikes return self.bikes def returnBike(self): """ Allows customers to return their bikes to the rental shop. """ if self.rentalBasis and self.rentalTime and self.bikes: return self.rentalTime, self.rentalBasis, self.bikes else: return 0,0,0
Всегда пишите строки документации для ваших функций и четкие комментарии для ваших логических утверждений.
Разработка через тестирование (TDD)
- Тесты сэкономят ваше время
- Тесты не просто выявляют проблемы, они предотвращают их
- Тесты делают ваш код более привлекательным
- Тесты помогают командам работать вместе
«Код без тестов нарушен по замыслу».
Теперь перейдем к модулю модульного тестирования. Обратите внимание на следующее
- Методы испытаний следуют за змеиным случаем
- Методы тестирования носят описательный характер, и их названия говорят о том, какие функции они тестируют.
- Всегда проверяйте очень экстремальные входные данные, такие как нулевые значения, нулевые массивы, нецелочисленные входные данные, недопустимые даты
- тестовые файлы часто работают дольше, чем основная программа, но это хорошо.
Резервирование тестовых модулей - это хорошо!
import unittest from datetime import datetime, timedelta from bikeRental import BikeRental, Customer class BikeRentalTest(unittest.TestCase): def test_Bike_Rental_diplays_correct_stock(self): shop1 = BikeRental() shop2 = BikeRental(10) self.assertEqual(shop1.displaystock(), 0) self.assertEqual(shop2.displaystock(), 10) def test_rentBikeOnHourlyBasis_for_negative_number_of_bikes(self): shop = BikeRental(10) self.assertEqual(shop.rentBikeOnHourlyBasis(-1), None) def test_rentBikeOnHourlyBasis_for_zero_number_of_bikes(self): shop = BikeRental(10) self.assertEqual(shop.rentBikeOnHourlyBasis(0), None) def test_rentBikeOnHourlyBasis_for_valid_positive_number_of_bikes(self): shop = BikeRental(10) hour = datetime.now().hour self.assertEqual(shop.rentBikeOnHourlyBasis(2).hour, hour) def test_rentBikeOnHourlyBasis_for_invalid_positive_number_of_bikes(self): shop = BikeRental(10) self.assertEqual(shop.rentBikeOnHourlyBasis(11), None) def test_rentBikeOnDailyBasis_for_negative_number_of_bikes(self): shop = BikeRental(10) self.assertEqual(shop.rentBikeOnDailyBasis(-1), None) def test_rentBikeOnDailyBasis_for_zero_number_of_bikes(self): shop = BikeRental(10) self.assertEqual(shop.rentBikeOnDailyBasis(0), None) def test_rentBikeOnDailyBasis_for_valid_positive_number_of_bikes(self): shop = BikeRental(10) hour = datetime.now().hour self.assertEqual(shop.rentBikeOnDailyBasis(2).hour, hour) def test_rentBikeOnDailyBasis_for_invalid_positive_number_of_bikes(self): shop = BikeRental(10) self.assertEqual(shop.rentBikeOnDailyBasis(11), None) def test_rentBikeOnWeeklyBasis_for_negative_number_of_bikes(self): shop = BikeRental(10) self.assertEqual(shop.rentBikeOnWeeklyBasis(-1), None) def test_rentBikeOnWeeklyBasis_for_zero_number_of_bikes(self): shop = BikeRental(10) self.assertEqual(shop.rentBikeOnWeeklyBasis(0), None) def test_rentBikeOnWeeklyBasis_for_valid_positive_number_of_bikes(self): shop = BikeRental(10) hour = datetime.now().hour self.assertEqual(shop.rentBikeOnWeeklyBasis(2).hour, hour) def test_rentBikeOnWeeklyBasis_for_invalid_positive_number_of_bikes(self): shop = BikeRental(10) self.assertEqual(shop.rentBikeOnWeeklyBasis(11), None) def test_returnBike_for_invalid_rentalTime(self): # create a shop and a customer shop = BikeRental(10) customer = Customer() # let the customer not rent a bike a try to return one. request = customer.returnBike() self.assertIsNone(shop.returnBike(request)) # manually check return function with error values self.assertIsNone(shop.returnBike((0,0,0))) def test_returnBike_for_invalid_rentalBasis(self): # create a shop and a customer shop = BikeRental(10) customer = Customer() # create valid rentalTime and bikes customer.rentalTime = datetime.now() customer.bikes = 3 # create invalid rentalbasis customer.rentalBasis = 7 request = customer.returnBike() self.assertEqual(shop.returnBike(request), 0) def test_returnBike_for_invalid_numOfBikes(self): # create a shop and a customer shop = BikeRental(10) customer = Customer() # create valid rentalTime and rentalBasis customer.rentalTime = datetime.now() customer.rentalBasis = 1 # create invalid bikes customer.bikes = 0 request = customer.returnBike() self.assertIsNone(shop.returnBike(request)) def test_returnBike_for_valid_credentials(self): # create a shop and a various customers shop = BikeRental(50) customer1 = Customer() customer2 = Customer() customer3 = Customer() customer4 = Customer() customer5 = Customer() customer6 = Customer() # create valid rentalBasis for each customer customer1.rentalBasis = 1 # hourly customer2.rentalBasis = 1 # hourly customer3.rentalBasis = 2 # daily customer4.rentalBasis = 2 # daily customer5.rentalBasis = 3 # weekly customer6.rentalBasis = 3 # weekly # create valid bikes for each customer customer1.bikes = 1 customer2.bikes = 5 # eligible for family discount 30% customer3.bikes = 2 customer4.bikes = 8 customer5.bikes = 15 customer6.bikes = 30 # create past valid rental times for each customer customer1.rentalTime = datetime.now() + timedelta(hours=-4) customer2.rentalTime = datetime.now() + timedelta(hours=-23) customer3.rentalTime = datetime.now() + timedelta(days=-4) customer4.rentalTime = datetime.now() + timedelta(days=-13) customer5.rentalTime = datetime.now() + timedelta(weeks=-6) customer6.rentalTime = datetime.now() + timedelta(weeks=-12) # make all customers return their bikes request1 = customer1.returnBike() request2 = customer2.returnBike() request3 = customer3.returnBike() request4 = customer4.returnBike() request5 = customer5.returnBike() request6 = customer6.returnBike() # check if all of them get correct bill self.assertEqual(shop.returnBike(request1), 20) self.assertEqual(shop.returnBike(request2), 402.5) self.assertEqual(shop.returnBike(request3), 160) self.assertEqual(shop.returnBike(request4), 2080) self.assertEqual(shop.returnBike(request5), 5400) self.assertEqual(shop.returnBike(request6), 21600) class CustomerTest(unittest.TestCase): def test_return_Bike_with_valid_input(self): # create a customer customer = Customer() # create valid rentalTime, rentalBasis, bikes now = datetime.now() customer.rentalTime = now customer.rentalBasis = 1 customer.bikes = 4 self.assertEqual(customer.returnBike(),(now,1, 4)) def test_return_Bike_with_invalid_input(self): # create a customer customer = Customer() # create valid rentalBasis and bikes customer.rentalBasis = 1 customer.bikes = 0 # create invalid rentalTime customer.rentalTime = 0 self.assertEqual(customer.returnBike(),(0,0,0)) if __name__ == '__main__': unittest.main()
Модуль тестирования написан вместе с основной программой для тщательной проверки классов и методов на наличие ошибок.
Большинство ошибок возникает в значениях NULL, отрицательных значениях и нецелочисленных входных данных.
В случае несоответствия, пожалуйста, поднимите вопрос или отправьте запрос на перенос.
Запуск тестов
Для запуска тестов выполните соответствующую команду ниже (почему они разные):
- Python 2.7: py.test bikeRental_test.py
- Python 3.4+: pytest bikeRental_test.py
В качестве альтернативы вы можете указать Python запустить модуль pytest (позволяя использовать ту же команду независимо от ss версии Python): python -m pytest bikeRental_test.py
Общие параметры pytest
- -v: включить подробный вывод
- -x: остановить выполнение тестов при первом сбое
- - ff: запускать ошибки из предыдущего теста перед запуском других тестовых примеров
Для других вариантов см. Python -m pytest -h
Как бежать?
Этот код написан на python3.6. Просто беги
python main.py # or depending upon your config python3 main.py
Я рекомендую вам писать аналогичные проекты в ООП, чтобы получить глубокое понимание языка Python и, более того, того, как писать профессиональный проект.
Подводя итог, ваш проект должен включать в себя следующие
- Реализуйте набор классов для моделирования этой области и логики, короче рабочего кода!
- Добавьте автоматические тесты, чтобы обеспечить охват более 85%
- Используйте GitHub для хранения и версии вашего кода
- Применяйте все рекомендуемые методы, которые вы бы использовали в реальном проекте.
- Добавьте файл README.md в корень вашего репозитория, чтобы объяснить: ваш дизайн, применяемые методы разработки и способы запуска тестов.
Надеюсь, вы кое-что узнали из этого. Поделитесь своим кодом, я тоже хотел бы поучиться у вас. До скорого.