Skip to content

andrehora/library

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tests

Library refactoring example

Neste exercício, iremos refatorar um sistema simples para aluguel de livros de uma biblioteca. Este exercício é adaptado do livro Refactoring de Martin Fowler e Kent Beck. Você deve realizar os 5 commits descritos abaixo e submeter os 5 links dos commits via Moodle.

Overview

Primeiramente, explore o código do sistema em model.py. Note que temos três classes: Book (livros que podem ser alugados), Rental (dados de um aluguel) e Client (clientes da biblioteca). A classe Client possui um método statement, responsável por gerar o recibo do aluguel para o cliente:

    def statement(self) -> str:

        total_amount = 0
        frequent_renter_points = 0
        result = f"Rental summary for {self.name}\n"
        
        for rental in self._rentals:
            amount = 0
            
            # determine amounts for each line
            if rental.book.price_code == Book.REGULAR:
                amount += 2
                if rental.days_rented > 2:
                    amount += (rental.days_rented - 2) * 1.5
            elif rental.book.price_code == Book.NEW_RELEASE:
                amount += rental.days_rented * 3
            elif rental.book.price_code == Book.CHILDREN:
                amount += 1.5
                if rental.days_rented > 3:
                    amount += (rental.days_rented - 3) * 1.5

            # add frequent renter points
            frequent_renter_points += 1
            if rental.book.price_code == Book.NEW_RELEASE and rental.days_rented > 1:
                frequent_renter_points += 1

            # show each rental result
            result += f"- {rental.book.title}: {amount}\n"
            total_amount += amount
        
        # show total result
        result += f"Total: {total_amount}\n"
        result += f"Points: {frequent_renter_points}"
        return result

Reflita sobre os possíveis problemas do método statement:

  • Esse método possui muitas responsabilidades?
  • Como adicionar um novo tipo filme?
  • Como adicionar um novo tipo de recibo, por exemplo, HTML, CSV, JSON, etc?

Explore também os testes em tests.py para entender melhor como o sistema funciona. Por exemplo, o teste test_rent_regular_book_short_duration:

def test_rent_regular_book_short_duration():
    book = Book("Refactoring", Book.REGULAR)
    r = Rental(book, 2)

    c = Client("Fulano")
    c.add_rental(r)
    
    expected_report = (
        "Rental summary for Fulano\n"
        "- Refactoring: 2\n"
        "Total: 2\n"
        "Points: 1"
    )
    
    assert c.statement() == expected_report

Você deve realizar os 5 commits descritos abaixo e submeter os 5 links dos commits via Moodle.

Commit 1: Running the tests

Antes de iniciar as atividades de refatoração, precisamos configurar o repositório de trabalho.

Crie um fork deste repositório

Primeiramente, crie um fork deste repositório. Para isso, basta clicar no botão Fork no canto superior direito. Caso tenha dúvidas, verifique a documentação do GitHub sobre como criar fork de um repositório.

Ative o GitHub Actions para rodar os testes a cada commit

Neste projeto, utilizamos o GitHub Actions (ferramenta de CI/CD do GitHub) para executar os testes automaticamente a cada commit. Abra 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.

Ative o GitHub Actions no seu repositório. Para isso, basta ir na aba Actions e clicar no botão verde.

Clone o seu repositório

Em seguida, clone o seu repositório para uma pasta local e entre na pasta:

$ git clone https://github.com/<SEU-USUARIO>/library
$ cd library

Instale o pytest

Nossos testes utilizam o framework de testes pytest. Instale o pytest:

$ pip install pytest

Rode os testes localmente

Para executar os testes localmente, basta rodar o comando pytest -v tests.py:

$ pytest -v tests.py
========================================== test session starts ==========================================
...                                                                                  
tests.py::test_rent_regular_book_short_duration PASSED                                            [  9%]
tests.py::test_rent_regular_book_long_duration PASSED                                             [ 18%]
tests.py::test_rent_multiple_regular_books PASSED                                                 [ 27%]
tests.py::test_rent_children_book_short_duration PASSED                                           [ 36%]
tests.py::test_rent_children_book_long_duration PASSED                                            [ 45%]
tests.py::test_rent_multiple_children_books PASSED                                                [ 54%]
tests.py::test_rent_new_release_book_short_duration PASSED                                        [ 63%]
tests.py::test_rent_new_release_book_long_duration PASSED                                         [ 72%]
tests.py::test_rent_multiple_new_release_books PASSED                                             [ 81%]
tests.py::test_rent_distinct_books_short_duration PASSED                                          [ 90%]
tests.py::test_rent_distinct_books_long_duration PASSED                                           [100%]
========================================== 11 passed in 0.01s ===========================================

