Espiral

Comece entendendo sistemas para internet

Neni

Introdução

Objetivo e escopo

A ideia do projeto nasceu de dois interesses:

Caso tenha interesse em contribuir, seja acrescentando/melhorando/removendo assuntos quanto corrigindo erros de português, acesse o projeto

Mantido com uma linguagem informal, o conteúdo a seguir pretende ensinar de maneira simples assuntos introdutórios para entrar na área de desenvolvimento web. Abordaremos:

Os assuntos serão pouco aprofundados, a proposta desse material é explicar o essencial para que você (leitor) estude tópicos mais avançados por conta própria ou saiba reconhecer cursos/livros interessantes. Não prometo que ao final da leitura você estará apto para a primeira vaga, pois acredito que o ideal é desenvolver e expor projetos pequenos no Github e Linkedin.

Após dominar os assuntos presentes nesse material, recomendo o estudo de:

Dicas iniciais

FAQ

  1. Preciso fazer faculdade?

Não, algumas empresas maiores podem exigir, porém é muito fácil de encontrar vagas que não se importam com isso. Vale mais a pena focar em conseguir a primeira oportunidade e depois cogitar ir para uma faculdade.

  1. Preciso estudar inglês?

Não, mas certamente ajudaria para pesquisar mais tópicos e conteúdos mais especializados.

  1. Como devo ler o material?

Recomendo ler 3 vezes: 1) uma primeira leitura rápida para alinhar a expectativa do que será abordado, 2) mais calma fazendo anotações e praticando quando fizer sentido e 3) rápida para entender os conceitos abordados e praticados.

  1. Como deve ser o computador?

Acredito que no mínimo um processador i3 e 4gb de RAM.

  1. Qual editor de texto usar?

Visual Studio Code, não confundir com Visual Studio. Vai ser a ferramenta que usaremos para escrever nos arquivos.

O que é a web

O desenvolvimento web é uma área específica dentro da área de desenvolvimento de software (programa). De maneira simplificada, a web é o acesso a sites/sistemas online utilizando o protocolo HTTP, onde que é feita uma requisição a um servidor e é retornada uma resposta (podendo ser em HTML, JSON ou outros formatos).

Fluxo de web

Se tratando de desenvolvimento web existem duas ramificações:

HTML + CSS + Javascript

Uma página web normalmente vai ter um conteúdo, estilização e interação (respectivamente: HTML, CSS e Javascript). O navegador/browser lê os arquivos recebidos do back-end (.html, .css e .js) e renderiza de acordo. Porém com ctrlshifti podemos ver como eles realmente são: texto!

HTML

Propósito

Imagine o seuginte cenário: O navegador recebe um arquivo contendo todo o conteúdo do site, como ele sabe o que é título, link, lista e etc? Afinal, é uma informação necessária para delimitar a informação, exemplo:

Lista de compras
Mercado
Farinha
Leite
Ovos

Pode ser lido como

Lista de compras <- titulo
Mercado          <- item da lista
Farinha          <- item da lista
Leite            <- item da lista
Ovos             <- item da lista

ou

Lista de compras <- titulo
Mercado          <- subtitulo
Farinha          <- item da lista
Leite            <- item da lista
Ovos             <- item da lista

Tags

Para isso existem as tags, cujo explicitam a intenção de um grupo de texto. O exemplo anterior ficaria da seguinte forma:

<h1>Lista de compras</h1>
<h2>Mercado</h2>
<ul>
<li>Farinha</li>
<li>Leite</li>
<li>Ovos</li>
</ul>

Tags abrem, envolvem e fecham da seguinte maneira: <nome>conteudo</nome>

Sua vez: Crie um arquivo chamado teste.html, e abra de duas maneiras diferentes: 1) com seu editor de texto e 2) com o navegador. Escreva a lista de compras pelo editor de texto e visualize as alterações no navegador! Lembre de atualizar a página do navegador com F5 a cada mudança.

Todas tags podem possuir classes e um id. Esses artíficios não são visuais, eles servem para registrar de alguma forma essa tag para poder ser utilizada mais facilmente pelo CSS e Javascript.

<h1 class="titulo">Lista de compras</h1>
<h2 id="subtitulo">Mercado</h2>
<ul>
<li class="item-novo importante">Farinha</li>
<li class="">Leite</li>
<li class="importante">Ovos</li>
</ul>

CSS

Não basta especificar um título, link etc, precisamos estilizá-lo também. Tamanho da fonte, cor, alinhamento e outras propriedades são configuráveis.

Essas estilizações são feitas com um arquivo .css, para mudar a cor do título podemos fazer o seguinte:

