Testing approaches in Elixir

Oleksandr Rozumii, Toptal

Testing approaches in Elixir

Oleksandr Rozumii
github.com/brain-geek
Kyiv Elixir meetup 2, 2016

Who am I?

  1. Doing some projects with Elixir for almost a year now
  2. Kyiv Elixir meetup organizer :)
  3. Elixir School contributor/translator
  4. Ruby (lead) developer for >5 years

Who thinks that testing is important?

Who runs tests?

Who knows what does those tests cover?

Who understands that even 9001% coverage does not mean you don't have bugs?

What is testing?

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.

What is testing?

Software testing is an investigation conducted to provide stakeholders with information about the quality of the product or service under test.

What is testing automation?

Test automation is the use of special software to control the execution of tests and the comparison of actual outcomes with predicted outcomes.

What is testing from the practical approach?

It's about checking that things work when needed…

…and that they do not work when it should not!

That's also named 'positive' and 'negative' testing.

Testing levels

  1. Unit
  2. Integration
  3. System

Less theory!

Let's test a model

      defmodule Example.MatchSubscriptionTest do
        @valid_attrs %{user_id: 42, msg_id: 4323, disabled: false}
        test "changeset with valid attributes" do
          changeset = MatchSubscription.changeset(
      %MatchSubscription{}, @valid_attrs)
          assert changeset.valid?
        end
      end
    

Let's test a model

      defmodule Example.EventSpec do
        let :changeset, do: Event.changeset(%Event{}, attrs)
        subject do: changeset.valid?
        context "changeset with valid attributes" do
          let :attrs, do: %{"user_id" => 42, "msg_id" => 4323, 
      "disabled" => false}
          it "is valid" do
            is_expected.to be_truthy
          end
        end
      end
    

ExUnit

      defmodule ExampleTest do
        use ExUnit.Case
        doctest Example
        test "the truth" do
          assert 1 + 1 == 2
        end
      end
    

Async

      defmodule AssertionTest do
        use ExUnit.Case, async: true
        test "the truth" do
          assert true
        end
      end
    

Callbacks

      setup_all do
        IO.puts "Starting AssertionTest"
        :ok
      end
    

Callbacks

      setup do
        IO.puts "This is a setup callback"
        on_exit fn ->
        IO.puts "This is invoked once the test is done"
        end
        [hello: "world"]
      end
    

Assert(s)

assert false, "it will never be true"
refute true, "This will obviously fail"
assert_in_delta 1.1, 1.5, 0.2
flunk "This should raise an error"

ExUnit: Pros

  1. Simple
  2. Ships with language

ExUnit: Cons

  1. It tends to grow
  2. Lots of copy-paste (not really)
  3. No mocks

ESpec

      defmodule SomeSpec do
        use ESpec
        context "Some context" do
          it do: expect "abc" |> to(match ~r/b/)
        end
      end
    

Let/Subject/Shared data

      defmodule SomeSpec do
        use ESpec
        before do: {:shared, a: 1}
        let! :a, do: shared.a
        let :b, do: shared.a + 1
        it do: expect a |> to(eq 1)
        it do: expect b |> to(eq 2)
      end
    

Contexts

      describe "#prepare_event_text" do
        subject do: described_module.prepare_event_text(event)

        context "red team won" do
          context "within 5 minutes from start"
    

Callbacks

before do: LiveMatchesStore.set_live_games_info prev_state
config.finally fn(shared) -> IO.puts shared.answer  end

Expectation syntax

RSpec syntax with expect helper: expect(smth1).to eq(smth2) or is_expected.to eq(smth) when subject is defined;

expect syntax with pipe operator expect smth1 |> to(eq smth2) or is_expected |> to(eq smth) when subject is defined;

should syntax: smth1 |> should(eq smth2) or should eq smth when subject is defined.

Yeah, it's copied from README. ;)

Matchers

expect collection |> to(be_empty)
expect list |> to(have_first value)
expect string |> to(have_first value)
expect map |> to(have_key value)
expect function |> to(throw_term)

Mocks

      defmodule SomeSpec do
        use ESpec
        before do: allow SomeModule |> to(accept :func)
        it do: expect SomeModule.func |> to(be_nil)
        it do: expect SomeModule.func(42) |> to(be_nil)
      end
    

ESpec: Pros

  1. Has a lot of features
  2. Can be used to have complex nested tests
  3. You can annoy ask the author easily

ESpec: Cons

  1. Limited amount of users
  2. Lot of features

Elixir stuff

Faker

  1. Faker::Name.name #=> "Tyshawn Johns Sr."
  2. Faker::Avatar.image #=> "https://robohash.org/sitsequiquia.png?size=300x300"
  3. Faker::Number.number(10) #=> "1968353479"
  4. Faker::Commerce.department #=> "Grocery, Health & Beauty"

Factory

      defmodule Factory do
        def create(:company) do
          ...
        end

        def create(:city) do
          ...
        end
      end
    

Macroses

Thank you!
Questions?

Fork me on GitHub