paint-brush
Como criar um programa Python CLI para o Trello Board Management (Parte 1)por@elainechan01
2,031 leituras
2,031 leituras

Como criar um programa Python CLI para o Trello Board Management (Parte 1)

por Elaine Yun Ru Chan19m2023/08/15
Read on Terminal Reader

Muito longo; Para ler

Conforme declarado na Wikipedia, “uma interface de linha de comando (CLI) é um meio de interagir com um dispositivo ou programa de computador com comandos de um usuário ou cliente e respostas do dispositivo ou programa, na forma de linhas de texto”. Em outras palavras, um programa CLI é um programa pelo qual o usuário usa a linha de comando para interagir com o programa, fornecendo instruções para execução. Muitos softwares do dia-a-dia são agrupados como um programa CLI. Pegue o editor de texto vim, por exemplo - uma ferramenta fornecida com qualquer sistema UNIX que pode ser ativada simplesmente executando vim <FILE> no terminal. Com relação ao Google Cloud CLI, vamos mergulhar na anatomia de um programa CLI.
featured image - Como criar um programa Python CLI para o Trello Board Management (Parte 1)
Elaine Yun Ru Chan HackerNoon profile picture
0-item

Isenção de responsabilidade: este tutorial pressupõe que os leitores tenham um conhecimento básico de Python, APIs, Git e testes de unidade.

Eu encontrei vários softwares CLI com as animações mais legais, e isso me fez pensar - eu poderia atualizar meu projeto escolar 'minimalista' pedra-papel-tesoura?


Olá, vamos jogar! Escolha seu lutador (pedra, papel, tesoura): pedra

O que é um programa CLI?

Conforme declarado na Wikipedia, “uma interface de linha de comando (CLI) é um meio de interagir com um dispositivo ou programa de computador com comandos de um usuário ou cliente e respostas do dispositivo ou programa, na forma de linhas de texto”.


Em outras palavras, um programa CLI é um programa pelo qual o usuário usa a linha de comando para interagir com o programa fornecendo instruções para execução.


Muitos softwares do dia-a-dia são agrupados como um programa CLI. Pegue o editor de texto vim , por exemplo - uma ferramenta fornecida com qualquer sistema UNIX que pode ser ativada simplesmente executando vim <FILE> no terminal.


Com relação à CLI do Google Cloud , vamos nos aprofundar na anatomia de um programa CLI.

argumentos

Argumentos (Parâmetros) são itens de informação fornecidos a um programa. Muitas vezes, são chamados de argumentos posicionais porque são identificados por sua posição.


Por exemplo, quando queremos definir a propriedade project na seção principal, executamos gcloud config set project <PROJECT_ID>


Notavelmente, podemos traduzir isso em

Argumento

Contente

Arg 0

gcloud

Arg 1

configuração

Comandos

Os comandos são uma matriz de argumentos que fornecem instruções ao computador.


Com base no exemplo anterior, definimos a propriedade project na seção principal executando gcloud config set project <PROJECT_ID>


Em outras palavras, set é um comando.

Comandos Opcionais

Normalmente, os comandos são necessários, mas podemos fazer exceções. Com base no caso de uso do programa, podemos definir comandos opcionais.


Voltando ao comando gcloud config , conforme declarado em sua documentação oficial, gcloud config é um grupo de comandos que permite modificar propriedades. O uso é assim:

 gcloud config GROUP | COMMAND [GCLOUD_WIDE_FLAG … ]

pelo qual COMMAND pode ser set , list , e assim por diante… (Observe que GROUP é config )

Opções

Opções são tipos documentados de parâmetros que modificam o comportamento de um comando. Eles são pares chave-valor indicados por '-' ou '--'.


Voltando ao uso do grupo de comandos gcloud config , a(s) opção(ões), neste caso, é GCLOUD_WIDE_FLAG .


Por exemplo, digamos que queremos exibir o uso e a descrição detalhados do comando, executamos gcloud config set –help . Em outras palavras, --help é a opção.


Outro exemplo é quando queremos definir a propriedade de zona na seção de computação de um projeto específico, executamos gcloud config set compute <ZONE_NAME> –project=<PROJECT_ID> . Em outras palavras, --project é uma opção que contém o valor <PROJECT_ID> .


Também é importante observar que suas posições geralmente não importam.

Opções Obrigatórias

As opções, como seu nome, geralmente são opcionais, mas também podem ser personalizadas para serem obrigatórias.


Por exemplo, quando queremos criar um cluster dataproc, executamos gcloud dataproc clusters create <CLUSTER_NAME> –region=<REGION> . E conforme declarado em sua documentação de uso:

 gcloud dataproc clusters create (CLUSTER: –region=REGION)

O sinalizador --region é obrigatório se não tiver sido configurado anteriormente.

Opções curtas vs. opções longas

As opções curtas começam com - seguidas por um único caractere alfanumérico, enquanto as opções longas começam com -- seguidas por vários caracteres. Pense nas opções curtas como atalhos quando o usuário tiver certeza do que deseja, enquanto as opções longas são mais legíveis.


Você escolheu a pedra! O computador agora fará sua seleção.

O que alcançaremos com este tutorial?

Então eu menti… Não tentaremos atualizar o programa CLI pedra-papel-tesoura básico.

Em vez disso, vamos dar uma olhada em um cenário do mundo real:

Esboço e Objetivos

Sua equipe usa o Trello para acompanhar os problemas e o progresso do projeto. Sua equipe está procurando uma maneira mais simplificada de interagir com o quadro - algo semelhante a criar um novo repositório GitHub por meio do terminal. A equipe recorreu a você para criar um programa CLI com esse requisito básico de poder adicionar um novo cartão à coluna 'Tarefas' do quadro.


Com base no requisito mencionado, vamos esboçar nosso programa CLI definindo seus requisitos:


Requisitos funcionais

  • O usuário pode adicionar um novo cartão a uma coluna no quadro
    • Entradas necessárias: coluna, nome do cartão
    • Entradas opcionais: descrição do cartão, rótulos do cartão (selecione entre os existentes)

Requisitos não Funcionais

  • Programa para solicitar ao usuário acesso à conta do Trello (autorização)
  • Programa para solicitar que o usuário defina em qual quadro do Trello trabalhar (configuração)

Requisitos Opcionais

  • O usuário pode adicionar uma nova coluna ao quadro
  • O usuário pode adicionar um novo rótulo ao quadro
  • O usuário pode ver uma visão simplificada/detalhada de todas as colunas


Com base no exposto, podemos formalizar os comandos e opções do nosso programa CLI como tal:

Visualização de tabela detalhada da estrutura CLI com base nos requisitos


Ps Não se preocupe com as duas últimas colunas, aprenderemos sobre isso mais tarde…


Quanto à nossa pilha de tecnologia, vamos nos ater a isso:


Testes de unidade

  • pytest
  • pytest-mock
  • ajudantes de teste de cli

Trello

  • py-trello (Python wrapper para o Trello SDK)

CLI

  • digitador
  • rico
  • menu de termo simples

Utilitários (Diversos)

  • python-dotenv

Linha do tempo

Estaremos abordando este projeto em partes e aqui está um trecho do que você pode esperar:


Parte 1

  • Implementação da lógica de negócios py-trello

Parte 2

  • Implementação da lógica de negócios CLI
  • Distribuindo o programa CLI como um pacote

Parte 3

  • Implementação de requisitos funcionais opcionais
  • atualização do pacote


O computador escolheu a tesoura! Vamos ver quem ganha essa batalha...

Vamos começar

Estrutura de pastas

O objetivo é distribuir o programa CLI como um pacote em PyPI . Assim, tal configuração é necessária:

 trellocli/ __init__.py __main__.py models.py cli.py trelloservice.py tests/ test_cli.py test_trelloservice.py README.md pyproject.toml .env .gitignore


Aqui está um mergulho profundo em cada arquivo e/ou diretório:

  • trellocli : atua como o nome do pacote a ser usado pelos usuários, por exemplo, pip install trellocli
    • __init__.py : representa a raiz do pacote, conforma a pasta como um pacote Python
    • __main__.py : define o ponto de entrada e permite que os usuários executem módulos sem especificar o caminho do arquivo usando o sinalizador -m , por exemplo, python -m <module_name> para substituir python -m <parent_folder>/<module_name>.py
    • models.py : armazena classes usadas globalmente, por exemplo, modelos com os quais se espera que as respostas da API estejam em conformidade
    • cli.py : armazena a lógica de negócios para comandos e opções da CLI
    • trelloservice.py : armazena a lógica de negócios para interagir com py-trello
  • tests : armazena testes de unidade para o programa
    • test_cli.py : armazena testes de unidade para a implementação da CLI
    • test_trelloservice.py : armazena testes de unidade para a interação com py-trello
  • README.md : armazena a documentação do programa
  • pyproject.toml : armazena as configurações e requisitos do pacote
  • .env : armazena variáveis de ambiente
  • .gitignore : especifica os arquivos a serem ignorados (não rastreados) durante o controle de versão


Para obter uma explicação mais detalhada sobre a publicação de pacotes Python, confira este ótimo artigo: How to Publish an Open-Source Python Package to PyPI by Geir Arne Hjelle

Configurar

Antes de começarmos, vamos nos basear na configuração do pacote.


Começando com o arquivo __init__.py em nosso pacote, que seria onde as constantes e variáveis do pacote são armazenadas, como nome e versão do aplicativo. No nosso caso, queremos inicializar o seguinte:

  • nome do aplicativo
  • versão
  • Constantes de SUCESSO e ERRO
 # trellocli/__init__.py __app_name__ = "trellocli" __version__ = "0.1.0" ( SUCCESS, TRELLO_WRITE_ERROR, TRELLO_READ_ERROR ) = range(3) ERRORS = { TRELLO_WRITE_ERROR: "Error when writing to Trello", TRELLO_READ_ERROR: "Error when reading from Trello" }


Passando para o arquivo __main__.py , o fluxo principal do seu programa deve ser armazenado aqui. No nosso caso, armazenaremos o ponto de entrada do programa CLI, assumindo que haverá uma função chamável em cli.py .

 # trellocli/__main__.py from trellocli import cli def main(): # we'll modify this later - after the implementation of `cli.py` pass if __name__ == "__main__": main()


Agora que o pacote foi configurado, vamos dar uma olhada na atualização do nosso arquivo README.md (documentação principal). Não existe uma estrutura específica que devemos seguir, mas um bom README consistiria no seguinte:

  • Visão geral
  • Instalação e Requisitos
  • Introdução e uso

Outro ótimo post para ler se você quiser se aprofundar: Como escrever um bom README por merlos


Aqui está como eu gostaria de estruturar o README para este projeto

 <!--- README.md --> # Overview # Getting Started # Usage # Architecture ## Data Flow ## Tech Stack # Running Tests # Next Steps # References


Vamos deixar o esqueleto como está por enquanto - voltaremos a isso mais tarde.


Seguindo em frente, vamos configurar os metadados do nosso pacote com base na documentação oficial

 # pyproject.toml [project] name = "trellocli_<YOUR_USERNAME>" version = "0.1.0" authors = [ { name = "<YOUR_NAME>", email = "<YOUR_EMAIL>" } ] description = "Program to modify your Trello boards from your computer's command line" readme = "README.md" requires-python = ">=3.7" classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] dependencies = [] [project.urls] "Homepage" = ""


Observe como existem espaços reservados que você deve modificar, por exemplo, seu nome de usuário, seu nome…


Em outra observação, deixaremos o URL da página inicial vazio por enquanto. Faremos alterações depois de publicá-lo no GitHub. Também deixaremos a parte de dependências vazia por enquanto e adicionaremos à medida que avançamos.


O próximo na lista seria nosso arquivo .env , onde armazenamos nossas variáveis de ambiente, como chaves e segredos de API. É importante observar que esse arquivo não deve ser rastreado pelo Git, pois contém informações confidenciais.


No nosso caso, armazenaremos nossas credenciais do Trello aqui. Para criar um Power-Up no Trello, siga este guia . Mais especificamente, com base no uso do py-trello , como pretendemos usar OAuth para nosso aplicativo, precisaremos do seguinte para interagir com o Trello:

  • Chave API (para nosso aplicativo)
  • Segredo da API (para nosso aplicativo)
  • Token (token do usuário para conceder acesso aos seus dados)


Depois de recuperar sua chave e segredo de API, armazene-os no arquivo .env como tal

 # .env TRELLO_API_KEY=<your_api_key> TRELLO_API_SECRET=<your_api_secret>


Por último, mas não menos importante, vamos usar o template Python .gitignore que pode ser encontrado aqui . Observe que isso é crucial para garantir que nosso arquivo .env nunca seja rastreado - se em algum momento, nosso arquivo .env foi rastreado, mesmo se removermos o arquivo em etapas posteriores, o dano está feito e agentes mal-intencionados podem rastrear o anterior patches para informações confidenciais.