h1 {
    color: blue;
}

Especificamos com um seletor e em seguida atribuimos alguma de suas propriedades. Os seletores mais comuns são: nome da tag, classe ou id.

.titulo { /* classe */
    color: blue;
}
#subtitulo { /* id */
    color: blue;
}

Para utilizar as definições feitas pelo CSS precisamos importar elas no nosso HTML precisamos alterá-lo com:

 <head>
  <link rel="stylesheet" href="./nome-do-arquivo.css">
</head> 

Javascript

Com conteúdo estruturado e estilizado falta adicionar interação ao site.

Algoritmos com Javascript

Git e Github

Fluxo de trabalho comum

Introdução ao desenvolvimento web com React

Introdução ao desenvolvimento web com Laravel

Criaremos um sistema de agendamento chamado Reiserva onde que:

Trabalharemos com TDD, primeiro criamos um teste falho, fazemos ele passar, melhoramos e repetimos. O foco será sempre utilizar o teste, não necessariamente validar em tela algum fluxo, mas sinta-se livre para fazê-lo.

Algumas vezes nos testes utilizaremos os seguintes termos para comentar a intenção estrutural do bloco de código dentro do teste:

Qual usuário? Qual contexto do banco?

Qual o método que vai ser chamado?

Quais as consequências? Elas correspondem ao esperado? Valor persistido? Evento emitido? E-mail enviado?

Syntax php

Breve explicação da syntax do PHP com o arquivo final da primeira versão de uma classe que criaremos.

<?php // tag obrigatória para escrever script php
 
/*
 * Pacote/"Sobrenome" do arquivo, normalmente segue o padrão de pasta.
 * Arquivos com App\Services está na pasta app/services
 */
namespace App\Services;

// Importação de outras classes através de seu Namespace + nome da classe
use App\Models\Ambiente;
use App\Models\Reserva;
use App\Models\User;

/* 
 * Classe é como uma "gaveta" cujo possui:
 * propriedades: semelhantes a variáveis
 * métodos: semelhantes a funções
 */
class ReservaService
{
    // método
    public function criar(Ambiente $ambiente, User $usuario)
    {
        // variável com $
        // propriedade acessada através de ->
        $reserva = new Reserva();
        $reserva->id_ambiente = $ambiente->id;
        $reserva->id_usuario = $usuario->id;
        $reserva->save();
    }
}

Organização proposta

Mesmo utilizando laravel, cujo é um framework que estipula padrões para nós, podemos complementá-lo com nossas próprias definições, cujo serão:

Cada um desses pontos vai ser abordado e aprofundado no decorrer do desenvolvimento do projeto.

Configuração inicial

Criação do projeto

Acesse a pasta onde guarda seus projetos, abra um terminal e execute

composer create-project laravel/laravel reiserva

Criando a reserva simplificada

Vamos validar que dado um usuário (User) e um ambiente, podemos criar uma reserva através da classe ReservaService.

  1. Crie o arquivo que vai possuir os testes com php artisan make:test ReservaServiceTest no terminal dentro do projeto

Esse comando (scaffold) é uma facilidade do Laravel, cujo vai criar o arquivo de teste no local correto. Durante o andamento do projeto utilizaremos outros semelhantes a esse

  1. Crie o teste testReservaAmbiente como abaixo

A nomenclatura de testes utilizada é a do Spotify

SUT significa System Under Test

<?php

namespace Tests\Unit;

use App\Models\User;
use App\Models\Ambiente;
use App\Models\Reserva;
use App\Services\ReservaService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ReservaServiceTest extends TestCase
{
    use RefreshDatabase;
    
    public function testReservaAmbiente(): void
    {
        // Arrange: usuário existente
        $usuario = new User([
            'name' => 'Fulano de tal',
            'email' =>'fulano@tal.com',
            'password'=> '123',
        ]);
        $usuario->save();

        // Arrange: ambiente existente
        $ambiente = new Ambiente();
        $ambiente->nome = 'Salão de festas';
        $ambiente->save();

        // Act: criação da reserva
        $sut = new ReservaService();
        $sut->criar($ambiente, $usuario);

        // Assert: existência da reserva no banco de dados
        $this->assertDatabaseHas(Reserva::class, [
            'id_ambiente'=> $ambiente->id,
            'id_usuario'=> $usuario->id
        ]);
    }
}
  1. Execute o teste com php artisan test --filter testReservaAmbiente e perceba que falhou por faltar algumas dessas classes
FAILED  Tests\Unit\ReservaServiceTest > reserva ambiente
Class "Tests\Unit\User" not found
  1. Crie as classes que faltam:
<?php

namespace App\Services;

class ReservaService
{
    public function criar()
    {
        // ...
    }
}
  1. Reexecute o teste (php artisan test --filter testReservaAmbiente) e perceba que o erro mudou. Agora ele está falhando pois não sabe persistir (->save()) o ambiente (User é uma entidade existente no laravel). Precisamos criar as tabelas nos bancos, porém não de qualquer forma, mas sim com migrations.
FAILED  Tests\Unit\ReservaServiceTest > reserva ambiente   
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'testing.ambientes' doesn't exist (Connection: mysql, SQL: insert into `ambientes` (`nome`, `updated_at`, `created_at`) values (Salão de festas, 2023-06-22 07:07:23, 2023-06-22 07:07:23))

Migration é um script que é lido e possui o proposito de alterar a estrutura do banco de dados, seja criando tabelas ou até modificando-as (renomeando campos, mudando seus tipos etc)

  1. Crie a migration com php artisan make:migration cria_ambiente --create=ambientes e acesse o arquivo criado dentro de database/migrations. Adicione nas propriedades o nome do ambiente.
<?php
// ...
        Schema::create('ambientes', function (Blueprint $table) {
            $table->id();
            $table->string('nome');
            $table->timestamps();
        });
// ...
  1. Reexecute o teste (php artisan test --filter testReservaAmbiente) e perceba que ocorreu o mesmo com a tabela de reservas. Crie uma nova migration com php artisan make:migration cria_ambiente --create=reservas
<?php
// ...
        Schema::create('reservas', function (Blueprint $table) {
            $table->id();
            $table->foreignId('id_usuario')->constrained('users');
            $table->foreignId('id_ambiente')->constrained('ambientes');
            $table->timestamps();
        });
// ...
  1. Reexecute o teste e o erro muda, agora ele está falhando pois não foi encontrada uma reserva persistida com o id do usuário e do ambiente
FAILED  Tests\Unit\ReservaServiceTest > reserva ambiente                                  
Failed asserting that a row in the table [reservas] matches the attributes {
  "id_ambiente": 1,
  "id_usuario": 1
}.
  1. Vamos finalizar a funcionalidade persistindo a Reserva no Service, reexecutar o teste e validar o sucesso
<?php

namespace App\Services;

use App\Models\Ambiente;
use App\Models\Reserva;
use App\Models\User;

class ReservaService
{
    public function criar(Ambiente $ambiente, User $usuario)
    {
        $reserva = new Reserva();
        $reserva->id_ambiente = $ambiente->id;
        $reserva->id_usuario = $usuario->id;
        $reserva->save();
    }
}

Refatoração: Factories

Testes devem ser o mais manuteniveis o possível. E para esse fim, uma abordagem interessante é deixá-los o mais simples/objetivos. Ao invés de criar o contexto do banco com save, utilizaremos um recurso do laravel chamado Factory

  1. Por padrão já exise uma Factory para User (UserFactory), portanto é só utilizá-la
-        $usuario = new User([
-            'name' => 'Fulano de tal',
-            'email' =>'fulano@tal.com',
-            'password'=> '123',
-        ]);
-        $usuario->save();
+        $usuario = User::factory()->create();
  1. Crie uma factory para model de ambiente com sail artisan make:factory AmbienteFactory
  2. Defina as propriedades do ambiente no retorno de definition. É interessante utilizar dados aleatorios com fake, para não enviesar nosso teste com sempre os mesmos parametros
<?php

// ...
    public function definition(): array
    {
        return [
            'nome' => fake()->streetName() // na falta de um nome aleatrio para ambientes
        ];
    }
// ...
  1. Atualize o teste novamente
-        $ambiente = new Ambiente();
-        $ambiente->nome = 'Salão de festas';
-        $ambiente->save();
+        $ambiente = Ambiente::factory()->create();

O teste deve continuar passando com php artisan test --filter testReservaAmbiente

Refatoração: simplificando a parametrização da Service

Existem vários métodos que dizem respeito a regras de negócio que podemos colocar na Service, e a grande maioria vai envolver usuário, ambiente e a própria reserva. Portanto não faz muito sentido cada um sempre receber alguns desses como parametros, seria um código repetitivo e, pior ainda, extenso. Muitos parametros numa função dificultam seu entendimento e utilização, e uma abordagem para isso são métodos auxiliares para criar um estado na service.

  1. Crie as propriedades readonly $usuario, $ambiente e $reserva métodos publicos setUsuario, setAmbiente e getReserva na ReservaService
<?php

// ...

