Upgrade to Pro — share decks privately, control downloads, hide ads and more …

testando_no_mundo_elixir.pdf

Rafael Rocha
May 26, 2019
110

 testando_no_mundo_elixir.pdf

Rafael Rocha

May 26, 2019
Tweet

Transcript

  1. Rafael Rocha 4 Senior Software Engineer na The RealReal 4

    Desenvolvedor senior na Plataformatec 2015-2019 4 Engenheiro de software na LG Electronics 2010-2014 4 Mestre em Engenharia Elétrica pela UNICAMP
  2. Agenda - Especificações e desenvolvimento de software - Por que

    testar? - Conceitos base de teste - Tipos de testes - Pirâmide de testes - Claridade dos testes - Caso de Uso com **Elixir** - Usando uma abordagem de fora pra dentro - Refatorando com testes - dublês e fake clients - Doctests
  3. Quando começamos uma estória de usuário, lemos a descrição, os

    critérios de aceite e começamos a desenvolver.
  4. Por que testar? [x] Ter confiança sobre suas entregas [x]

    Ajuda a organizar os pensamentos [x] Manter os custos baixos
  5. Por que testar? [x] Ter confiança sobre suas entregas [x]

    Ajuda a organizar os pensamentos [x] Manter os custos baixos [x] Qualidade
  6. Aceitação: 4 Expressa um cenário 4 Ponta a ponta 4

    Mais próximo da camada de apresentação 4 Lentos 4 Garantem qualidade externa
  7. Unitários: 4 Testa o comportamento de uma entidade 4 Detectamos

    os erros mais rápido 4 Rápidos 4 Garantem qualidade interna 4 Mais fáceis de corrigir erros
  8. Greenbox É uma loja online que vende produtos de beleza

    orgânicos, onde os usuários podem escolher uma diferente variedade de produtos e montar sua própria caixinha. 4 O estoque muda os preço a cada 10 minutos, devido a umas promoções malucas.
  9. Critério de aceite: 4 id, nome do produto e preço

    devem ser pegos de tempo em tempo 4 O nome do produto deve ser capitalizado 4 O preço deve estar num formato de dolar, como: $12.50
  10. Tarefas: 1) Pegar os Produtos da API 2) Montar uma

    estrutura com id, nome capitalizado e preço 3) Montar uma interface para consumir os dados
  11. Qual é a camada mais externa do nosso sistema? []

    Pegar os Produtos da API [] Montar uma estrutura com id, nome capitalizado e preço [] Montar uma interface para consumir os dados
  12. Qual é a camada mais externa do nosso sistema? []

    Pegar os produtos da API [] Montar uma estrutura com id, nome capitalizado e preço [1] Montar uma interface para consumir os dados
  13. O que é um GenServer? "Um GenServer é um processo

    como qualquer outro processo no Elixir, e pode ser usado para manter o estado, executar código de forma assíncrona e assim por diante." -- Documentação do Elixir
  14. # [1] Montar uma interface para consumir os dados defmodule

    GreenBox.PriceUpdater do use GenServer def start_link do GenServer.start_link(__MODULE__, []) end def init(state) do {:ok, state} end
  15. # [1] Montar uma interface para consumir os dados def

    list_products(pid) do GenServer.call(pid, :list_products) end def handle_call(:list_products, _, state) do {:reply, state, state} end
  16. Qual é a camada mais externa do nosso sistema? [2]

    Pegar os produtos da API [] Montar uma estrutura com id, nome capitalizado e preço [1] Montar uma interface para consumir os dados
  17. # [2] *Pegar os produtos* da API defmodule GreenBox.PriceUpdater do

    use GenServer def start_link do GenServer.start_link(__MODULE__, []) end def init(_) do state = fetch_products() schedule_work() {:ok, state} end
  18. # [2] *Pegar os produtos* da API @doc """ Run

    the job and reschedule it to run again after some time. """ def handle_info(:get_products, _state) do products = fetch_products() schedule_work() {:noreply, products} end defp fetch_products do response = HTTPoison.get!("http://abcdpricing.com/products") Poison.decode!(response.body) end @time_to_consume 10000 * 60 # 10 minutes defp schedule_work do Process.send_after(self(), :get_products, @time_to_consume) end
  19. Qual é a camada mais externa do nosso sistema? [2]

    Pegar os produtos da API [3] Montar uma estrutura com id, nome capitalizado e preço [1] Montar uma interface para consumir os dados
  20. # [3] Montar uma *estrutura* com id, nome capitalizado e

    preço def init(_) do state = build_products() schedule_work() {:ok, state} end defp build_products do fetch_products() |> process_products() end
  21. # [3] Montar uma *estrutura* com id, nome capitalizado e

    preço defp fetch_products do response = HTTPoison.get!("http://abcdpricing.com/products") Poison.decode!(response.body) end defp process_products(products) do Enum.map(products, fn %{id: id, name: name, price: price} -> new_name = String.capitalize(name) new_price = "$#{price/100}" %{ id: id, name: new_name, price: new_price } end) end
  22. # Teste de integração defmodule Greenbox.ProductFetcherTest do use ExUnit.Case, async:

    true alias Greenbox.ProductFetcher # Specifications into code describe "Given a request to fetch a list of products" do test "builds a list of products with id, capitalized name and price in dollar" do products = ProductFetcher.build() assert [ %{id: "1234", name: "Blue ocean cream", price: _}, %{id: "1235", name: "Sea soap", price: _} ] = products end
  23. # Teste de integração test "builds a product with the

    price with a dollar sign" do product = ProductFetcher.build() |> List.first() # Expected format "$12.45" assert Regex.match?(~r(\$\d+\.\d+), product.price) end
  24. Product Fetcher - Uma nova entidade defmodule Greenbox.ProductFetcher do def

    build do fetch_products() |> process_products() end defp fetch_products do response = HTTPoison.get!("http://abcdpricing.com/products") Poison.decode!(response.body) end defp process_products(products) do Enum.map(products, fn %{id: id, name: name, price: price} -> %{ id: id, name: capitalize_name(name), price: price_to_money(price) } end) end
  25. Product Fetcher, está montando a estrutura do Produto... defp process_products(products)

    do Enum.map(products, fn %{id: id, name: name, price: price} -> %{ id: id, name: capitalize_name(name), price: price_to_money(price) } end) end
  26. # Escute seu código defp price_to_money(price) do "$#{price / 100}"

    end defp capitalize_name(name) do String.capitalize(name) end
  27. Construindo testes unitários para a estrutura do produto defmodule Greenbox.ProductTest

    do use ExUnit.Case, async: true alias Greenbox.Product describe "Given a product" do test "transforms its name by capitalizing it" do # Setup product_name = "BLUE SOAP" # Exercise capitalized_name = Product.capitalize_name(product_name) # Verify assert capitalized_name == "Blue soap" end
  28. # Construindo _**testes unitários**_ para a estrutura do produto test

    "transforms the price in cents to dollar" do # Setup product_price_in_cents = 1253 # Exercise product_price = Product.price_to_money(product_price_in_cents) # Verify assert product_price == "$12.53" end end end
  29. Produto defmodule Greenbox.Product do defstruct [:id, :name, :price] def price_to_money(price)

    do "$#{price / 100}" end def capitalize_name(name) do String.capitalize(name) end end
  30. Finalmente, construir um cliente para comunicar com a API externa

    defmodule Greenbox.ProductClient do def fetch_products do response = url() |> HTTPoison.get!() Poison.decode!(response.body) end defp url do Application.get_env(:greenbox, :abc_products_url) end end
  31. Chamada para API externa defmodule Greenbox.ProductClient do def fetch_products do

    response = url() |> HTTPoison.get!() Poison.decode!(response.body) end defp url do Application.get_env(:greenbox, :abc_products_url) end end
  32. Fake client # test/support/fake_client.ex defmodule Greenbox.FakeClient do def fetch_products do

    [ %{id: "1234", name: "BLUE OCEAN CREAM", price: Enum.random(8000..10000)}, %{id: "1235", name: "SEA SOAP", price: Enum.random(5000..60000)} ] end end
  33. Configurando o Fake Client defmodule Greenbox.MixProject do use Mix.Project def

    project do [ app: :greenbox, version: "0.1.0", elixir: "~> 1.7", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, deps: deps() ] end # Specifies which paths to compile per environment. defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(_), do: ["lib"] end
  34. Outras maneiras de stubar e mockar com Elixir 4 Bypass

    (https://github.com/PSPDFKit-labs/bypass) 4 Mox (https://github.com/plataformatec/mox)
  35. # Doctest defmodule Greenbox.Product do defstruct [:id, :name, :price] @doc

    """ Converts price in cents to a string money format. ## Example: iex> Greenbox.Product.price_to_money(1245) "$12.45" """ def price_to_money(price) do "$#{price / 100}" end
  36. 4 Escreva descrição de testes claras 4 Siga as especificações

    4 Pense de fora para dentro 4 Use a Pirâmide de Testes 4 Construa fake clients 4 Não teste os callbacks do GenServer 4 Abstraia o código em novos modulos