{"id":26967317,"url":"https://github.com/andrehora/library","last_synced_at":"2025-04-09T15:48:50.852Z","repository":{"id":285836334,"uuid":"959511224","full_name":"andrehora/library","owner":"andrehora","description":"Library refactoring example","archived":false,"fork":false,"pushed_at":"2025-04-02T23:11:43.000Z","size":0,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-02T23:30:14.831Z","etag":null,"topics":["example","python","refactoring"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andrehora.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-04-02T22:49:39.000Z","updated_at":"2025-04-02T23:11:52.000Z","dependencies_parsed_at":"2025-04-02T23:40:28.535Z","dependency_job_id":null,"html_url":"https://github.com/andrehora/library","commit_stats":null,"previous_names":["andrehora/library"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrehora%2Flibrary","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrehora%2Flibrary/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrehora%2Flibrary/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andrehora%2Flibrary/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andrehora","download_url":"https://codeload.github.com/andrehora/library/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248063266,"owners_count":21041746,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["example","python","refactoring"],"created_at":"2025-04-03T08:50:39.685Z","updated_at":"2025-04-09T15:48:50.832Z","avatar_url":"https://github.com/andrehora.png","language":"Python","readme":"[![tests](https://github.com/andrehora/library/actions/workflows/tests.yml/badge.svg)](https://github.com/andrehora/library/actions/workflows/tests.yml)\n\n# Library refactoring example\n\nNeste exercício, iremos refatorar um sistema simples para aluguel de livros de uma biblioteca.\nEste exercício é adaptado do livro *Refactoring* de Martin Fowler e Kent Beck.\nVocê deve realizar os 5 commits descritos abaixo e submeter os 5 links dos commits via Moodle.\n\n### Overview\n\nPrimeiramente, explore o código do sistema em [model.py](https://github.com/andrehora/library/blob/main/model.py).\nNote que temos três classes: `Book` (livros que podem ser alugados), `Rental` (dados de um aluguel) e `Client` (clientes da biblioteca).\nA classe `Client` possui um método `statement`, responsável por gerar o recibo do aluguel para o cliente:\n\n```python\n    def statement(self) -\u003e str:\n\n        total_amount = 0\n        frequent_renter_points = 0\n        result = f\"Rental summary for {self.name}\\n\"\n        \n        for rental in self._rentals:\n            amount = 0\n            \n            # determine amounts for each line\n            if rental.book.price_code == Book.REGULAR:\n                amount += 2\n                if rental.days_rented \u003e 2:\n                    amount += (rental.days_rented - 2) * 1.5\n            elif rental.book.price_code == Book.NEW_RELEASE:\n                amount += rental.days_rented * 3\n            elif rental.book.price_code == Book.CHILDREN:\n                amount += 1.5\n                if rental.days_rented \u003e 3:\n                    amount += (rental.days_rented - 3) * 1.5\n\n            # add frequent renter points\n            frequent_renter_points += 1\n            if rental.book.price_code == Book.NEW_RELEASE and rental.days_rented \u003e 1:\n                frequent_renter_points += 1\n\n            # show each rental result\n            result += f\"- {rental.book.title}: {amount}\\n\"\n            total_amount += amount\n        \n        # show total result\n        result += f\"Total: {total_amount}\\n\"\n        result += f\"Points: {frequent_renter_points}\"\n        return result\n```\n\nReflita sobre os possíveis problemas do método `statement`:\n- Esse método possui muitas responsabilidades?\n- Como adicionar um novo tipo filme?\n- Como adicionar um novo tipo de recibo, por exemplo, HTML, CSV, JSON, etc?\n\nExplore também os testes em [tests.py](https://github.com/andrehora/library/blob/main/tests.py) para entender melhor como o sistema funciona.\nPor exemplo, o teste `test_rent_regular_book_short_duration`:\n\n```python\ndef test_rent_regular_book_short_duration():\n    book = Book(\"Refactoring\", Book.REGULAR)\n    r = Rental(book, 2)\n\n    c = Client(\"Fulano\")\n    c.add_rental(r)\n    \n    expected_report = (\n        \"Rental summary for Fulano\\n\"\n        \"- Refactoring: 2\\n\"\n        \"Total: 2\\n\"\n        \"Points: 1\"\n    )\n    \n    assert c.statement() == expected_report\n```\n\nVocê deve realizar os 5 commits descritos abaixo e submeter os 5 links dos commits via Moodle.\n\n# Commit 1: Running the tests\n\nAntes de iniciar as atividades de refatoração, precisamos configurar o repositório de trabalho.\n\n### Crie um fork deste repositório\n\nPrimeiramente, crie um fork deste repositório.\nPara isso, basta clicar no botão `Fork` no canto superior direito.\nCaso tenha dúvidas, verifique a documentação do GitHub sobre como [criar fork de um repositório](https://docs.github.com/pt/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo).\n\n### Ative o GitHub Actions para rodar os testes a cada commit\n\nNeste projeto, utilizamos o GitHub Actions (ferramenta de CI/CD do GitHub) para executar os testes automaticamente a cada commit.\nAbra o arquivo`.github/workflows/tests.yml` e observe que os testes são executados em três sistemas operacionais (Ubuntu, macOS e Windows) e várias versões da linguagem Python. Veja um exemplo em https://github.com/andrehora/library/actions/runs/14231197771.\n\nAtive o GitHub Actions no seu repositório.\nPara isso, basta ir na aba `Actions` e clicar no botão verde.\n\n### Clone o seu repositório\n\nEm seguida, clone o seu repositório para uma pasta local e entre na pasta:\n\n```\n$ git clone https://github.com/\u003cSEU-USUARIO\u003e/library\n$ cd library\n```\n\n### Instale o pytest\n\nNossos testes utilizam o framework de testes [pytest](https://docs.pytest.org).\nInstale o pytest:\n\n```\n$ pip install pytest\n```\n\n### Rode os testes localmente\n\nPara executar os testes localmente, basta rodar o comando `pytest -v tests.py`:\n\n```\n$ pytest -v tests.py\n========================================== test session starts ==========================================\n...                                                                                  \ntests.py::test_rent_regular_book_short_duration PASSED                                            [  9%]\ntests.py::test_rent_regular_book_long_duration PASSED                                             [ 18%]\ntests.py::test_rent_multiple_regular_books PASSED                                                 [ 27%]\ntests.py::test_rent_children_book_short_duration PASSED                                           [ 36%]\ntests.py::test_rent_children_book_long_duration PASSED                                            [ 45%]\ntests.py::test_rent_multiple_children_books PASSED                                                [ 54%]\ntests.py::test_rent_new_release_book_short_duration PASSED                                        [ 63%]\ntests.py::test_rent_new_release_book_long_duration PASSED                                         [ 72%]\ntests.py::test_rent_multiple_new_release_books PASSED                                             [ 81%]\ntests.py::test_rent_distinct_books_short_duration PASSED                                          [ 90%]\ntests.py::test_rent_distinct_books_long_duration PASSED                                           [100%]\n========================================== 11 passed in 0.01s ===========================================\n```\n\n### Rode os testes remotamente (via GitHub Actions)\n\nOs testes são executados automaticamente no GitHub Actions sempre que um commit é realizado.\nPortanto, para rodar os testes no GitHub Actions, realize uma alteração qualquer neste arquivo `README.md` e faça o commit da alteração com a seguinte mensagem: *Commit 1: Running the tests*.\n\nEm seguida, clique na aba `Actions` e veja que os testes foram executados com sucesso no GitHub Actions. \nObserve as execuções em múltiplos sistemas operacionais e versões da linguagem Python.\n\n# Commit 2: Removing getters @property and renaming attributes\n\nObserve que as classes `Book`, `Rental` e `Client` possuem 5 propriedades `@property` que representam métodos getters.\nNão iremos precisar dessas propriedades, portanto, remova todas as 5.\nEm seguida, renomeie os 5 atributos das classes, removendo o underline (_). Por exemplo, mude de `self._book` para `self.book`.\n\n**Rode os testes localmente para garantir que o comportamento do sistema não foi alterado.\nSó faça o commit com os testes passando.\nRefatoração não deve quebrar os teste.**\n\nPara rodar os testes localmente, basta executar o pytest na linha comando:\n\n```\n$ pytest -v tests.py\n========================================== test session starts ==========================================\n...                                                                                  \ntests.py::test_rent_regular_book_short_duration PASSED                                            [  9%]\ntests.py::test_rent_regular_book_long_duration PASSED                                             [ 18%]\ntests.py::test_rent_multiple_regular_books PASSED                                                 [ 27%]\ntests.py::test_rent_children_book_short_duration PASSED                                           [ 36%]\ntests.py::test_rent_children_book_long_duration PASSED                                            [ 45%]\ntests.py::test_rent_multiple_children_books PASSED                                                [ 54%]\ntests.py::test_rent_new_release_book_short_duration PASSED                                        [ 63%]\ntests.py::test_rent_new_release_book_long_duration PASSED                                         [ 72%]\ntests.py::test_rent_multiple_new_release_books PASSED                                             [ 81%]\ntests.py::test_rent_distinct_books_short_duration PASSED                                          [ 90%]\ntests.py::test_rent_distinct_books_long_duration PASSED                                           [100%]\n========================================== 11 passed in 0.01s ===========================================\n```\n\n#### Faça o commit das alterações\nCom os testes passando, faça o commit com a seguinte mensagem: *Commit 2: Removing getters @property and renaming attributes*.\n\n# Commit 3: Extracting method get_charge from Client.statement\n\nExtraia um método chamado `get_charge` de `Client.statement` para a própria classe `Client`.\nO novo método deverá ter a seguinte assinatura:\n\n```python\ndef get_charge(self, rental: Rental) -\u003e float:\n```\n\nO método extraído deve conter o código relativo ao comentário *determine amounts for each line*.\n\n#### Faça o commit das alterações\nCom os testes passando, faça o commit com a seguinte mensagem: *Commit 3: Extracting method get_charge from Client.statement*.\n\n# Commit 4: Moving method get_charge from Client to Rental\n\nMova o método `get_charge` da classe `Client` para a classe `Rental`, já que esse método não usa informações da primeira, mas sim da segunda classe.\n\nNão esqueça de atualizar o método `get_charge` em `Rental` para chamar `self` ao invés de `rental`, por exemplo:\n\n```python\n# Em Client\nif rental.book.price_code == Book.REGULAR:\n# Em Rental\nif self.book.price_code == Book.REGULAR:\n```\n\nTambém não esqueça de atualizar a chamada do método `get_charge` em `statement`:\n```python\n# De...\namount = self.get_charge(rental)\n# Para...\namount = rental.get_charge()\n```\n\n#### Faça o commit das alterações\nCom os testes passando, faça o commit com a seguinte mensagem: *Commit 4: Moving method get_charge from Client to Rental*.\n\n# Commit 5: Extracting get_frequent_renter_points from Client.statement to Rental\n\nVamos decompor mais uma vez `statement` para diminuir seu tamanho e complexidade. \n\nO método extraído deve conter o código relativo ao comentário *add frequent renter points*.\nExtraia o seguinte método chamado `get_frequent_renter_points` e para classe `Rental`:\n\n```python\ndef get_frequent_renter_points(self):\n    points = 1\n    if self.book.price_code == Book.NEW_RELEASE and self.days_rented \u003e 1:\n        points += 1\n    return points\n````\n\nAtualize a chamada do método `get_frequent_renter_points` em `statement`:\n\n```python\nfrequent_renter_points = rental.get_frequent_renter_points()\n```\n\nRode os testes, e observe que vários testes falham.\nOu seja, inserimos uma regressão (BUG!) no sistema.\nTestes funcionam como uma rede de proteção contra a inserção de bugs.\n\nEncontre e corrija o bug!\nDica: o bug está no código acima.\n\n#### Faça o commit das alterações\nCom os testes passando, faça o commit com a seguinte mensagem: *Commit 5: Extracting get_frequent_renter_points from Client.statement to Rental*.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrehora%2Flibrary","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandrehora%2Flibrary","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandrehora%2Flibrary/lists"}