class ReservaService
{
    public readonly User $usuario;
    public readonly Ambiente $ambiente;
    public readonly Reserva $reserva;

    public function setUsuario(User $usuario): self
    {
        $this->usuario = $usuario;
        return $this;
    }

    public function setAmbiente(Ambiente $ambiente): self
    {
        $this->ambiente = $ambiente;
        return $this;
    }

    public function getReserva(): Reserva
    {
        return $this->reserva;
    }
// ...
  1. Atualize o método criar para deixar de receber esses parametros
-    public function criar(Ambiente $ambiente, User $usuario)
+    public function criar(): self
  1. Atualize o teste, pois a assinatura do método além de ter mudado agora precisamos parametrizar usuario e ambiente de maneira diferente
-        $sut->criar($ambiente, $usuario);
+        $sut->setUsuario($usuario);
+        $sut->setAmbiente($ambiente);
+        $sut->criar();
  1. Aproveitando o ensejo, vamos deixar de repetir a utilização de $sut, pois os métodos são fluentes e podemos encadear as chamadas já que retornam self (o próprio objeto de ReservaService)
<?php

// ...
        // Act: criação da reserva
        $sut = new ReservaService();
        $sut->setUsuario($usuario)
            ->setAmbiente($ambiente)
            ->criar();
// ...

O teste deve continuar passando com php artisan test --filter testReservaAmbiente

Refatoração: utilizando injeção de dependência

Vamos utilizar injeção de dependência para não precisar instanciar a ReservaService no teste

<?php

// ...
        // Act: criação da reserva
        app(ReservaService::class)
            ->setUsuario($usuario)
            ->setAmbiente($ambiente)
            ->criar();
// ...

Com app() o Laravel resolve a dependência e nos retorna uma instancia de ReservaService

O teste deve continuar passando com php artisan test --filter testReservaAmbiente

Restringindo horário de reserva

Vamos fazer algumas validações com as datas:

  1. Atualize o teste existente (testCriaReserva) exigindo horário
<?php
// ...
use Illuminate\Support\Carbon;
// ...
class ReservaServiceTest extends TestCase
{
    use RefreshDatabase;
    
    public function testReservaAmbiente(): void
    {
        // Arrange: usuário existente
        $usuario = User::factory()->create();

        // Arrange: ambiente existente
        $ambiente = Ambiente::factory()->create();

        // Act: criação da reserva
        $dataInicio = Carbon::create(2023, 2, 22, 5, 10, 10);   // 22/02/2023
        $dataFim = Carbon::create(2023, 2, 23, 5, 10, 10);      // 23/02/2023
        app(ReservaService::class)
            ->setUsuario($usuario)
            ->setAmbiente($ambiente)
            ->criar($dataInicio, $dataFim);

        // Assert: existência da reserva no banco de dados
        $this->assertDatabaseHas(Reserva::class, [
            'id_ambiente'=> $ambiente->id,
            'id_usuario'=> $usuario->id,
            'data_inicio'=>$dataInicio->toDatetimeString(),
            'data_fim'=>$dataFim->toDateTimeString(),
        ]);
    }
}
  1. Deve falhar por faltar as colunas no banco, no momento da persistencia
FAILED  Tests\Feature\ReservaServiceTest > reserva ambiente                         QueryException   
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'data_inicio' in 'where clause' (Connection: mysql, SQL: select count(*) as aggregate from `reservas` where (`id_ambiente` = 1 and `id_usuario` = 1 and `data_inicio` = 2023-02-22 05:10:10 and `data_fim` = 2023-02-23 05:10:10))
  1. Como já vimos, bora criar a migration com nosso scaffold php artisan make:migration add_inicio_e_fim_na_reserva --table=reservas

Lembre que podemos sempre perguntar para o próprio cli como podemos utilizá-lo. Com php artisan make:migration --help posso ver que aceita --create (ja usamos para criar tabelas) ou --table para alterar uma tabela existente (nosso caso)