Rode os testes remotamente (via GitHub Actions)

Os testes são executados automaticamente no GitHub Actions sempre que um commit é realizado. Portanto, 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.

Em seguida, clique na aba Actions e veja que os testes foram executados com sucesso no GitHub Actions. Observe as execuções em múltiplos sistemas operacionais e versões da linguagem Python.

Commit 2: Removing getters @property and renaming attributes

Observe que as classes Book, Rental e Client possuem 5 propriedades @property que representam métodos getters. Não iremos precisar dessas propriedades, portanto, remova todas as 5. Em seguida, renomeie os 5 atributos das classes, removendo o underline (_). Por exemplo, mude de self._book para self.book.

Rode os testes localmente para garantir que o comportamento do sistema não foi alterado. Só faça o commit com os testes passando. Refatoração não deve quebrar os teste.

Para rodar os testes localmente, basta executar o pytest na linha comando:

$ pytest -v tests.py
========================================== test session starts ==========================================
...                                                                                  
tests.py::test_rent_regular_book_short_duration PASSED                                            [  9%]
tests.py::test_rent_regular_book_long_duration PASSED                                             [ 18%]
tests.py::test_rent_multiple_regular_books PASSED                                                 [ 27%]
tests.py::test_rent_children_book_short_duration PASSED                                           [ 36%]
tests.py::test_rent_children_book_long_duration PASSED                                            [ 45%]
tests.py::test_rent_multiple_children_books PASSED                                                [ 54%]
tests.py::test_rent_new_release_book_short_duration PASSED                                        [ 63%]
tests.py::test_rent_new_release_book_long_duration PASSED                                         [ 72%]
tests.py::test_rent_multiple_new_release_books PASSED                                             [ 81%]
tests.py::test_rent_distinct_books_short_duration PASSED                                          [ 90%]
tests.py::test_rent_distinct_books_long_duration PASSED                                           [100%]
========================================== 11 passed in 0.01s ===========================================

Faça o commit das alterações

Com os testes passando, faça o commit com a seguinte mensagem: Commit 2: Removing getters @property and renaming attributes.

Commit 3: Extracting method get_charge from Client.statement

Extraia um método chamado get_charge de Client.statement para a própria classe Client. O novo método deverá ter a seguinte assinatura:

def get_charge(self, rental: Rental) -> float:

O método extraído deve conter o código relativo ao comentário determine amounts for each line.

Faça o commit das alterações

Com os testes passando, faça o commit com a seguinte mensagem: Commit 3: Extracting method get_charge from Client.statement.

Commit 4: Moving method get_charge from Client to Rental

Mova 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ão esqueça de atualizar o método get_charge em Rental para chamar self ao invés de rental, por exemplo:

# Em Client
if rental.book.price_code == Book.REGULAR:
# Em Rental
if self.book.price_code == Book.REGULAR:

Também não esqueça de atualizar a chamada do método get_charge em statement:

# De...
amount = self.get_charge(rental)
# Para...
amount = rental.get_charge()

Faça o commit das alterações

Com os testes passando, faça o commit com a seguinte mensagem: Commit 4: Moving method get_charge from Client to Rental.

Commit 5: Extracting get_frequent_renter_points from Client.statement to Rental

Vamos decompor mais uma vez statement para diminuir seu tamanho e complexidade.

O método extraído deve conter o código relativo ao comentário add frequent renter points. Extraia o seguinte método chamado get_frequent_renter_points e para classe Rental:

def get_frequent_renter_points(self):
    points = 1
    if self.book.price_code == Book.NEW_RELEASE and self.days_rented > 1:
        points += 1
    return points

Atualize a chamada do método get_frequent_renter_points em statement:

frequent_renter_points = rental.get_frequent_renter_points()

Rode os testes, e observe que vários testes falham. Ou seja, inserimos uma regressão (BUG!) no sistema. Testes funcionam como uma rede de proteção contra a inserção de bugs.

Encontre e corrija o bug! Dica: o bug está no código acima.

Faça o commit das alterações

Com os testes passando, faça o commit com a seguinte mensagem: Commit 5: Extracting get_frequent_renter_points from Client.statement to Rental.

About

Library refactoring example

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages