Mas obviamente faltou muito pra dizer além de algumas otimizações e recursos que poderiam ser adicionados ao projeto, como roteamento ou context. Mas como o foco era o TDD é neste tema que eu vou focar, a começar por dizer o que testar::
Teste o uso e não a stack
Significa que os testes primordiais são os testes baseados no uso de sua aplicação. Teste cliques em botões, preenchimentos de formulários, itens que deveriam aparecer em uma lista e etc.
Estes testes são melhores, pois testam justamente os locais onde o usuário vai mexer, dessa forma os erros serão mais aparentes e impactantes. Qualquer erro fora desse grupo será a exceção da exceção e apesar de também ser ruim e preciso cuidar serão mais raros.
Outro fator que também nos dá segurança é que, a maioria dos frameworks e pacotes usados no frontend já são testados por seus criadores, trazendo maior segurança em seu uso e a possibilidade de reportar qualquer anomalia para uma equipe com maior conhecimento sobre o produto.
Use o AAA
Só pra relembrar: Como os testes geralmente serão pautados no uso comum do app/site estas três etapas já garantem a possibilidade de se efetuar vários testes
Arrange: A preparação do ambiente geral do teste e do teste em sí.
Antes dos testes existem alguns itens em comum que são definidos por termos como beforeEach e . Mocks e Stubs (Que basicamente são objetos que simulam uma resposta de API) também entram nessa dança. “Arranjar” também é útil para reproduzir um possível cenário de exceção (erro) e é um conceito, ou seja vai te servir para qualquer outra linguaem de programação que você for testar na vida !
Act: Simula justamente a ação do usuário. Algo que eu quero frisar neste item é que justamente o Javascript surgiu para definir eventos em uma página HTML então essa parte faz total sentido em testes de frontend. Por ser um conceito ele ultrapassa qualquer barreira de linguagem, se é frontend ou backend e até mesmo se é uma ação do usuário, portanto, não se limite a regras este ponto, teste o máximo que for preciso com relação as ações possíveis no se produto.
Assert: O resultado esperado. Mesmo que você não saiba o que esperar, coloque um valor e veja o resultado. Todas as suites de testes fornecem centenas de possibilidades e nada impede mais de uma “Asserção”, o que pode te trazer a tão esperada segurança em seus testes.
Conclusão: Por serem conceitos, essas três etapas vão servir para qualquer linguagem, e digo isso porquê muitas pessoas mudam de linguagem e pensam que terão que reaprender tudo de novo, e isso não é verdade. Esses são conceitos que irão permanecer por muito tempo e por isso devem ser tratados com… Carinho.
Abuse do Stack Overflow
Ninguém sabe de tudo, e mesmo que saiba, não vai se lembrar de tudo. O “wolf revok cats” e demais foruns servem como uma base de dados incrível de soluções que podem ser adaptadas(ou copiadas) em seu projeto. Com o passar do tempo você vai até mesmo propor respostas para problemas do dia a dia, e é muito gostoso receber um upvote na sua solução.
Não tema criticas
Ninguém é perfeito !
E a alegria do ruim é achar o pior !
Não importa o que você faça, alguém vai criticar ou sugerir outra solução, nem sempre melhor. Cabe a você interpretar essas críticas sem se abater e compreender quando algo será bom pra você ou não.
Não é fácil entender isso, mas é uma etapa necessária para todas as profissões.
Não tem pra onde correr, mas também não é o inferno.
Meu conselho é; ignore ofensas, considere as críticas e ACEITE os elogios sinceros.
Ignorar elogios sinceros é tão errado quanto aceitar criticas injustas.
Esse arremate terá duas dicas rápidas e pequenas mudanças na pasta public: uma referente aos ícones e descrição e a outra uma breve introdução ao SEO.
Ícones e descrição
No React temos dois ícones iguais em tamanhos diferentes chamados logo (do tipo png) e servem para apresentar o seu app em dispositivos específicos (como produtos Apple). E um ícone do tipo ico, então caso você queira alterar o ícone você precisa respeitar essas características nos três ícones, embora você possa alterar o nome e o destino deles dentro da pasta public.
Para facilitar o serviço eu uso um site (https://icoconvert.com/) para criar os ícones e simplesmente substituo eles dentro da pasta. Você pode usar um editor de imagem diferente caso queira.
O resultado será commitado antes do nosso deploy.
Após essa mudança você também pode adicionar o nome do seu projeto na tag title e também alterar a sua descrição na tag meta que tem o name igual a description.
SEO e metatags
Ah o SEO… A menina dos olhos da maioria dos freelancers…
Seo significa Search Engine Optimization e em resumo são metatags(tags que se referem ao próprio site) que fornecem diversas informações necessárias para que os sites de buscas classifiquem o seu conteúdo.
Existem também metatags do tipo Open Graph que servem para exibir seu site em um formato aceitável para um site específico, como o Facebook ou o Twitter. Não vamos utilizar esse recurso aqui, mas se preciso for, eis uma boa fonte de estudo em inglês: https://css-tricks.com/essential-meta-tags-social-media/
git add .
git commit -m "Structure" -m"Change logo ad metatags for seo description."
git push -u origin main
Deploy: Vercel
Então… Ultimamente (10/2021) eu estudei muito soluções de deploy baseadas no Netlify. Muito tempo atrás eu aprendi a fazer deploys React no Github pages. Mas pra ser sincero, se você for aprender React nos dias de hoje, a melhor opção se chama Nextjs. Em resumo ele é o React que funciona no backend, diminuindo muito o excesso de trabalho para dispor uma aplicação para o cliente. A empresa que mantém o Nextjs se chama Vercel (antiga Zeit) e ela fornece um domínio gratuito para hospedagem.
Basicamente você baixa um pacote e edita a entrada para o Github no arquivo package.json.
Já no Vercel você cria as credenciais do seu cliente Git e ele pega o código no repositório e dispõe. Igual no Netlify, mas ele será útil quando formos criar um projeto usando Next…
… Someday ….
Bom Você pode se conectar a Vercel Via Github, Gitlab ou Bitbucket… Ou por um email….
De qualquer forma vamos precisar de um cliente que no meu caso é o Github. Selecione ele se for o seu caso.
Você vai clicar o botão New Project e depois em Adjust app permssions, na popup que se abrir você vai o item chamado repository access a vai pequisar e adicionar o git do nosso projeto e clicar em save.
Depois desta eapa, você vai clicar na opção import contida no repositório que vamos usar. Clique em Skip na opção create a team e por fim no botão DEPLOY e…
Confetti !!!!!
O projeto foi criado !
Agora é só caprichar no README, que faltou fazermos e pronto.
Dando seguimento ao Projeto vamos as parte mais complexas porém ainda simples do nosso projetinho.
Mas antes de continuarmos, adianto que o CSS será adicionado por último, mas hey ! Não precisamos ainda porque não estamos usando a interface de exibição do código, mas sim a suite de testes. Será que é possível permanecer assim quando for necessário clicar em algo ?
Veremos …
Obs.: O teste já está “mastigado” para poupar espaço, porém ele pode ser utilizado para futuras otimizações do código. Ex: desacoplar o excesso de código no componente Main.
Falando nisso…
Crie o componente no caminho:
src/components/Main/index.test.js:
/* Render escreve, screen lê e waitFor constroi o await da promise */
import {render, waitFor, screen} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
/* Componente a ser testado*/
import Main from ".";
/* Cria o mock de um servidor. Simulado. */
import {setupServer} from "msw/node";
/* Faz o mock dos processos rest (get, post, put, patch e update) */
import {rest} from "msw";
/* URL real a ser consumida */
const BASE_URL = "https://nova-joke-api.netlify.app/.netlify/functions/index/api/random"
/* Cria o mock do servidor. O objeto json não precisa ser igual ao verdadeiro, mas se possível
deixe ao menos os itens que você irá usar conforme a construção original*/
const server = setupServer(
rest.get(
BASE_URL,
(req, res, ctx) => {
return res(
ctx.json({
id: 1,
type: "general",
setup: "Joke",
punchline: "LOL",
})
);
}
)
);
/* Processos de limpeza necessários antes de cada teste de api ser iniciado. */
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
/* Efetua o teste de api com tudo ok. Você pode testar o conteúdo da resposta,
o código da resposta, ou até mesmo mensagens de erro */
test("loads and displays the joke", async () => {
render(<Main />);
const response = await waitFor(() => screen.getByText(/general/i));
expect(response).toBeInTheDocument();
});
/* Testa o comportamento do botão que chama a punchline */
test("Activates the joke punchline card", async () => {
render(<Main />);
window.HTMLElement.prototype.scrollIntoView = function () {};
window.HTMLElement.prototype.scrollTo = function () {};
const punchlineButton = screen.getByText("Answer");
userEvent.click(punchlineButton);
const punchline = await waitFor(() => screen.getByText(/lol/i));
expect(punchline).toBeInTheDocument();
});
/* Dispara uma nova piada */
test("Get one new joke", async () => {
render(<Main />);
/* Testa piada antiga para confirmar se a piada foi mesmo alterada */
const oldJokeId = await waitFor(() => screen.getByText(/1/i));
expect(oldJokeId).toBeInTheDocument();
server.use(
rest.get(
BASE_URL,
(req, res, ctx) => {
return res(
ctx.json({
id: 2,
type: "general",
setup: "Another joke",
punchline: "HUE HUE !",
})
);
}
)
);
/* Como o comportamento mudou. Temos que chamar um botão antes do outro */
const punchlineButton = screen.getByText("Answer");
userEvent.click(punchlineButton);
/* Simula um novo request, para confirmar se a chamada foi realmente efetuada */
const newJokeRequest = await waitFor(() => screen.getByText("New Joke"));
userEvent.click(newJokeRequest);
const newJokeId = await waitFor(() => screen.getByText(/2/i));
expect(newJokeId).toBeInTheDocument();
});
/* Observa erro */
test("Receive an network error", async () => {
server.use(
rest.post(
BASE_URL,
(req, res, ctx) => {
return res(
ctx.status(500),
ctx.json({message: "Internal server error"})
);
}
)
);
render(<Main />);
/* Utiliza uma função do Jest, mas funciona... */
console.log = jest.fn();
console.log("Internal server error");
expect(console.log).toHaveBeenCalledWith("Internal server error");
});
Nossa é coisa pra caramba ! Mas tudo bem. Vai te servir de referência para futuros projetos, pode apostar nisso. Deixei o necessário explicado nos comentários, mas caso precise de mais explicações, deixe um comentário o post.
Também estamos utilizando um evento de ação, o click, que consegue justamente simular um clique do usuário, isso evita o teste manual de ir clicar no aplicativo em execução no navegador.
O primeiro indica que temos que baixar uma dependência, o Mock Service Worker, que vai simular uma chamada para o endereço da api. Então vamos lá. Feche a suite de testes e execute o comando:
npm i -D msw
Agora se você rodar o teste novamente, vão aparecer muitos erros, pois o nosso componente ainda não existe. vamos criá-lo então:
src/components/Main/index.js:
import {useEffect, useRef, useState} from "react";
import Setup from "../Setup";
import Punchline from "../Punchline";
import Loading from "../Loading";
/* Note como este componente é o único que possui um estado e também o único que possui hooks.
Esta construção poderia ainda ser delegada ao context ou Redux, mas um estado centralizado
funciona muito bem quando existe pouca complexidade. */
function Main() {
const [data, setData] = useState([]);
const [isVisible, setIsVisible] = useState(false);
const [apiRequest, setApiRequest] = useState(false);
const punchlineRef = useRef(null);
const scrollToPunchline = () => {
punchlineRef.current?.scrollIntoView(punchlineRef);
};
const scrollToTop = () => {
punchlineRef.current?.scrollIntoView(0,0);
};
useEffect(() => {
fetch("https://nova-joke-api.netlify.app/.netlify/functions/index/api/random")
.then((res) => res.json())
.then((result) => {
setData(result);
})
.catch(error => console.log("Internal server error"));
/* Retorna um state vazio ao desmontar o componente evitando o erro de ler
um componente desmontado */
return () => {
setData([]);
setIsVisible(false);
};
}, [apiRequest]);
useEffect(() => {
isVisible ? scrollToPunchline() : scrollToTop();
}, [isVisible]);
return (
<>
{data.length === 0 ? (
<Loading />
) : (
<>
<Setup
jokeId={data.id}
jokeType={data.type}
jokeSetup={data.setup}
/>
</>
)}
<div ref={punchlineRef}></div>
<Punchline jokePunchline={data.punchline} visible={isVisible} />
{isVisible ? (
<button onClick={() => setApiRequest(!apiRequest)} >
New Joke
</button>
) : (
<button onClick={() => setIsVisible(!isVisible)}>Answer</button>
)}
</>
);
}
export default Main;
Sim tem muita coisa acontecendo neste componente, Mas basicamente ele carrega a api e exibe a resposta na tela. Vou tentar descrever o que acontece neste elemento:
Ele utiliza três tipos de hooks… Quê ?
O que é um react hook ?
Um React hook é uma abstração que permite o comportamento de objetos e classes de forma funcional (dentro de uma função pura). Isso permite uma escrita mais clara e a possibilidade reúso e independência das instâncias (hooks) utilizados.
É como se as classes do Javascript fossem levadas para o segundo plano, fazendo com que você trabalhe apenas com os métodos.
Temos três hooks de estado (data, isVisible, apiRequest) e eles não dependem de um único objeto, ou seja são independentes entre si, assim como os demais hooks que são de um mesmo grupo, mas podem depender de um hook de outro grupo (outro tipo).
Os hooks de estado (useState) tem uma variável de leitura (ex: data), uma de escrita (ex: setData) e um valor inicial (= useState([])). Um state hook pode ser alterado por um hook de efeito (useEffect) ou diretamente pela chamada do segundo argumento (ex: setIsData([“Echo”})). O ideal é quele seja alterado por um hook de efeito, mas não é obrigatório.
Já o hook de efeito (useEffect) executa uma função anonima em seu primeiro argumento baseado na alteração de estado do seu segundo argumento, que força um novo ciclo de vida no componente (renderização no caso) Ele também é utilizado para tratamento de entradas “impuras” no componente, tipo um request que não pode ser previsto se vai funcionar ou não, logo um useEffect vai cuidar da requisição. Pelo fato do efeito durar um ciclo (uma renderização) talvez ele precise de uma limpeza antes de ser reescrito, isso funciona igual uma variável que é limpa ates de usada novamente. É mais simples do que parece, embora deva ser analisado conforme o comportamento esperado.
O segundo argumento de um useEffect funciona como um watcher (observador) que renderiza o componente com o que possui no primeiro argumento.
O segundo argumento do efeito re-renderiza “pra sempre” se não for escrito (deve ser evitado nessa forma), uma vez se o array estiver vazio ou toda vez que um item dentro do array for alterado. De novo, é algo complexo de se entender mas muito simples de ser utilizado.
Já o hook de referencia (useRef) geralmente é usado para marcar uma posição no VDom(Uma espécie de pagina virtual que o React cria) a ser utilizada, é como se colocássemos uma ancora na tela para ir a esta posição quando for preciso. Até por essa razão eu tive que criar uma div vazia para marcar uma área que fosse do meu agrado.
Já no retorno do componente temos o uso de 2 operadores ternários concatenados, o que é feio pra cacete, mas é muito utilizado no React, o Operador ternário funciona exatamente igual ao if/else. É, ou deveria ser, mais fácil de utilizar, mas se torna confuso caso tenha mais de uma operação concatenada (como em nosso caso)
Dito isso, vamos criar os componentes que faltam, a começar pelo (teste do) Loading:
src/components/Loading/index.test.js:
import { render, screen } from '@testing-library/react';
import Loading from '.';
test('renders Loading message', () => {
render(<Loading />);
const loadingMessage1 = screen.getByText(/Loading.../i);
const loadingMessage2 = screen.getByText(/If persists check your network or refresh the page/i);
expect(loadingMessage1).toBeInTheDocument();
expect(loadingMessage2).toBeInTheDocument();
});
E agora ? Será que é possível testar apenas o refere-se ao Loading ?
Vamos pensar um pouco… Zoeira.
É possível selecionar testes específicos baseados em seus rótulos, no nosso caso você vai pressionar no terminal As teclas: W e depois R e digitar as palavras loading message.
na linha tests: vai aparecer o total de testes que falharam, foram ignorados e que passaram.
Pra que o teste passe vamos criar o componente de Loading em:
src/components/Loading/index.js:
// import "./index.css"
function Loading() {
return (
<div className="loading">
Loading...
<p>If persists check your network or refresh the page</p>
</div>
);
}
export default Loading;
Ao salvar o nosso vai passar, porém o teste anterior ainda vai apresentar erro. Mas antes de prosseguirmos vamos reativar a linha que importa o CSS nesse componente e falar um pouco sobre os CSS’s no React.
Tipos de CSS em React
O React aceita três tipos internos de escrita de CSS, e as três são boas:
CSS global
O css “puro” dentro do react não interpreta a separação do código (code splitting) ou seja, a sua aplicação é vista como um único documento. Isso geralmente traz o erro de um atributo CSS sobrescrevendo outro atributo que é renderizado antes dele.
Para evitar esse contratempo a solução é mesma utilizada no CSS comum: O uso de id’s e classes (classNames) para especificidade e o uso de variáveis CSS para atribuir dinamismo.
Ocorre também o fato de bastante pessoas adicionarem todo o css o arquivo App.css já que ele é o primeiro componente a ser carregado. Isso pode trazer o problema do CSS ficar confuso caso seja muito grande e elimina qualquer possibilidade de reúso do componente com estilo.
Eu recomendo você a colocar o App.css apenas o código que realmente generalista (tipo a importação da fonte estilos do body e as variáveis CSS) e tudo o que for classe e id de componente em um documento index.css na pasta do componente (ou Componente.css se você usar Componente.js como raiz). Faça isso mesmo que o as mesmas instruções se repitam, pois isso vai te livrar de muitas dores de cabeça.
Muda o nome da classe, copia e já era. Sabe por quê ? Porque é isso que o React vai fazer nos dois próximos dois Itens !
CSS Modules
O principio é o mesmo do CSS global porem ao adicionar o termo module no seu estilo (Ex: index.module.css), ele se torna uma classe com um identificador único(um sufixo) que impede a possibilidade haver colisões, mesmo que outro módulo ou className puro tenha o mesmo nome que ele.
Viu só, uma gambiarra em plena luz do dia !
Módulos CSS raramente são usados pois ficam no meio termo entre o mais simples e o mais elaborado.
Mas é útil saber que eles existem ! Até porque eles são o “segredo” do próximo item…
CSS-in-JS
CSS-in-JS é a forma mais moderna e polêmica de escrita estilo, pois você trata os atributos CSS como objetos Javascript. Até a escrita muda (background-color vira backgroundColor)
A principal vantagem desta técnica é a possibilidade tratar diretamente variáveis JS e props (propriedades únicas do seu componente) diretamente no arquivo escrito, que o próprio React vai convertê-las em módulos CSS aceitáveis para o funcionamento no navegador, ou seja, você corta o caminho da interpretação CSS do React pela contrapartida da escrita fora do padrão e alguns recursos avançados incompatíveis de forma direta (ex: animações). Um preço muito baixo a ser pago, já que o React vai fazer essa conversão em módulos de qualquer forma e por não haver perda considerável de performance em sua utilização.
Existem diversas técnicas para usar CSS-in-JS, as mais comuns são a escrita direta do objeto em uma variável, ou as bibliotecas JSS e Styled-components (Emotion é praticamente igual). Disparado a biblioteca Styled-components é a mais usada.
O uso dessa técnica também permite a extensão de recursos por meio dos frameworks de componentes como o Material-ui e o Ant design que possuem componentes já prontos para os casos de uso mais comuns.
SASS/LESS
Você também pode adicionar pré processadores CSS baixando as bibliotecas necessárias para usar os recursos dessas ferramentas.
O seu uso é desencorajado pelos mantenedores do create-react-app, mas FODA-SE ! Porque todo recrutador pede que você saiba usar este treco no React pra acomodar as gambiarras que algum filho da puta escreveu em mil novecentos e lá vão chavascas !!!
…..
Ou você pode realmente ser um programador experiente com essa técnica e preferir adaptar ela ao React e assim ganhar em produtividade…
Dito isto, vamos utilizar o CSS puro, de forma duplicada se preciso for.
Eu prometo que faço um artigo sobre outros tipos de estilo em React ainda esse ano…
Remova o // que define o comentário dentro de src/components/Loading/index.js e crie o arquivo abaixo:
No CSS existe uma animação chamada fadeInLeft que é exatamente igual a animação utilizada em Punchline, ela poderia ser atribuída para uma única classe e essa classe utilizada nos dois componentes, porém o meu objetivo é deixá-las separadas para que os componentes sejam independentes ao máximo em relação aos estilos.
Vamos criar o componente Punchline com os seus testes, raiz e estilos:
Se formos ao testes agora vai dar um puta erro quilométrico !!!
Desisto… SOU UM IMPOSTOR !!!!
Perai…
Deixei esse erro para que você possa ver que a stack do erro aparece diversas vezes devido a re-renderização do componente zoado e, mesmo em “ambiente de teste” ele mostra um erro explicito de execução !
Pra corrigir esse erro, vamos ter que refatorar alguns componentes. Eu já fiz isso e o erro era causado por um mal uso da minha parte dos métodos scrollInto, scrollIntoView e métodos assíncronos
Eis as correções:
src/components/Main/index.js:
import {useEffect, useRef, useState} from "react";
import Setup from "../Setup";
import Punchline from "../Punchline";
import Loading from "../Loading";
/* Note como este componente é o único que possui um estado e também o único que possui hooks.
Esta construção poderia ainda ser delegada ao context ou Redux, mas um estado centralizado
funciona muito bem quando existe pouca complexidade. */
function Main() {
const [data, setData] = useState([]);
const [isVisible, setIsVisible] = useState(false);
const [apiRequest, setApiRequest] = useState(false);
const punchlineRef = useRef(null);
const scrollToBottom = () => {
punchlineRef.current?.scrollIntoView(punchlineRef);
};
useEffect(() => {
fetch("https://nova-joke-api.netlify.app/.netlify/functions/index/api/random")
.then((res) => res.json())
.then((result) => {
setData(result);
})
.catch(error => console.log("Internal server error"));
/* Retorna um state vazio ao desmontar o componente evitando o erro de ler
um componente desmontado */
return () => {
setData([]);
setIsVisible(false);
window.scrollTo(0,0)
};
}, [apiRequest]);
useEffect(() => {
scrollToBottom()
}, [isVisible]);
return (
<>
{data.length === 0 ? (
<Loading />
) : (
<>
<Setup
jokeId={data.id}
jokeType={data.type}
jokeSetup={data.setup}
/>
<div ref={punchlineRef}></div>
<Punchline jokePunchline={data.punchline} visible={isVisible} />
{isVisible ? (
<button onClick={() => setApiRequest(!apiRequest)}>
New Joke
</button>
) : (
<button onClick={() => setIsVisible(!isVisible)}>Answer</button>
)}
</>
)}
</>
);
}
export default Main;
src/components/Main/index.test.js:
/* Render escreve, screen lê e waitFor constrói o await da promise */
import {render, waitFor, screen} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
/* Componente a ser testado*/
import Main from ".";
/* Cria o mock de um servidor. Simulado. */
import {setupServer} from "msw/node";
/* Faz o mock dos processos rest (get, post, put, patch e update) */
import {rest} from "msw";
/* URL real a ser consumida */
const BASE_URL = "https://nova-joke-api.netlify.app/.netlify/functions/index/api/random"
/* Spy Jest que emula o DOM para aceitar a função scrollTo */
global.scrollTo = jest.fn();
/* Cria o mock do servidor. O objeto json não precisa ser igual ao verdadeiro, mas se possível
deixe ao menos os itens que você irá usar conforme a construção original*/
const server = setupServer(
rest.get(
BASE_URL,
(req, res, ctx) => {
return res(
ctx.json({
id: 1,
type: "general",
setup: "Joke",
punchline: "LOL",
})
);
}
)
);
/* Processos de limpeza necessários antes de cada teste de api ser iniciado. */
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
/* Efetua o teste de api com tudo ok. Você pode testar o conteúdo da resposta,
o código da resposta, ou até mesmo mensagens de erro */
test("loads and displays the joke", async () => {
render(<Main />);
const response = await waitFor(() => screen.getByText(/general/i));
expect(response).toBeInTheDocument();
});
/* Testa o comportamento do botão que chama a punchline */
test("Activates the joke punchline card", async () => {
render(<Main />);
window.HTMLElement.prototype.scrollIntoView = function () {};
window.HTMLElement.prototype.scrollTo = function () {};
const punchlineButton = waitFor(() => screen.getByText("Answer"));
userEvent.click(await punchlineButton);
const punchline = await waitFor(() => screen.getByText(/lol/i));
expect(punchline).toBeInTheDocument();
});
/* Dispara uma nova piada */
test("Get one new joke", async () => {
render(<Main />);
/* Testa piada antiga para confirmar se a piada foi mesmo alterada */
const oldJokeId = await waitFor(() => screen.getByText(/1/i));
expect(oldJokeId).toBeInTheDocument();
server.use(
rest.get(
BASE_URL,
(req, res, ctx) => {
return res(
ctx.json({
id: 2,
type: "general",
setup: "Another joke",
punchline: "HUE HUE !",
})
);
}
)
);
/* Como o comportamento mudou. Temos que chamar um botão antes do outro */
const punchlineButton = screen.getByText("Answer");
userEvent.click(punchlineButton);
/* Simula um novo request, para confirmar se a chamada foi realmente efetuada */
const newJokeRequest = await waitFor(() => screen.getByText("New Joke"));
userEvent.click(newJokeRequest);
const newJokeId = await waitFor(() => screen.getByText(/2/i));
expect(newJokeId).toBeInTheDocument();
});
/* Observa erro */
test("Receive an network error", async () => {
server.use(
rest.post(
BASE_URL,
(req, res, ctx) => {
return res(
ctx.status(500),
ctx.json({message: "Internal server error"})
);
}
)
);
render(<Main />);
/* Utiliza uma função do Jest, mas funciona... */
console.log = jest.fn();
console.log("Internal server error");
expect(console.log).toHaveBeenCalledWith("Internal server error");
});
Agora, antes de aplicar mais estilos, vamos apagar o arquivo srcApp.test.js:
E vamos adicionar o componente Main na pagina Home:
src/pages/Home/index.js:
import Header from "../../components/Header";
import Main from "../../components/Main";
import Footer from "../../components/Footer";
function Home() {
return (
<>
<Header />
<Main/>
<Footer />
</>
);
}
export default Home;
Também temos que adicionar o Jest spy que permita a interpretação do movimento scrollTo no teste em:
src/pages/Home/index.test.js:
import {render} from "@testing-library/react";
import Home from ".";
/* Spy Jest que emula o DOM para aceitar a função scrollTo */
global.scrollTo = jest.fn();
test("Renders Home snapshot", () => {
/* Escreve o componente Home atual como um React.Fragment permitindo a comparação com snapshot */
const {asFragment} = render(<Home />);
/* Compara o fragmento com a snapshot existente */
expect(asFragment(<Home />)).toMatchSnapshot();
});
Agora salve os arquivos e atualize a snapshot que todos os testes deverão estar verdes !
Vamos commitar agora as nossas alterações enquanto tudo está indo bem. No terminal digite:
Como testes visuais e nem de acessibilidade serão necessários em nosso caso, vamos agora fechar os testes e executar o nosso app com o comando:
npm start
Olha ! mesmo sem termos adicionados todos os estilos o nosso app já está 100% funcional !
Para concluir a aparência que eu defini como ideal adicione os códigos CSS abaixo, mas fique a vontade para alterar essa parte como quiser
Obs.: Crie os arquivos CSS que não existirem e adicione ao seu respectivo componente, embaixo dos imports que já existirem e acima do componente, o código:
Os arquivos Setup e Punchline não serão alterados.
Mais um comiit e o projeto estará quaase pronto. Pretendo fazer mais um artigo GIGANTESCO adicionando mais alguns recursos e fazendo um apanhado do que foi feito aqui.
Vamos ao commit:
git add .
git commit -m "Refactor" -m "Add styles to Header, Footer, Loading, Main, Loading and App components."
git push -u origin main
Olá pessoall !!! Finalmente vamos ao filé do processo que é a entregado projeto ao “contratante” ! Como eu disse anteriormente o projeto é bem simples mas ficou bem legal e vai ajudar você a desenvolver o processo de testes em aplicações React já existente ou novas. Espero ajudar também se você não usar React, já que o processo inicial é igual para qualquer linguagem e vai ao encontro das grandes publicações sobre o tema (copie o que é bom e adicione a sua “cara”)
Ciclo de Testes
O ciclo de testes envolve Três passos simples:
Um teste que falha.
Um código que faça o teste funcionar.
A reescrita do código da melhor forma possível (refatoração)
Parece bem simples, mas algo me travava no começo dos testes TDD/BDD era seguinte questão:
Porque diabos eu tenho que escrever um teste que falha quando começo ?
Existem alguns motivos pra fazer assim.: Evitar um falso positivo (quando o teste fica verde mesmo sem fazer nada). Ajuda a criar o “rascunho” do que se pretende fazer, Ajuda a buscar o código que resolve a implementação (embora muitas vezes você acabe procurando como acomodar o seu teste ao código ao invés do contrário…) E por fim, permite que você altere o seu código para um formato melhor de forma mais confiável.
Apesar de ser identificado como um ciclo, os testes na verdade funcionam como uma espiral, pois assim que você consegue concluir um trecho do código você acaba efetuando outro logo em seguida, seguindo geralmente os seguintes passos:
Escreve o teste básico (que falha)
Escreve o código necessário para o teste passar.
Escreve mais testes/código de confirmação similares (geralmente passam também – OPCIONAL)
Escreve testes de situações limites (Edge cases em inglês)
Escreve testes além dos limites (Nesses casos o certo é disparar o erro correto)
Escreve testes de integração (ex: Quando componentes interagem)
Escreve testes de comportamento suspeito (Smoke tests – OPCIONAL)
Automatiza os testes (Geralmente com um navegador de testes como o Pupeteer)
Cria testes de comportamento do usuário (A/B tests – OPCIONAL)
Adiciona outros testes sob demanda (existem outros tipos de testes que podem ser necessários no projeto)
Considerando a lista acima, farei os 6 primeiros passos e o de automação, pois são os essenciais em qualquer caso. Quanto aos demais, fica a observação que eles existem e têm muitos cursos que os abordam, mas com esses básicos você já pode dizer que “sabe” testar e, espero, que eles realmente te ajudem em termos de produção de código também !
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Pronto. Referente a demais alterações de “limpeza”, faremos depois, quando for necessário.
Caso você salve os arquivos e execute o comando npm start o nosso app já deve estar funcionar corretamente.
Você pode usar o terminal integrado da sua IDE (VS Code no meu caso) ou o terminal do sistema (geralmente bash no Linux ou git Bash pra quem usa Windows)
Agora encerre a execução do app (CTRL +C com o terminal selecionado) e digite o comando:
npm run test
E o teste vai falhar pois o conteúdo esperado não existe mais. Como queremos criar um app chamado Nova Joke App, é este texto que iremos esperar.
Existe um arquivo chamado setupTests.js que apenas importa a react testing library, ele fica intacto. Vamos editar outro arquivo (abaixo):
Você vai reparar que apenas o titulo do teste, o nome da variável interna e o texto esperado foram alterados e a expressão /nova joke api/i define um regex para um texto insensitivo (que aceita letras maiúsculas e minusculas).
Ao Salvar o arquivo, o teste será executado automaticamente e ainda estará com erro. Logo, vamos alterar o texto de Hello word para Nova Joke App:
App.js:
...
return (
<><h1>Nova Joke APP</h1></>
);
...
Agora é só salvar e PIMBA ! O teste passou !!!
Bora pra casa ! Acabou…
Brincadeirinha… Agora vamos ao terceiro passo do ciclo que é justamente a refatoração do código, que neste caso seria exibir essa frase como título presente no header da página.
Dentro da pasta src você vai criar uma pasta chamada components e, dentro dela, uma pasta chamada Header (inicial maiúscula). Como precisamos testar rápido e começar com um erro, vamos conferir no teste se existe um elemento header em nosso app.
O arquivo não existe, logo terá erro. vamos criar ele com o nome de index.js dentro da pasta:
src/components/Header/index.js:
function Header() {
return (
<header className="header">
<h1>Welcome to Nova Joke APP</h1>
</header>
);
}
export default Header;
Ao salvar agora, o componente será carregado no teste e funcionará corretamente, mas o primeiro teste terá um erro. A princípio seria apenas a questão de carregar o componente Header dentro App, mas vamos focar agora na estrutura do aplicativo para que ele fique organizado.
vamos criar dentro da pata src uma pasta chamada pages(pode se chamar containers também), essa pasta terá outra pasta chamada Home, que será o componente principal do App.
Obs.: O aplicativo funcionaria da mesma forma se conteúdo estivesse dentro do componente App, mas esse tipo de separação permite uma melhor escalabilidade, ou seja, se o app crescer ao ponto de ter mais páginas e uma rota direcionamento ou gerenciamento de contexto, o componente App seria o responsável por importar e organizar esses recursos. E o arquivo index.js da raiz (fora da pasta src) não é alterado a não ser que seja extremamente necessário.
Dito isto crie o arquivo:
src/pages/Home/index.js:
import Header from "../../components/Header";
function Home() {
return (
<>
<Header />
</>
);
}
export default Home;
E altere o return de:
App.js:
...
function App() {
return <Home/>
}
...
Ao salvar o teste deverá passar.
Agora se observarmos bem (eu demorei a perceber) o componete App Já não cuida mais do que os seus testes propõem, na verdade eles só testam o Header, Então vamos criar o componente de teste dentro da pasta Header.
Obs.: Algumas pessoas utilizam uma pasta separada para agrupar todos os testes, porém isso pode se tornar confuso caso o programa cresça em tamanho. Você pode mesclar o uso de uma pasta para determinados tipos de testes e deixar outros na mesma pasta dos componentes ou usar somente as pastas dos componentes .
src/components/Header/index.test.js
import { render, screen } from '@testing-library/react';
import Header from '.';
test('renders app name inside Header', () => {
render(<Header />);
expect(screen.getByRole('heading')).toHaveTextContent(/nova joke app/i)
});
A convenção no React faz com que o componente que é criado como pasta use o arquivo index.js como raiz do componente. Da mesma forma o index.test.js será o módulo de teste desse componente.
Alterei o teste para um formato mais simples, porém que serve para observar como um teste não precisa ter muitos passos antes da conclusão (geralmente os três passos AAA).
O módulo de teste do App agora é redundante, mas vamos mantê-lo por enquanto.
Agora vamos criar o recurso mais polêmico de testes, um snapshot.
Um snapshot basicamente converte o componente testado em um objeto JSON e repete a conversão quando tem um novo save, dessa forma ele compara se teve alguma alteração entre um save e outro (diff comparation)
Ele era muito utilizado tempos atrás como uma forma simples de atingir 100% de código testado, já que ele copia todo o código solicitado para dentro do seu objeto. Depois caiu em desgraça quando começou a ser visto como uma tática sem-vergonha para alegar que um código foi testado, pois ele obviamente não testa nada além da comparação de objetos, o que é muito pouco em cenários realistas de testes.
Na minha opinião, o Snapshot test é um smoke test, porque ele justamente aponta algo que pode provocar um erro, mas não um erro em si. Se algo mudou isso pode dar erro, cabe a você verificar se a mudança é aceitável, um erro ou se é preciso criar um novo teste de outro tipo para detectar uma falha.
Enfim, nem herói e nem vilão, ele só faz o que precisa. Vamos criar apenas um Snapshot test no componente Home, pois ele é justamente o local onde tem mais componentes agregados e assim, novas alterações terão um impacto direto. vamos lá:
src/pages/Home/index.test.js:
import {render} from "@testing-library/react";
import Home from ".";
test("Renders Home snapshot", () => {
/* Escreve o componente Home atual como um React.Fragment permitindo a comparação com snapshot */
const {asFragment} = render(<Home />);
/* Compara o fragmento com a snapshot existente */
expect(asFragment(<Home />)).toMatchSnapshot();
});
Ao salvar o arquivo vai aparecer essa mensagem no terminal:
...
› 1 snapshot written from 1 test suite.
...
Também será criada uma nova pasta com o snap fragment em:
src/pages/Home/__snapshots__/index.test.js.snap
Este é o local onde o fragmento é guardado, não precisa ser alterado nada manualmente, pois a alteração se dará pela linha de comando.
Obs.: Geralmente o snapshot test só é executado em um momento específico no ciclo de desenvolvimento, mas como estamos executando todos os testes na forma padrão, ele é atualizado a cada save.
Para fins de simplicidade, vamos agora criar o componente Footer e observar o snapshot também. Crie a pasta Footer e o arquivo index.js dentro da pasta components:
Altere o nome e endereço do link para o seu Github (caso não tenha, adapte para um local que você queira redirecionar o usuário) e salve o arquivo.
Agora importe o Footer para dentro do componente Home:
src/pages/Home/index.js:
import Header from "../../components/Header";
import Footer from "../../components/Footer";
function Home() {
return (
<>
<Header />
<Footer />
</>
);
}
export default Home;
Salve. Vai aparecer no terminal um erro de snapshot, com o trecho adicionado em verde. Remoções aparecem em vermelho.
Agora precisamos apenas atualizar a Snapshot. Pressione a tecla u com o seu terminal selecionado e a snapshot será atualizada automágicamente !!!
...
Snapshot Summary
› 1 snapshot updated from 1 test suite.
...
Bom, percebi o texto agora está quilométrico e também que ainda não efetuamos nenhum commit para salvar o que fizemos. Logo vamos fazer o commit agora e concluir o projeto no próximo artigo.
Saia dos testes em seu terminal pressionando CTRL + C e digite os seguintes comandos:
Dica: você pode adicionar duas mensagens de commit ao mesmo tempo, a primeira vira uma espécie de titulo. Já na segunda mensagem você pode usar a tecla enter pra pular linhas, desde que as aspas de fechamento não sejam inseridas.
Olá amigos, espero que você e os seus estejam bem no momento em lêem este compêndio. Antes de iniciar esta parte, me surgiu uma duvida que preciso justificar na forma daquelas famosas perguntas irônicas que eu escuto ao longo da vida:
Marley, pq vc não usa o Notion ao invés de escrever textinho ?
E sim, a pergunta é relevante ! Realmente, qualquer roteiro pode e deve ser transformado em uma lista de tarefas.: seja ela Notion, Kambam, MS Project ou qualquer outra forma que você use para listar os seus afazeres profissionais… … Até mesmo um to-do poderia ser útil.
A questão é que o roteiro consegue sintetizar diversos conceitos de uma forma “orgânica”, ou seja, É mais natural para o seu cérebro lidar com um texto do que com uma lista.
Uma lista exprime a ideia de algo que têm que ser feito, e você tem que fazer tudo contido nela antes de fazer algo a mais, caso contrário você pode ficar sem dinheiro para comprar arroz no supermercado, ou não realizar as tarefas aderentes a avaliação de uma entrevista de emprego (imagina te pedirem pra escrever uma redação e você entregar uma folha cheia de risquinhos). A execução da lista tem que ser PURA ! (entendedores entenderão) Listas remetem a obrigações e compromissos. E o objetivo do roteiro é justamente ser mais amigável e realista à como algum superior ou contratante te exporia o que precisa ser feito, ou o que você poderia perguntar pra pessoa caso ela te pedisse pra você criar um “novo Uber que paga mais” ou um “Facebook à prova de gente chata”, enfim, você não teria uma lista de possibilidades pronta além do que a sua cabeça pensaria nesse momento… Por isso conversamos ou enrolamos dependendo do caso. E não adianta dizer que é introvertido ou tímido pois, dependendo do caso você perderia um cliente que realmente vale a pena, ou não encontraria a chance de organizar seu amado Notion pra enviar por e-mail o que é necessário no projeto, já que outra pessoa disponível poderia ser contratada.
Dito isto, vamos ao projeto… Que é bem fraquinho ! Mas vou tentar fazer valer a pena ! Let’s go !
O roteiro do projeto
O texto a seguir é a simulação de uma conversa:
O que é o projeto? O projeto é simples porque o foco serão os testes. Ele consiste em um site SPA que consome a joke api (EDIT: issso me rendeu o próximo artigo !) que retorna um piada aleatória a cada request. A página vai acessar uma piada ao carregar e a pessoa vai clicar em um botão ou área para chamar a punchline (resposta), dai vai aparecer um novo botão para a pessoa chamar uma nova piada e por aí vai…
Fritas acompanha ? (Só isso mesmo ou algo a mais?)
Eu quero que o projeto tenha uma página About para exaltar o meu projeto, mas pode ser só uma menção no rodapé mesmo. Como a gente vai falar de testes, vamos usar a ferramenta de teste padrão do projeto, se não tiver vamos usar a mais popular dísponível. E animações ! Eu quero animações porque o projeto é simples então precisa de uma coisa pra deixar bonito ! Uma Header na parte de cima mesmo que só tenha o título, pra ficar bonito também.
Têm autenticação de usuários ?
Não.
Paleta de cores ?
Pode ser qualquer uma, ficando bonito é o que importa.
Você tem alguma plataforma em mente? (Como você quer que seja feito ?)
Eu quero que o MEU projeto seja feito em React ! Porque eu vi no Linkedin que ele é FODA !!! E aqueles carinhas que vendem cursos falam que quem usa ganha doze mil Reais por mês !!!! Vamos nos ajudarmo-nos ! Ah, tudo vai estar no Github e ele tem que ser hospedado no Heroku ou no Netlify. Ok ?
Pronto.
Extrato
O que tiramos dessa conversa ?
Bom, exceto a exigência do “manda-chuva”, esse projeto pode ser tranquilamente executado em JS puro ou qualquer outra linguagem/framework com acesso web. Como “ele” quer em React vamos usar o que o React nos propõe, sem overengineering (ou seja nada de Redux nem Styled-components)
IMPORTANTE: se atente que foi usada a palavra bonito no diálogo e o conceito de bonito é relativo. Tome cuidado para não ser pego por esse tipo de abstração, mas pareça incomodado pela expressão, informe se é possível efetuar mudanças nesses aspectos caso seja possível. Mas pelo amor de Deus, seja realista nas expectativas.
Algumas perguntas, obviamente mudam em uma conversa real: seria necessário perguntar o valor do projeto, você com certeza teria (ou deveria ter) alguma ideia pra agregar valor inicialmente. O cliente nem sempre tem a melhor visão sobre a ideia, as vezes ele quer algo em Node mas uma solução em Ruby é melhor. O WordPress tá aí até hoje, ou talvez você conheça apenas o Laravel ou o NextJS, daí você usa o que sabe, pois assim o resultado sai. Portanto, sempre planeje e argumente com quem vai te pagar (ou com você mesmo) para extrair o máximo de informações possíveis antes de começar.
Nesse caso eu extraí a lista abaixo:
O usuário será um visitante.
O projeto sera feito com create-react-app
O processo de construção será baseado em TDD com o uso da React-testing-library
O projeto terá duas rotas no máximo, Caso necessário, usaremos o React Router.
O código-fonte ficará armazenado no (Co-pilot) Github. (ou semelhante)
A hospedagem será por meio do Netlify (ou Heroku). Disponível conforme estas plataformas.
Para estilos usaremos CSS puro.
Para as animações usaremos CSS puro ou Framer motion.
Para as requisições vamos usar fetch (já deu de Axios né galerinha)
A estimativa de entrega é uma semana.
Pronto é isso !
Agora eu vou só testar um negócio aqui e começaremos o FINALMENTE oprojeto !
Até a próxima !
Nota: antes de escrever este artigo encontrei dificuldade em como expor da melhor forma a parte prática, talvez os dois últimos capítulos demorem um pouco mais que o previsto
Bom, eu gosto de usar React e tive essa idéia ao tentar aprender a combinação React Testing Library e Jest. Qualquer linguagem ou framework irá servir para o meu propósito que é justamente como sair do zero com testes em um projeto simples. Quero justamente criar o que me falta na minha limitação, que é o elo entre o abstrato e a escrita inicial. Caso dê certo, aquela paralisia inicial e o famoso tutorial-hell serão reduzidos em futuros projetos e acho que é isso que falta em muitas pessoas que começam a programar…
E outra coisa: O Javascript precisa de um runtime para ser testado facilmente.
É até possível testá-lo sem de forma pura, mas só é recomendado em casos específicos. O básico é realmente o uso do runtime mais famoso do universo js, o Node.
Vamos manter as coisas simples certo ?
Por que testar ?
Testar não serve só pra ver se funciona, ou para impressionar a chefia ! Os testes possuem alguns objetivos diretos e te ajuda em alguns conceitos que você utilizará agora ou no futuro. Alguns desses conceitos são:
Testar o comportamento do programa: se ele está funcionando como o esperado
Atomizar o programa: quanto menor for o trecho independente de um código, mais fácil ele fica de ser analisado. Os testes ajudam nesse tipo de organização.
Trazer “confidência” para o código: se você sabe como o seu trecho de código deve funcionar e os testes garantem o funcionamento, logo você pode alterar o código diversas vezes com a confiança que se tiver algum erro ele será detectado
Testar situações limites: “O que acontece se eu colocar 10 espaços vazios no meu username ? E se eu colocar um script nesse campo ? E se eu colocar um valor negativo no campo qtd ?” Esses são alguns exemplos de situações limites que podem existir no seu código.
Automatização e reusabilidade: Se os testes são um roteiro de como o meu programa deve funcionar, logo eu posso repassar essa tarefa para uma ferramenta automática fazer esta tarefa pra mim. Isso ajuda a reduzir o famoso teste “Xouveraqui” e é quase uma benção quando se trata de formulários a serem preenchidos.
Prototipagem: Vamos pegar o bom e velho to-do: vamos supor que você tenha um conjunto de testes para um to-do em Jest. Este framework é compatível com praticamente todo o ecossistema Javascript, logo você pode fazer o to-do funcionar em React, Vue, Agular, Svelte, etc. E digo mais ! Você pode adaptar essa “solução” para o PHP-unit ou o Capybara e criar o programa em PHP ou Ruby on Rails. E DIGO MAIS !!! Os testes ainda podem ser expandidos para testes visuais e/ou de preenchimento automático e fazer com que a sua solução tenha a mesma aparência rotina de execução em qualquer plataforma !!! Antes de morrer talvez eu faça isso…
Tudo isso pode ser feito com o auxílio de testes. Porém nem todo mundo gosta de testar códigos, principalmente quando a janela de execução é curta ou o trabalho específico não é bom. mas no geral um código testado, mesmo que não nasça melhor, pode se tornar melhor. Já um código sem testes pode se tornar “imexível” com o tempo.
O que testar ?
Atenção ! Não existe uma regra que impõe o que deva ser testado em uma aplicação e, apesar do senso comum definir que tudo deva ser testado, a “lei geral da criação” (feito é melhor que perfeito) que se aplica ao mundo real nos leva a um caminho próximo ao do listado abaixo:
Recursos de alto valor: Se o seu programa é um app de vendas, A coisa mais importante para ele é efetuar uma venda ! Isso deve ser construído e testado antes de qualquer outra característica. Um app de notícias precisa atualizar em tempo real. Um de música precisa garantir a reprodução de áudio. E assim por diante. Geralmente são utilizados testes unitários E de integração, mas podem haver outros tipos também.
Situações limites de recursos de alto valor: O que acontece se a compra for cancelada ? o que acontece se a notícia for atualizada no momento do acesso ? O que acontece se uma pessoa quiser a transcrição da letra da música que está tocando ? Enfim, considerando o mais importante no seu programa, você pode se desdobrar em maiores detalhes para garantir que tudo está conforme o necessário.
Recursos problemáticos: A carne de pescoço do programa. Uma situação clássica é o acesso à uma API externa, que pode ou não estar funcionando. Eventos de toque ou de movimento que os ajustes são bem complexos também. Alguns detestam formulários que de fato, são tretosos. Esses cenários estão fora do eixo essencial para o sistema (não dá prejuízo imediato) mas podem trazer contratempos ao seu produto. Alguns especialistas recomendam um tipo de teste chamado teste de fumaça para detectar erros de difícil detecção. Fica a dica caso precise se aprofundar no tema de testes.
Funcionamento básico: o resto. Aliás, muitas pessoas criticam os testes de algo que é garantido pela lógica, e de fato estão certos. Porém as vezes o seu programa tem um comportamento que se desvia da norma: ex: um app de vendas não tem numero negativo no campo de quantidade, logo o que você vai fazer quando “bater no zero” ? desativar o botão “de menos”, disparar uma mensagem ou fazer o zero aparecer constantemente ? Por isso uma forma de definir os testes unitários é definir um teste chamado teste de comportamento (behavior test driven ou BDD)
testes comportamentais: podem ser definidos no chamado teste A/B, é um teste onde dois tipos de ambientes são mostrados ao seus usuários e o que obter melhor resultado acaba sendo absorvido pelo sistema. Geralmente envolve a parte de apresentação (aparência) do sistema para definir o que é mais agradável para (a cobaia) o usuário. Geralmente são necessários quando seu produto já possui um tráfego de visitantes, senão não faz muito sentido. Por isso é uma última análise de teste. Mas está longe de ser ignorada e faz a diferença quando aplicada em produtos que já possuem mercado.
Como testar ?
O padrão mais utilizado para escrita em testes é conhecido como AAA (Arrange, Act and Assert – arranjar, atuar e afirmar em tradução livre) que definem as três partes que um teste deve ter para ser compreensível e eficaz. Na verdade é bem simples, veja:
Arrange (arranjar/organizar): Basicamente é a preparação do que o teste precisa. Entenda que não é preparação de todo o ambiente de teste mas de um teste em sí. O exemplo prático é carregar o componente a ser testado, mesmo que nem tenha nada escrito nele. Ou criar um mock, que é uma simulação de algo que o seu componente precisa (ex: resposta de api). Dessa forma o seu teste vai ter o que precisa para funcionar corretamente naquele trecho.
Act(Atuar/agir): A ação do teste,ou seja, o que o teste faz. Exemplos: com o componente “arranjado”, você vai testar se aparece a palavra “Descontão” quando ele carrega, logo você só precisa carregar o componente. Agora você precisa testar se ao clicar na palavra “Descontão” um novo preço deve aparecer ao lado do antigo e em verde, também deve aparecer um botão escrito “Eu quero!”. Agora você tem que carregar o componente e simular um clique. E assim por diante.
Assert (afirmar/aferir): é justamente avaliar a igualdade ou diferença da sua ação. Exemplos: A palavra “Descontão” apareceu ? Não apareceu a mensagem “teste de componente” ? Enfim, O seu resultado esperado para definir se trecho de código está correto
Basicamente você cria o cenário e avalia a entrada e a saída, mas mesmo parecendo óbvio, essa é a maneira mais produtiva de se fazer testes.
Dito isto, vamos ao que contém a criação básica de um projeto.
Sobre projetos
Antes de começar eu vou tentar usar conceitos que são úteis tanto para o desenvolvimento tradicional, quanto para o auto proclamado ágil:
Atores e histórias !
Em desenvolvimento de softwares existe o conceito do ator, que é, basicamente, um objeto que representa quem acessa uma interface do seu programa. Não chega a ser necessariamente uma pessoa, mas quase sempre é uma, quando não, geralmente é um serviço ou equipamento que dependa da ação de uma pessoa. Existe uma gigantesca teoria por traz desse conceito, mas você não precisa saber tudo isso antes de começar, até porque tanto esse conceito como o próximo, têm que ser administrado por uma equipe em qualquer cenário profissional, a não ser que você esteja trabalhando sozinho, como nesse caso.
Requisitos funcionais (e não-funcionais)
Requisitos funcionais e não-funcionais, são conceitos que tangem os recursos e características necessários para a subsistência do seu projeto, ou seja, o que precisa pra existir e ser o “SEU” projeto. Requisitos funcionais geralmente são características técnicas do seu sistema(linguagem, banco de dados, comportamento do sistema), e não-funcionais remetem a características que são auxiliares ao seu sistema (Custo, precificação, confiabilidade, estruturas de armazenamento, etc)
Definição de Requisitos é um dos pináculos do desenvolvimento de software, e não vou me meter a ensinar ninguém sobre isso, vamos ficar no básico e ir para a parte prática que é a construção do roteiro por meio do “caminho feliz” (YAY !!!)
Roteiro – conceito
Eis aí o que eu espero ser o divisor de águas em nossas vidas (amém !!!) A construção de um roteiro é essencial, pois o seu lado “humano” vai permanecer com as bases que terão de ser executadas pelo seu lado “programador” e isso irá ajudar a reduzir a névoa de pensamentos que geralmente nos ataca ao começar um projeto. Lembrando que isso não irá definir se o seu projeto será bom ou ruim, mas irá garantir que você comece “DO ZERO” até a conclusão de um produto minimamente viável (Minimal Viable Product – MVP). O ideal na construção de um roteiro é ser o mais objetivo possível porém mantendo todos os itens necessários para que o que será feito seja claro, específico e realista. Evite ser Linkediano nesse momento, seu projeto não faz o cliente ter uma experiência única e irrepetível a cada acesso, ele baixa a porra de um PDF ! Ou uma piada em inglês no nosso caso. Defina o seu objetivo, ações e pronto. Quantos e Quais atores possui: Usuário (sem especificar) é quase um termo genérico, evite. Se não precisa de autenticação, então é um visitante, se precisa são usuários autorizados e não autorizados. Se tem dashboards, então tem usuários administrativos. Assinatura: assinantes e por aí vai… Eis aí os seus atores, e deles vão surgir o que é preciso para que existam dados, interfaces, rotas de navegação, funcionalidades, etc. Requisitos funcionais: Vai usar React? PHP? Postgres? Lê QR? Têm IA? Python? Você que sabe ! Diz ai…. Requisitos não funcionais: O código estará hospedado em plataforma open source? Necessidade de precificação? Disponibilidade? Servidor próprio ou de terceiros?
etc…
E com todos esses conceitos contidos em um texto que serve mais como guia do que como documento você, provavelmente, conseguirá desenvolver melhor a sua criação ou discutir sobre ela com maior clareza.
Conclusão
Se você leu até este ponto deve ter percebido que algumas características tanto do desenvolvimento quanto dos testes são bem semelhantes. Ao saber isso você acaba solucionando uma etapa em cada processo sem muito custo mental (cansaço).
Tú já é 2x e nem começamos !!!
Ah uma questão também que sempre os iniciantes vão sofrer é sobre como codar, e pra isso geralmente eu uso um “Toolbelt” de códigos, que basicamente é uma coleção de snippets para problemas comuns. Eles já podem vir com testes e prontos para uso futuro (porque choras Github Copilot ?)
Mas isso é pra outra hora….
A próxima parte irá continuar focando nessa preparação, que neste caso é mais importante que o código em sí, porque serve de guia para futuros projetos maiores (espero)
Outrora faria uma looonga introdução sobre como os testes são importantes utilizando mais uma vez a “fábula” do joãozinho, sobre como ele não utilizou testes em sua aplicação e a necessidade de adicionar recursos ou refatorar o app fez da vida dele um inferno(js), porém além de ser muito parecida com a versão anterior sobre os nomes de variáveis, também seria desnecessária já que nesse caso o Joãozinho (somos nozes) fui eu !
Estava eu todo feliz com a minha versão da API do Star-Wars, quando eu decidi reorganizar melhor o código, me livrando de alguns Frankeinsteins na parte do CSS e tornando a escrita do JSX mais elegante e “perfumada”…
Lá fui reescrever uma chamada de função e criar uma custom hook e.. Boom ! Tudo parou de funcionar. A chamada de api ficou maluca e o console ficou mais vermelho que colorau de feira, eu voltei com o que estava escrito antes e deixei lá o meu código funcionando com todos os “code smells” e gambiarras que tenho direito, já que só eu acesso esse treco mesmo…
Situação análoga já havia me ocorrido outrora, porém ou eram simples a ponto d’eu resolvê⁻las sem muito esforço ou simplesmente nem fazer.
O fato é que eu queria fazer algo com TDD desde o começo pra ver se faz algum sentido mesmo ou se “99% dos programadores do mundo” estão certos em não utilizar testes em seus projetos.
Ah, esta parte estou escrevendo antes de começar, porém com algum conhecimento prévio sobre o uso de testes. IMPORTANTE: não use este artigo como fonte de boas práticas pois o único tipo de teste que eu sei fazer é o unitário mesmo. Se você quiser aprender sobre teste de fumaça, fogo, sombra, água-fresca, integração, divisão, refração, animação, coação ou qualquer outro que já tenham inventado, com certeza deve existir algum artigo no Medium ou um curso de trezentos contos na Udemy para te “ensinar”.
Bom, no próximo artigo, ainda sem escrever código ou testes, eu vou definir qual plataforma e projeto eu vou desenvolver, Isso é importante pois pode ajudar muitas pessoas que ficam presas no “tutorial-hell”, pois dessa forma você sempre terá uma forma de começar algo novo sem muita paralisia. Diabos na verdade eu vou tentar fazer algo nesse sentido e agregar mais valor aos meus escritos… Ou desistir e nunca mais tocar no assunto… Enfim…
Decisões heurísticas, navalhas e outros métodos de “adivinhação”
Bom, inicialmente depois de quase uma década eu estou escrevendo este artigo. Talvez ele passe por algumas alterações futuramente pois, no momento, ele é apenas uma rápida conclusão referente ao tema de uso dos If’s. Como sempre, ele mais opinativo que professoral, porém busca expor porquê a computação de alta complexidade(tipo IA) não é apenas um amontoado de if’s como dizem por aí… Não da forma que dizem, pelo menos.
busca binária e navalha de Occam
Pra quem não conhece a busca binária é um algoritmo que faz a busca por um valor mediante a bipartição de um vetor amostral, ou seja, vai cortando pela metade o “array” para encontrar o item… Estranho né ? Mas faz sentido se você tiver uma “lista” muito grande de itens para depender de uma busca sequencial (ex: Encontrar o numero 256739 dentro de 1000000 de itens). Existem Outros tipos de busca que podem ser melhores que este dependendo do caso, tipo bubblesort ou mergesort, sem falar dos cálculos de big O que comparam diversos tipos de busca para definir a melhor escolha.
… Por isso que existem faculdades né ??? Pra ensinar sobre esses temas.
Já a navalha de Occam (AKA lei da parcimônia) postula que: “dentro de um conjunto de explicações, a mais simples, é sempre a mais adequada.” Ou e bom português: É melhor explicar do que complicar. E, a não ser que você (tenha a sorte de) nunca ter acessado o Linkedin, já deve ter ouvido falar dessa expressão.
Mas Marley, o que isso tem a ver com I.A. ?
Well, o problema é que nem sempre é possível aplicar essa lei em um objeto observado, seja por intensificar as distorções ou por falta de referência (Ahn ???)
Heurística, limites e retroalimentação de I.A.
Por exemplo, quanto mais luz há em um ambiente, mais fácil é de enxergar, mas ao mesmo tempo se torna mais nítido as falhas que um objeto possui (ex: rugas e acnes nos rosto). Na matemática e na computação esse fenômeno é observado em situações chamadas limites, que são cálculos que tendem a um resultado, porém nunca chegam a este valor. Outro ponto em que se analisa “distorções” ao invés de pureza é na análise espectral de frequências, onde se deve observar variações que quase nunca tem um “ritmo” porém possui a mesma origem(ex: pulsar estelar).
Já com relação a falta de referência, o modelo matemático ou de estudo precisa justamente um guia que vai servir de comparação com entrada para gerar uma nova saída. Qualquer espécie de medida ou transcrição entra neste exemplo.
É aí que entra a IA na história, pois o que sistemas de inteligência artificial precisam, é justamente do modelo de aprendizado, onde geralmente partem “do zero” e com afirmações humanas ou de ocasião(ex: tempo, distância), determinam se estão o caminho certo. Desse ponto em diante, os modelos não dependem mais de um guia, mas sim dos dados que ele mesmo gera(retroalimentação), chagando assim, em um modelo autossustentável, o qual conhecemos por (bruxaria !!!!) inteligência artificial. Daí chega um momento onde mais amostras podem ser prejudiciais a cada tomada de decisão, pois pra todas elas, funções complexas chamadas nêuron vão atribuir um peso aquela decisão, e diversas decisões binárias, vão se tornar justamente uma tendencia que vai de 0,0 até 1, mas nunca 0 e nunca 1.
Viu só, igual o treco do limite… E o “basculho” de Occam, ou aquelas meninas que usam lâmpadas redondas para o rosto ficar mais iluminado no canto e sombreado no meio, o que suaviza suas linhas de expressão… Enfim, Tudo é uma coisa só !
[Insira sua teoria da conspiração aqui]
Enfim..
Destes resultados, surge a tendência de absorver a resposta mais simples (Occam) e dela trazer a resposta da forma que nós, meros mortais entendamos.
E assim meus amigos, uma miríade de situações simples geram uma situação complexa e os cálculos matemáticos ou heurísticos (que trazem respostas superficiais para iniciar uma avaliação mais complexa) mesmo podendo ser considerados um amontoado de if’s e elses, são na verdade algo que leva a compreensão humana para níveis inimagináveis.
Stratégia, em grego strateegia, em latim strategi, em francês stratégie…
Chegamos ao ponto que me despertou escrever estes artigos, o conceito do “se” elevado ao nível de padrões de projeto (Design patterns).
Mas que diabos é esse conceito?
O que são design patterns?
No que isso vai me ajudar a essa altura da vida?
De forma simples, Design patterns são arquiteturas de construção do seu código, algumas linguagens já possuem algumas dessas estruturas já incorporadas, mas no geral, a referência original dos “depat’s” ainda é a melhor fonte de informação.
No Caso do Strategy ele pega a implementação “real” das escolhas e a incorpora em uma classe(ou objeto) que compõe as escolhas. Em resumo, ele te ajuda a não ficar dependente de if’s e switches diretos em sua arquitetura, principalmente se ela se basear em classes.
Por exemplo vamos pensar em um cardápio de restaurante:
Tô com fome…
Ele poderia ser implementado com uma cadeia de if’s e switches dependendo do prato, mas isso geraria um gigantesco encadeamento, e, provavelmente o item seria diferenciado pelo seu número, o que resolveria o problema de endereçamento, mas não o caso da organização em sí.
Na organização proposta com classes e strategy, os itens seriam diferenciados pelo tipo, cabendo aos seus objetos de tipo definirem características próprias como preço e ingredientes.
Ficaria algo mais ou menos assim:
// Itens do cardápio - "MoDelS"
const diario = "prato do dia";
const comerciais = {
prato1: "Viradão",
prato2: "Contra-Fillet",
prato3: "Parmeggiana",
};
const pratoCompostoPrato = ["Picanha", "Vegetariano", "Baby-beef"];
const pratoCompostoBebida = ["Refrigerante", "Suco", "Cerveja"];
const pratoCompostoSobremesa = ["Sorvete", "Pudim", "Diet-cake"];
// Definições de preço e composição - CoNtRoLlErS
const pratoDoDia = (prato) => {
const itemSolicitado = {prato: prato, preco: "R$ 16,00"};
return itemSolicitado;
};
const pratosComerciais = (prato) => {
const itemSolicitado = {prato: prato, preco: "R$ 20,00"};
return itemSolicitado;
};
const pratoComposto = (prato, bebida, sobremesa) => {
const composicao = {
prato: prato || "Sem prato",
bebida: bebida || "Sem bebida",
sobremesa: sobremesa || "Sem sobremesa",
preco: "R$ 35,00",
};
return composicao;
};
const comandaVazia = () => {
const vazio = {
pedido: "sem pedido/comanda vazia",
preco: "R$ 00,00",
};
return vazio;
};
// Classe construtora - Oo
const Cliente_1 = class {
constructor(pedido) {
this.pedido = pedido || "Sem Pedido";
}
};
//Chamadas de instanciação do objeto - ViEwS
//const comanda = new Cliente_1()
//const comanda = new Cliente_1(pratosComerciais(comerciais.prato1));
//const comanda = new Cliente_1(pratoDoDia(diario));
//const comanda = new Cliente_1(pratoComposto(pratoCompostoPrato[1],pratoCompostoBebida[0],pratoCompostoSobremesa[2]));
//const comanda = new Cliente_1(pratoComposto());//const comanda = new Cliente_1(comandaVazia());
console.log(comanda);
Se possível, cole esse código no console e “descomente” suas saídas para ver que agora a responsabilidade(controle) de lidar com os tipos de prato são de seus objetos, e não da classe que o chama.
Isso facilitaria uma precificação por tipo de pedido, e poderia ser expandido sem quebrar o código existente. Poderia ser criado um tipo especial para itens fora dos grupos ou promoções, note também que eu comentei que este tipo de organização lembra muito uma construção M.V.C. pois este método de construção é o mais lógico na maiorias dos casos de manipulação de dados.
… E que quem é vegetariano tomou na tarraqueta com o preço ….
Enfim, o objetivo deste exemplo é apenas mostrar que o encadeamento de decisões nem sempre precisa estar contido em um bloco de decisões explícito, porém o ideal é sempre começar com um, pois feito é melhor que perfeito 😉
Nhai! Marley, eu não entendi nada! Não seria mais fácil fazer um cardápio com todos os itens de uma veiz?
Sim, mas seria ideal somente se muitos preços fossem avulsos, o que pode ser incluído facilmente em mais um objeto de modelo, sem falar que seria um cardápio grande pois só as combinações compostas, teriam vinte e uma possibilidades, mais 7 pratos do dia, com mais 3 comerciais são 31 items. Com a estrutura strategy gerenciamos apenas três objetos que podem crescer ou ter novos itens de forma independente, ou seja, são escaláveis.
E com isso termino a segunda parte do tema if. Na próxima, vou tentar desmistificar a acusação que a IA é apenas um monte de if’s concatenados… Se bem que é. Mas o que não é?
Até a próxima 🙂
Se esse cardápio explodir, seus colegas vão todos pelos ares senhor…
Continuarei aqui o principio inicial do blog que é (tentar) ajudar a quem está iniciando os caminhos da programação e após tratar das variáveis vamos falar agora de uma das partes mais importantes do mundo da programação. Os comparadores de decisão.
– “Mas Marley, são apenas if e switch/cases…”
É o que você pensa… Vem comigo!
A computação depende, quase sempre, se basear em ações anteriores e até futuras para definir o que, e como, um programa vai se comportar e, a única forma de se fazer isso é por meio da comparação; desde um formulário, uma tela de login ou até mesmo a tão falada inteligência artificial! Estamos apenas colocando máquinas para comparar uma informação A com uma informação N. De exemplo:
var numero = 11;
if (numero > 10)
console.log ("Este número é maior que 10.");
else
console.log ("Este número não é maior que 10.");
Simples não?
Nota:Este código pode ser executado diretamente no console do navegador ao pressionar as tecla F12 e usar a opção console (em notebooks é preciso pressionar Shift junto)
Mas é fundamental enxergar como é poderosa a capacidade de comparar valores em um algoritmo. Abaixo temos a abstração simples de um sistema de autorização de login que gera uma saída diferente dependo do nível hierárquico de uma pessoa:
var nivelDoFuncionario = 2;
if (nivelDoFuncionario === 1)
console.log ("Fala peão !!!");
else if (nivelDoFuncionario === 2)
console.log("Boa tarde Doutor !!!")
else if (nivelDoFuncionario === 3)
console.log ("Boa tarde ilustríssimo e digníssimo chefinho !!! \nSe espirrar saúde !");
else
console.log ("ACESSO NÃO AUTORIZADO!");
Puxação de sacos à parte, o importante para fixar na memória neste momento é que essa saída pode ser qualquer ação a se fazer durante o “ciclo de vida” da aplicação, e esse é um conceito muito mais profundo.
.. mas vamos começar do mais simples..
Como foi visto nos exemplos anteriores, o comando if compara dois valores e retorna uma saída caso a comparação(expressão) seja correta(verdadeira) e caso seja necessário mais de um fator de comparação ele pode ser encadeado com a ajuda do comando else até que todas as abordagens de comparação sejam satisfeitas. Em algumas linguagens de programação a escrita muda um pouco (Python por exemplo, troca o else if por elif) mas o resultado é o mesmo, por isso, vou tentar tirar alguns coelhos da cartola para que você perceba que, antes mesmo de qualquer projeto ou linguagem que você escolher, você tem que saber exatamente quais são os limites que você vai trabalhar, e isso é pura e simplesmente uma comparação.
O básico sobre if’s e switches
Você não precisa usar variáveis sempre que for usar o comando if, mas é boa prática fazer isso pois… Diabos! Sempre use uma variável quando possível! Isso facilita a manutenção do código e evita o uso de números mágicos. Não banquem o Joãozinho.
Strings e tipos também podem ser comparados. Dê uma olhada na documentação da linguagem que estiver usando para ver a sintaxe correta. Em Javascript por exemplo, temos algo como Number(insiraAVariavelAqui) para ver se o conteúdo da variável é numérica. Isso pode ser muito útil para checar também conteúdo de classes ou validações. em algumas linguagens como o Javascript, o “numero de iguais” também altera a forma com que a expressão é avaliada.
Uma comparação verdadeira, não significa que a resposta vai ser true, quer dizer… significa mas…
peraí…
Vou mostrar sem if esse exemplo, mas a comparação vai existir da mesma forma:
//Um igual atribui um valor pra dentro da variável
var um = 1;
//Com dois iguais o valor é comparado. vai dar true
console.log(um == "1");
//Três iguais verificam o valor e o tipo. Vai dar false
console.log(um === "1");
//Exclamação nega o valor ou seja, inverte o seu sentido. Retorna false.
console.log(um !== 1);
//Porém como inverte, a exclamação valida uma expressão false, que retorna true.
console.log(um !== "1");
Perceba que mesmo sem usar o comando if, o console interpreta e compara os valores inseridos, provando que a comparação é um ato “orgânico” da computação. Por padrão a comparação é lógica(booleana(true e false)).
O comando if sempre aceita dois termos para comparação mas estes termos podem se dividir em mais dois termos se preciso, e estes dois em mais dois… E por aí em diante. Se tiver só um termo, cai na comparação lógica(booleana(você já entendeu né?…))
Além da igualdade e da negação, também temos os verificadores and(&&) e or(||) que são comparadores booleanos(lógicos(já perdeu a graça…)) e comparadores numéricos (>, <, <=, >=, por aí vai).
podem ser usados também como atalhos para verificações do tipo ternária(com três termos)
Exemplos:
Exemplo de múltiplos parâmetros
Obs.: Este código é meio inútil:
//Um igual atribui um valor pra dentro da variável
var um = 1;
//Com dois iguais o valor é comparado. vai dar true
console.log(um == "1");
//Três iguais verificam o valor e o tipo. Vai dar false
console.log(um === "1");
//Exclamação nega o valor ou seja, inverte o seu sentido. Retorna false.
console.log(um !== 1);
//Porém como inverte, a exclamação valida uma expressão false, que retorna true.
console.log(um !== "1");
Exemplo de operador ternário
Obs.: Este é útil pra caralho!!!
// Operadores ternários facilitam a escrita
var loginName = "JulyNee"
var estaLogado = true;
// Note que a comparação é "orgânica", sem a necessidade do comando if
// Perceba também que a comparação de um único item espera o retorno lógico(true ou false)
var resposta = (estaLogado) ? "Olá " + loginName + " !!!": "Efetue o Login.";
console.log(resposta);
Sintaxe: condição ? condição True : condiçãoFalse
Em casos onde o conjunto de opções a serem escolhidas são pré-determinadas e exclusivas, você pode usar a construção switch para melhor organização do código. Você também define uma resposta padrão caso nenhuma opção seja satisfeita.
Exemplo de switch/case
Cole o código no console, teste as opções e divirta-se 😉
// Obs.: O simbolo + faz a inferência para o tipo Number
// Obs 2 .: \n pula uma linha e a \ indica que o comando continua na linha de baixo
var escolha = +prompt("Escolha uma cor pelo numero:\n \
1- Azul\n \
2 - Verde \n \
3 - Amarelo \n \
4 - Laranja\n \
5 - Vermelho");
// o termo a ser comparado vai dentro do switch
switch(escolha) {
// O case é o comparador com o termo dentro do switch
case 1:
alert("%c Azul é a cor mais bonita!", "color:blue");
// O comando break interrompe a sequencia, sem ele, todos os cases são comparados
break;
case 2:
console.log("%c Verde é a cor da esperança!", "color:green");
break;
case 3:
console.log("%c Amarelo, uma bela cor que simboliza riqueza!", "color:yellow; background:navy");
break;
case 4:
console.log("%c Laranja. uma bela cor que representa criatividade!", "color:orange; background:navy");
break;
case 5:
console.log("%c Vermelho! A cor da nobreza e do poder!", "color:red");
break;
default:
console.log("Escolha uma opção válida.");
}
É igual ao if, não tem muito o que dizer, Observe a sintaxe e imagine casos onde seja interessante usá-lo 🙂
Basicamente, estas são as visões iniciais sobre o conceito de “se”. Bem técnica diga-se de passagem. Mas, no próximo artigo sobre o tema, será mostrada uma visão superior sobre o tema, pois as escolhas sempre permearão as ações de um engenheiro de software (Ai que bonito!)
Bom, se vocês leram o capítulo anterior, viram a estória (ou seria história ?) de Joãozinho, um dev super antenado, que passou por maus bocados devido a sua inaptidão para escolher nomes de variáveis. Antes de entrarmos no cerne da questão gostaria de pontuar algumas coisas: primeiramente, este artigo apesar de ser baseado em boas práticas, é um artigo de opinião, você pode discordar dele e até mesmo ter uma visão oposta sem ir contra o filosofia de fazer o certo. no final das contas o que importa é a sua facilidade em fazer o código. Segundo, parto do principio GNU de código “acadêmico”, ou seja, uma forma mais verbosa de se escrever o código. se você tem talento para pular estas etapas, vai fundo, mas aqui é um blog de principiantes e esta é a forma correta de se aprender. Por fim, vou desconsiderar pessoas que escrevem o código de forma confusa para garantirem seus empregos. Mudar nome de variável no meio do código é coisa de safado!!! (pronto fa – lei !)
MÃOS A OBRA!!!
Abaixo temos o trecho do código que fiz referente a função do piso logarítmico, resumindo o erro, a função recebe um argumento n e o transforma em x, sem nenhum motivo aparente. O pior é que esse numero n / x na verdade representa o logaritmando da função.
/* a função recebe um argumento n, sendo n > 1 e retorna o valor i satisfazendo
* a condição 2 ^ i <= n <= 2 ^ (i + 1) */
int pislog (int n) {
int x = n;
int i = 0, comp = 2;
/* i é o valor de piso correspondente a condição 2 ^ i <= n <= 2 ^ (i + 1). */
while (comp <= x) {
comp *= 2;
i++;
}
return i;
}
vemos também que eu, arbitrariamente chamei a variável de base de comp, simplesmente porque ela é o comparador do laço while mais abaixo. Já a letra i é famosa por ser utilizada como step-counter de laços (i vem de iterator, sacou ?) o que faz com que ela fique de fácil absorção para um dev, mas a sua real designação é o logaritmo.
Apesar do código funcionar muito bem, ele peca em um local que poucas pessoas observam, na sua ABSTRAÇÃO !
… ” Mas Marley, vc tar viajando! abstração é coisa de orientação a objetos e issu ae eh apnas estrutruras de dados. “
Sim, verdade, mas o conceito de abstração é justamente trazer algo do plano real (realidade do objeto) para o mundo virtual (seu código). No código que eu fiz acima, por mais que satisfaça a geração de um piso logarítmico, ele não representa tal realidade (um fake code, hue hue hue)
Veja como seria o mesmo código, absorvendo a realidade da construção de um logaritmo, ou seja, dando os nomes corretos aos bois:
/* a função recebe um argumento logaritmando, sendo logaritmando > 1 e retorna o valor
* logaritmoDePiso satisfazendo
* a condição 2 ^ logaritmoDePiso <= logaritmando <= 2 ^ (logaritmoDePiso + 1) */
int pisoLogaritmico (int logaritmando) {
int logaritmoDePiso = 0, base = 2;
/* logaritmoDePiso é o valor de piso correspondente a condição
* 2 ^ logaritmoDePiso <= n <= 2 ^ (logaritmoDePiso + 1). */
while (base <= logaritmando) {
base *= 2;
logaritmoDePiso++;
}
return logaritmoDePiso;
}
Sentiu?
Agora a leitura do código é quase uma leitura imperativa do que ele se propõe a fazer, apesar dele agora estar bem mais comprido do que antes (admito que foi um saco escrever logarimoDePiso) e a função não ter mais o simpático nome de pislog(), qualquer pessoa com conhecimento básico em álgebra ou programação sabe o que esse programa faz. Isso, senhoras e senhores é abstração !!!!
“Esse é meu garoto!!!”
CONSIDERAÇÔES
Admito que escrever deste tanto parece ser contraproducente no início, mas pense com sinceridade: qual dos dois códigos (que são exatamente o mesmo “objeto”/ abstração) será mais fácil de ler daqui a um mês? Sem falar que é muito mais fácil pesquisar sobre logaritmo de piso na internet, do que sobre “pislog”, ou seja, se alguém quiser questionar minha função ou criá-la de forma mais eficiente (refatorar), todas as informações já estão a sua disposição antes dele sequer pensar em me procurar.
Os códigos são como filhos: um dia você penteia eles e no outro eles estão saindo para mudar o mundo…
E sejamos francos: quantos códigos você criou e precisou revisitá-los ? Destes, quantos você não reconheceu o que fez ? Geralmente um programador gasta um esforço terrível para fazer algo, e assim que esteja pronto, você já não mexe mais nele (seja por medo ou desprezo), sendo assim, porque não solidificar o que já foi feito ao invés de procrastinar e mentir para si mesmo, dizendo que um dia vai visitá-lo. Por que somos assim e não assados?
…
… Tomar o remédio aqui…
… Voltei!
Outra consideração é o fato de, em algumas linguagens, sermos “obrigados” a colocar apenas uma ou duas letras como nomenclatura. E é aí que outro “pesadelo” de programadores surge como uma luz: a documentação ! (qqqqqqqq ?)
Sim você pode até usar x ou y como nome de variáveis desde que haja na documentação “escrita” uma correlação do que cada variável representa. Tipo:
n -> logaritmando
i -> logaritmo de piso
Rocky Balboa -> Sylvester Stalone
etc -> etecetera
Desta forma quem precisar ler a sua sopa de letrinhas, terá um documentação auxiliar para se guiar durante a tempestade de tretas conhecida como change(ou gmud).
Mais uma vez, eu sei que parece algo chato de se fazer, e é mesmo ! Mas ninguém volta a ter aula na 5 série só porque esqueceu como conjugar um verbo, você pega a minigramática e a lê (ou vai no Google), então porquê não criar a sua própria lexicografia (por Deus! porque eu não escrevi vocabulário?) e ajudar o seu eu do futuro?
CONSIDERAÇÔES FINAIS
Colocar nomes parecidos em variáveis interdependentes também é um erro crasso (sim estou falando com vocês Javeiros!) Tipo: uma classe tem a variável usuário e a sua subclasse tem a variável usu, sendo que esta tem na classe irmã usu_usuar e por aí vai … Evite fazer isso e ninguém vai se lembrar da sua progenitora enquanto lê o seu código.
Sabe, tenho uma conhecida que é famosa por escolher bons nomes para suas variáveis, e ao perguntar pra ela como faz isso, ela simplesmente me respondeu que apenas põe o nome da variável que acha simples, claro e correto.
No final das contas, a verdadeira genialidade está em manter as coisas simples.
No mais, espero ter ajudado você que leu este artigo, a programar melhor usando esses conceitos. Eu mesmo vou reescrever os códigos de alguns exercícios para que haja uma comparação clara entre eles !