  1. Adicione a criação das colunas na tabela de reservas
<?php

// ...
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('reservas', function (Blueprint $table) {
            $table->timestamp('data_inicio');
            $table->timestamp('data_fim');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('reservas', function (Blueprint $table) {
            $table->dropColumn('data_inicio');
            $table->dropColumn('data_fim');
        });
    }
// ...
  1. Atualize a assinatura do método esperando os parametros de data e teste novamente
<?php
// ...
use Carbon\CarbonInterface;
// ...
    public function criar(CarbonInterface $dataInicio, CarbonInterface $dataFim): self
    {
        $reserva = new Reserva();
        $reserva->id_ambiente = $this->ambiente->id;
        $reserva->id_usuario = $this->usuario->id;
        $reserva->data_inicio = $dataInicio;
        $reserva->data_fim = $dataFim;

        $reserva->save();
        return $this;
    }
// ...
  1. Rodando os testes novamente vai ter outro erro, pois agora a data inicio e fim são obrigatórias e não estamos salvando na nossa service

  2. Adicione um teste validando uma exception quando a data final sendo maior que a inicial

<?php
// ...
    public function testNaoReservaComDataInicialMaiorQueFinal(): void
    {
        $usuario = User::factory()->create();
        $ambiente = Ambiente::factory()->create();

        $dataInicio = Carbon::create(2023, 2, 23, 5, 10, 10);   // 23/02/2023
        $dataFim = Carbon::create(2023, 2, 22, 5, 10, 10);      // 22/02/2023

        $this->expectException(\App\Exceptions\DatasImpossiveisException::class);
        app(ReservaService::class)
            ->setUsuario($usuario)
            ->setAmbiente($ambiente)
            ->criar($dataInicio, $dataFim);
    }
// ...
  1. Testando com php artisan test --fitler testNaoReservaComDataInicialMaiorQueFinal vai falhar pois não estamos fazendo a validação. Crie a condicional no método da service
<?php
// ...
use App\Exceptions\DatasImpossiveisException;
// ...
    public function criar(CarbonInterface $dataInicio, CarbonInterface $dataFim): self
    {
        // valida se $dataInicio é maior ou igual (Greather Than Equal)
        // Se sim: erro
        // se não: prossegue normalmente
        if ($dataInicio->gte($dataFim)) {
            throw new DatasImpossiveisException();
        }
// ...
  1. Crie a exception com o scaffold sail artisan make:exception DatasImpossiveisException
  2. Reexecute o teste e veja-o passar com sucesso
  3. Vamos criar o último teste: Data inicio deve ser sempre futura
<?php
// ...
    public function testImpedeDataInicioMenorQueAgora(): void
    {
        $usuario = User::factory()->create();
        $ambiente = Ambiente::factory()->create();

        $dataInicio = now()->yesterday();
        $dataFim = now()->tomorrow();

        $this->expectException(\App\Exceptions\DatasImpossiveisException::class);
        app(ReservaService::class)
            ->setUsuario($usuario)
            ->setAmbiente($ambiente)
            ->criar($dataInicio, $dataFim);
    }
// ...
  1. Execute o teste com php artisan test --filter testImpedeDataInicioMenorQueAgora e confirme a falha por não haver sido lançada a exception. Novamente: crie a exception sail artisan make:exception DataInicioPassadaException e a coloque no teste com a condicional adequada
<?php
// ...
use App\Exceptions\DatasImpossiveisException;
// ...
    public function criar(CarbonInterface $dataInicio, CarbonInterface $dataFim): self
    {
        if ($dataInicio->gte($dataFim)) {
            throw new DatasImpossiveisException();
        }
        
        if ($dataInicio->isPast()) {
            throw new DataInicioPassadaException();
        }
// ...
  1. Perceba que se executar o teste com php artisan test --filter testImpedeDataInicioMenorQueAgora passa, porém com php artisan test --filter ReservaServiceTest o teste testReservaAmbiente falha. E digo mais: por pouco o testNaoReservaComDataInicialMaiorQueFinal também não falha, isso ocorre pois sua condicional é logo a primeira do teste. E por que falharam/falhariam? Pois estamos utilizando datas fixas, e as colocadas anteriormente ja passaram! Temos 3 opções: 1) Configurar elas para bem no futuro (ano de 2099) 2) utilizar um helper Carbon::setTestNow no inicio de cada teste para configurar quando é o “hoje” do teste ou 3) utilizar helper do laravel mantendo a operação com datas relativas. Utilizaremos esse ultimo
  2. Em testReservaAmbiente
-       $dataInicio = Carbon::create(2023, 2, 22, 5, 10, 10);
-       $dataFim = Carbon::create(2023, 2, 23, 5, 10, 10);
+       $dataInicio = now()->tomorrow();
+       $dataFim = now()->tomorrow()->addHour();
  1. Em testReservaAmbiente
-       $dataInicio = Carbon::create(2023, 2, 22, 5, 10, 10);
-       $dataFim = Carbon::create(2023, 2, 23, 5, 10, 10);
+       $dataInicio = now()->tomorrow()->addHours(2);
+       $dataFim = now()->tomorrow()->addHour();

Todos testes devem passar com php artisan test --filter ReservaServiceTest

Introdução à SQL