Do you want to pick up from where you left of?
Take me there

Testing

Testing er en viktig del i det å utvikle programvare. I denne seksjonen så vil vi se på hvordan vi kan teste vår Elixir kode med ExUnit og best praksiser for hvordan det skal gjøres.

ExUnit

Elixirs innebygde test rammeverk er ExUnit og inkluderer alt vi trenger for å kunne teste koden vår. Før vi beveger oss videre er det viktig å merke seg at tester i Elixir er implementert som skripts slik at vi må bruke .exs filendelsen. Før vi kan kjøre testene våre så må vi starte ExUnit med ExUnit.start(), dette er som oftest gjort i test/test_helper.exs

Når vi lager våre eksempel prosjekter, så vil mix alltid lage en veldig enkel test for oss, vi finner den under test/example_test.exs:

defmodule ExampleTest do
  use ExUnit.Case
  doctest Example

  test "the truth" do
    assert 1 + 1 == 2
  end
end

Vi kan eksekvere testene i prosjektet vårt med mix test. Hvis vi gjør det nå, så vil vi se noe slikt:

Finished in 0.03 seconds (0.02s on load, 0.01s on tests)
1 tests, 0 failures

assert

Hvis du har skrevet tester før så er du kjent med assert; i noen rammeverk har du kanskje sett det som should eller expect som fyller rollen til assert.

Når vi bruker assert makroen så tester vi at uttrykket er sant. I tilfeller hvor det ikke er sant, så vil en feil bli hevet og vår test vil ikke lykkes. For å teste dette så må vi endre eksempelet og kjøre mix test:

defmodule ExampleTest do
  use ExUnit.Case
  doctest Example

  test "the truth" do
    assert 1 + 1 == 3
  end
end

Som vil gi oss en annen type beskjed:

  1) test the truth (ExampleTest)
     test/example_test.exs:5
     Assertion with == failed
     code: 1 + 1 == 3
     lhs:  2
     rhs:  3
     stacktrace:
       test/example_test.exs:6

......

Finished in 0.03 seconds (0.02s on load, 0.01s on tests)
1 tests, 1 failures

ExUnit vil fortelle oss eksakt hvor vår antakelse feilet, hva den forventet verdien var, og hva den faktiske verdien var.

refute

refute er for assert hva unless er for if. Bruk refute når du trenger å forsikre deg om at et uttrykk alltid er usant.

assert_raise

Noen ganger så kan det være nødvendig å anta at en feil skal bli hevet. Vi kan gjøre dette med assert_raise. Vi vil se et eksempel på assert_raise i neste seksjon om Plug.

assert_received

Elixir applikasjoner består av aktorer/prosesser som sender meldinger til hverandre, derfor så vil vi gjerne sjekke meldingen som blir sendt. Siden ExUnit kjører i sin egen prosess så kan den motta meldinger akkurat som alle andre prosesser og du kan bruke assert på disse meldingen med assert_received makroen:

defmodule SendingProcess do
  def run(pid) do
    send(pid, :ping)
  end
end

defmodule TestReceive do
  use ExUnit.Case

  test "receives ping" do
    SendingProcess.run(self())
    assert_received :ping
  end
end

assert_received venter ikke på meldinger, men med assert_receive så kan du spesifisere en timeout.

capture_io og capture_log

Det er mulig å få tak i en applikasjons output med ExUnit.CaptureIO uten å må endre på original applikasjonen. Du trenger bare å gi en funksjon med det du ønsker å skrive ut:

defmodule OutputTest do
  use ExUnit.Case
  import ExUnit.CaptureIO

  test "outputs Hello World" do
    assert capture_io(fn -> IO.puts("Hello World") end) == "Hello World\n"
  end
end

ExUnit.CaptureLog er ekvivalent med det å sende output til Logger.

Test Setup

I noen instanser så kan det være nødvendig å utføre operasjoner på forhånd før man kjører testene. For å oppnå dete så kan vi bruke setup og setup_all makroene. setup vil kjøre før hver eneste test og setup_all kun en gang før alle testene blir kjørt. Det er forventet at både setup og setup_all vil returnere et tuple i formen av: {:ok, state}, hvor state vil være tilgjengelig i testene våre.

Kun som et eksempel, så vil vi endre koden vår til å bruke setup_all:

defmodule ExampleTest do
  use ExUnit.Case
  doctest Example

  setup_all do
    {:ok, number: 2}
  end

  test "the truth", state do
    assert 1 + 1 == state[:number]
  end
end

Mocking

Det enkle svaret når det gjelder bruk av mocking i Elixir er: Ikke gjør det. Du kan fort ty til mocker, men det er ikke anbefalt og det for gode grunner.

For en lengre diskusjon på dette: Flott artikkel. Tanken er at, istedenfor at man mocker vekk avhengigheter for testing (mock som et verb), så er det mange fordeler å eksplisit definere grensesnitt (oppførseler) for kode utenfor din applikasjon, og heller bruke Mock (som et substantiv) implementasjoner i din klient kode for testingen.

For å endre implementasjon av din applikasjon kode, så er den foretrukne måten og heller gi modulen som et argument og bruke en standard verdi. Hvis det ikke fungerer, så kan du bruke den innebygde konfigurasjons mekansimen. For å lage disse mock implementasjonene, så trenger du ikke et spesielt mocking bibliotek, kun oppførseler og tilbakekallende funksjoner.

Har du oppdaget en feil eller ønsker å bidra til leksjonen? Rediger denne leksjonen på GitHub!