Agora que a configuração está concluída, vamos enviar nossas alterações para o GitHub. Dependendo dos metadados especificados em pyproject.toml , lembre-se de atualizar sua LICENÇA e o URL da página inicial de acordo. Para referência sobre como escrever commits melhores: Write Better Commits, Build Better Projects por Victoria Dye


Outros passos notáveis:

Testes de unidade

Antes de começarmos a escrever nossos testes, é importante observar que, como estamos trabalhando com uma API, implementaremos testes simulados para poder testar nosso programa sem o risco de indisponibilidade da API. Aqui está outro ótimo artigo sobre testes simulados do Real Python: Mocking External APIs in Python


Com base nos requisitos funcionais, nossa principal preocupação é permitir que os usuários adicionem um novo cartão. Referenciando o método em py-trello : add_card . Para poder fazer isso, devemos chamar o método add_card da classe List , que pode ser recuperado da função get_list da classe Board , que pode ser recuperado…


Você entendeu a essência - precisaremos de muitos métodos auxiliares para chegar ao nosso destino final, vamos colocar em palavras:

  • Teste para recuperar o token do cliente
  • Teste para recuperar placas
  • Teste para recuperar uma placa
  • Teste para recuperar listas do quadro
  • Teste para recuperar uma lista
  • Teste para recuperar rótulos da placa
  • Teste para recuperar um rótulo
  • Teste para adicionar cartão
  • Teste para adicionar rótulo ao cartão


Também é importante observar que, ao escrever testes de unidade, queremos que nossos testes sejam o mais extensos possível - ele lida bem com os erros? Abrange todos os aspectos do nosso programa?


No entanto, apenas para fins deste tutorial, simplificaremos as coisas verificando apenas os casos de sucesso.


Antes de mergulhar no código, vamos modificar nosso arquivo pyproject.toml para incluir as dependências necessárias para escrever/executar testes de unidade.

 # pyproject.toml [project] dependencies = [ "pytest==7.4.0", "pytest-mock==3.11.1" ]


Em seguida, vamos ativar nosso virtualenv e executar pip install . para instalar as dependências.


Feito isso, vamos finalmente escrever alguns testes. Em geral, nossos testes devem incluir uma resposta simulada a ser retornada, um patch para a função que estamos tentando testar corrigindo o valor de retorno com a resposta simulada e, finalmente, uma chamada para a função. Um teste de amostra para recuperar os tokens de acesso do usuário seria o seguinte:

 # tests/test_trelloservice.py # module imports from trellocli import SUCCESS from trellocli.trelloservice import TrelloService from trellocli.models import * # dependencies imports # misc imports def test_get_access_token(mocker): """Test to check success retrieval of user's access token""" mock_res = GetOAuthTokenResponse( token="test", token_secret="test", status_code=SUCCESS ) mocker.patch( "trellocli.trelloservice.TrelloService.get_user_oauth_token", return_value=mock_res ) trellojob = TrelloService() res = trellojob.get_user_oauth_token() assert res.status_code == SUCCESS


Observe em meu código de amostra que GetOAuthTokenResponse é um modelo que ainda não foi definido em models.py . Ele fornece estrutura para escrever um código mais limpo, veremos isso em ação mais tarde.


Para executar nossos testes, basta executar python -m pytest . Observe como nossos testes falharão, mas tudo bem - funcionará no final.


Cantinho do Desafio 💡 Você pode tentar escrever mais testes sozinho? Sinta-se à vontade para consultareste patch para ver como são meus testes


Por enquanto, vamos construir nosso trelloservice . Começando com a adição de uma nova dependência, que é o wrapper py-trello .

 # pyproject.toml dependencies = [ "pytest==7.4.0", "pytest-mock==3.11.1", "py-trello==0.19.0" ]


Mais uma vez, execute pip install . para instalar as dependências.

modelos

Agora, vamos começar construindo nossos modelos - para regular as respostas que esperamos no trelloservice . Para esta parte, é melhor consultar nossos testes de unidade e o código-fonte py-trello para entender o tipo de valor de retorno que podemos esperar.


Por exemplo, digamos que queremos recuperar o token de acesso do usuário, referindo-se à função create_oauth_token do py-trello ( código-fonte ), sabemos que devemos esperar que o valor de retorno seja algo como isto

 # trellocli/models.py # module imports # dependencies imports # misc imports from typing import NamedTuple class GetOAuthTokenResponse(NamedTuple): token: str token_secret: str status_code: int


Por outro lado, esteja ciente das convenções de nomenclatura conflitantes. Por exemplo, o módulo py-trello tem uma classe chamada List . Uma solução para isso seria fornecer um alias durante a importação.

 # trellocli/models.py # dependencies imports from trello import List as Trellolist


Sinta-se à vontade para também usar esta oportunidade para adaptar os modelos às necessidades do seu programa. Por exemplo, digamos que você precise apenas de um atributo do valor de retorno, você pode refatorar seu modelo para esperar extrair o referido valor do valor de retorno em vez de armazená-lo como um todo.

 # trellocli/models.py class GetBoardName(NamedTuple): """Model to store board id Attributes id (str): Extracted board id from Board value type """ id: str


Cantinho do Desafio 💡 Você pode tentar escrever mais modelos sozinho? Sinta-se à vontade para consultareste patch para ver como meus modelos se parecem

Logíca de negócios

Configurar

Modelos desativados, vamos começar oficialmente a codificar o trelloservice . Novamente, devemos nos referir aos testes de unidade que criamos - diga que a lista atual de testes não fornece cobertura total para o serviço, sempre retorne e adicione mais testes quando necessário.


Como de costume, inclua todas as instruções de importação na parte superior. Em seguida, crie a classe TrelloService e os métodos de espaço reservado conforme o esperado. A ideia é inicializar uma instância compartilhada do serviço em cli.py e chamar seus métodos de acordo. Além disso, visamos a escalabilidade, portanto, a necessidade de uma ampla cobertura.

 # trellocli/trelloservice.py # module imports from trellocli import TRELLO_READ_ERROR, TRELLO_WRITE_ERROR, SUCCESS from trellocli.models import * # dependencies imports from trello import TrelloClient # misc imports class TrelloService: """Class to implement the business logic needed to interact with Trello""" def __init__(self) -> None: pass def get_user_oauth_token() -> GetOAuthTokenResponse: pass def get_all_boards() -> GetAllBoardsResponse: pass def get_board() -> GetBoardResponse: pass def get_all_lists() -> GetAllListsResponse: pass def get_list() -> GetListResponse: pass def get_all_labels() -> GetAllLabelsResponse: pass def get_label() -> GetLabelResponse: pass def add_card() -> AddCardResponse: pass


Observe como, desta vez, quando executarmos nossos testes, nossos testes serão aprovados. Na verdade, isso nos ajudará a garantir que estamos no caminho certo. O fluxo de trabalho deve ser estender nossas funções, executar nossos testes, verificar aprovação/reprovação e refatorar de acordo.

Autorização e inicialização do TrelloClient

Vamos começar com a função __init__ . A ideia é chamar a função get_user_oauth_token aqui e inicializar o TrelloClient . Novamente, enfatizando a necessidade de armazenar essas informações confidenciais apenas no arquivo .env , usaremos a dependência python-dotenv para recuperar informações confidenciais. Depois de modificar nosso arquivo pyproject.toml adequadamente, vamos começar a implementar as etapas de autorização.

 # trellocli/trelloservice.py class TrelloService: """Class to implement the business logic needed to interact with Trello""" def __init__(self) -> None: self.__load_oauth_token_env_var() self.__client = TrelloClient( api_key=os.getenv("TRELLO_API_KEY"), api_secret=os.getenv("TRELLO_API_SECRET"), token=os.getenv("TRELLO_OAUTH_TOKEN") ) def __load_oauth_token_env_var(self) -> None: """Private method to store user's oauth token as an environment variable""" load_dotenv() if not os.getenv("TRELLO_OAUTH_TOKEN"): res = self.get_user_oauth_token() if res.status_code == SUCCESS: dotenv_path = find_dotenv() set_key( dotenv_path=dotenv_path, key_to_set="TRELLO_OAUTH_TOKEN", value_to_set=res.token ) else: print("User denied access.") self.__load_oauth_token_env_var() def get_user_oauth_token(self) -> GetOAuthTokenResponse: """Helper method to retrieve user's oauth token Returns GetOAuthTokenResponse: user's oauth token """ try: res = create_oauth_token() return GetOAuthTokenResponse( token=res["oauth_token"], token_secret=res["oauth_token_secret"], status_code=SUCCESS ) except: return GetOAuthTokenResponse( token="", token_secret="", status_code=TRELLO_AUTHORIZATION_ERROR )


