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

testando_no_mundo_elixir.pdf

D62feb60ae5130e4b03c0c39053fb270?s=47 Rafael Rocha
May 26, 2019
75

 testando_no_mundo_elixir.pdf

D62feb60ae5130e4b03c0c39053fb270?s=128

Rafael Rocha

May 26, 2019
Tweet

Transcript

  1. Testando no mundo Elixir rafaelrochasilva@gmail.com @RocRafael

  2. 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
  3. 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
  4. Quando começamos uma estória de usuário, lemos a descrição, os

    critérios de aceite e começamos a desenvolver.
  5. Mas... Estamos trazendo as especificações para o código?

  6. Quão confiante você está com sua entrega?

  7. Por que testar? [x] Ter confiança sobre suas entregas

  8. Por que testar? [x] Ter confiança sobre suas entregas [x]

    Ajuda a organizar os pensamentos
  9. Por que testar? [x] Ter confiança sobre suas entregas [x]

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

    Ajuda a organizar os pensamentos [x] Manter os custos baixos [x] Qualidade
  11. Quais são os tipos de testes?

  12. 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
  13. Integração: 4 Testes entre a aceitação e unitários 4 Teste

    entre duas ou mais entidades
  14. 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
  15. Pirâmide de testes

  16. Imagine um produto chamado Greenbox

  17. 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.
  18. Vamos praticar? Pegar os produtos do abcdpricing.com, para podermos mostrar

    o nome e o preço do produto.
  19. 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
  20. 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
  21. Vamos desenvolver de fora para dentro

  22. 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
  23. 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
  24. Para pegar os produtos de tempos em tempos, vamos usar

    GenServer
  25. 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
  26. # [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
  27. # [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
  28. 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
  29. # [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
  30. # [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
  31. 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
  32. # [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
  33. # [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
  34. Como testar o GenServer?

  35. Cuidado para não testar os callbacks do GenServer

  36. Mude o Design!

  37. Nova Arquitetura

  38. Vamos construir um teste de integração para guiar o desenvolvimento

  39. # 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
  40. # 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
  41. 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
  42. Nova Arquitetura

  43. 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
  44. # Escute seu código defp price_to_money(price) do "$#{price / 100}"

    end defp capitalize_name(name) do String.capitalize(name) end
  45. Nova Arquitetura

  46. 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
  47. # 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
  48. 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
  49. Nova Arquitetura

  50. 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
  51. Nova Arquitetura

  52. Vocês perceberam que estamos comunicando com a API todas as

    vezes que rodamos nossos testes?
  53. 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
  54. Dublês, como stubar com Elixir?

  55. Dublê SUT: System Under Test DOC: Collaborator Double: Is the

    object that substitutes the real DOC
  56. Vamos criar nosso dublê

  57. 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
  58. 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
  59. # config/config.exs config :greenbox, abc_products_client: Greenbox.ProductClient

  60. # config/test.exs config :greenbox, abc_products_client: Greenbox.FakeClient

  61. Outras maneiras de stubar e mockar com Elixir 4 Bypass

    (https://github.com/PSPDFKit-labs/bypass) 4 Mox (https://github.com/plataformatec/mox)
  62. E os Doctests? Eles podem substituir os testes?

  63. # 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
  64. Como os testes podem refletir as especificações e nos ajudar

    a sermos mais confiantes?
  65. 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
  66. Obrigado! https://github.com/rafaelrochasilva/greenbox http://blog.plataformatec.com.br/2018/11/starting- with-elixir-the-study-guide/

  67. References: https://github.com/plataformatec/mox https://github.com/PSPDFKit-labs/bypass https://github.com/keathley/wallaby