Проект объектно-ориентированного программирования на python для вашего портфолио на github

Недавно я разработал систему проката велосипедов на Python с использованием объектно-ориентированного программирования (ООП) и хотел бы поделиться своими мыслями со всеми, кто хочет учиться.

Давайте начнем с подробностей проекта и изучим его шаг за шагом. Клонировать проект на

Ссылка на Github: https://github.com/gurupratap-matharu/Bike-Rental-System

Система проката велосипедов

Полноценная система проката велосипедов, реализованная на Python с использованием объектно-ориентированного программирования.

Клиенты могут

  • Посмотреть доступные велосипеды в магазине
  • Прокат велосипедов на почасовой основе 5 долларов в час.
  • Прокат велосипедов посуточно 20 долларов в день.
  • Прокат велосипедов на еженедельной основе 60 долларов в неделю.
  • Семейная аренда, акция, которая может включать от 3 до 5 аренды (любого типа) со скидкой 30% от общей стоимости.

В магазине проката велосипедов можно

  • выставить счет, когда клиент решит вернуть велосипед.
  • отображать доступный инвентарь
  • принимать запросы ежечасно, ежедневно и еженедельно путем перекрестной проверки запасов

Для простоты мы предполагаем, что

  1. Любой клиент запрашивает аренду только одного типа, то есть почасово, ежемесячно или еженедельно.
  2. Свободно выбирает количество велосипедов, которые он / она хочет
  3. Запрошенных велосипедов должно быть меньше, чем имеется в наличии.

В ООП, чтобы писать методы для классов, подумайте, какой функционал мог бы иметь класс?

Давайте погрузимся в основной код ниже. Смотрите мои комментарии, чтобы лучше понять

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 в корень вашего репозитория, чтобы объяснить: ваш дизайн, применяемые методы разработки и способы запуска тестов.

Надеюсь, вы кое-что узнали из этого. Поделитесь своим кодом, я тоже хотел бы поучиться у вас. До скорого.