Nesta implementação, criamos um método auxiliar para lidar com quaisquer erros previsíveis, por exemplo, quando o usuário clica em Deny durante a autorização. Além disso, ele está configurado para solicitar recursivamente a autorização do usuário até que uma resposta válida seja retornada, porque o fato é que não podemos continuar a menos que o usuário autorize nosso aplicativo a acessar os dados de sua conta.


Canto do desafio 💡 Observe TRELLO_AUTHORIZATION_ERROR ? Você pode declarar este erro como uma constante de pacote? Consulte Configuração para obter mais informações

Funções Auxiliares

Agora que a parte de autorização está concluída, vamos passar para as funções auxiliares, começando com a recuperação dos quadros Trello do usuário.

 # trellocli/trelloservice.py def get_all_boards(self) -> GetAllBoardsResponse: """Method to list all boards from user's account Returns GetAllBoardsResponse: array of user's trello boards """ try: res = self.__client.list_boards() return GetAllBoardsResponse( res=res, status_code=SUCCESS ) except: return GetAllBoardsResponse( res=[], status_code=TRELLO_READ_ERROR ) def get_board(self, board_id: str) -> GetBoardResponse: """Method to retrieve board Required Args board_id (str): board id Returns GetBoardResponse: trello board """ try: res = self.__client.get_board(board_id=board_id) return GetBoardResponse( res=res, status_code=SUCCESS ) except: return GetBoardResponse( res=None, status_code=TRELLO_READ_ERROR )


Quanto à recuperação das listas (colunas), teremos que verificar a classe Board do py-trello , ou seja, devemos aceitar um novo parâmetro do tipo de valor Board .

 # trellocli/trelloservice.py def get_all_lists(self, board: Board) -> GetAllListsResponse: """Method to list all lists (columns) from the trello board Required Args board (Board): trello board Returns GetAllListsResponse: array of trello lists """ try: res = board.all_lists() return GetAllListsResponse( res=res, status_code=SUCCESS ) except: return GetAllListsResponse( res=[], status_code=TRELLO_READ_ERROR ) def get_list(self, board: Board, list_id: str) -> GetListResponse: """Method to retrieve list (column) from the trello board Required Args board (Board): trello board list_id (str): list id Returns GetListResponse: trello list """ try: res = board.get_list(list_id=list_id) return GetListResponse( res=res, status_code=SUCCESS ) except: return GetListResponse( res=None, status_code=TRELLO_READ_ERROR )


Canto do desafio 💡 Você poderia implementar as funções get_all_labels e get_label por conta própria? Revise a classe Board do py-trello . Sinta-se à vontade para consultareste patch para ver como é minha implementação

Função para adicionar um novo cartão

Por último, mas não menos importante, finalmente alcançamos o que almejamos o tempo todo - adicionar um novo cartão. Lembre-se de que não usaremos todas as funções declaradas anteriormente aqui - o objetivo das funções auxiliares é aumentar a escalabilidade.

 # trellocli/trelloservice.py def add_card( self, col: Trellolist, name: str, desc: str = "", labels: List[Label] = [] ) -> AddCardResponse: """Method to add a new card to a list (column) on the trello board Required Args col (Trellolist): trello list name (str): card name Optional Args desc (str): card description labels (List[Label]): list of labels to be added to the card Returns AddCardResponse: newly-added card """ try: # create new card new_card = col.add_card(name=name) # add optional description if desc: new_card.set_description(description=desc) # add optional labels if labels: for label in labels: new_card.add_label(label=label) return AddCardResponse( res=new_card, status_code=SUCCESS ) except: return AddCardResponse( res=new_card, status_code=TRELLO_WRITE_ERROR )


🎉 Agora que está pronto, lembre-se de atualizar seu README de acordo e enviar seu código para o GitHub.


Parabéns! Você ganhou. Jogar de novo (s/N)?

Embrulhar

Obrigado pela paciência :) Por meio deste tutorial, você aprendeu com sucesso a implementar a simulação ao escrever testes de unidade, estruturar modelos para coesão, ler o código-fonte para encontrar as principais funcionalidades e implementar a lógica de negócios usando um wrapper de terceiros.


Fique de olho na Parte 2, onde faremos um mergulho profundo na implementação do próprio programa CLI.


Enquanto isso, vamos manter contato 👀