5 min read

Testing Python Dengan Behave

Hal penting tapi sering terlewat dalam proses dev tidak lain tidak bukan adalah implementasi testing, saya bukan orang yang rajin testing juga sih, bahkan lebih sering mengandalkan user, jika user oke maka yaudah deploy, hhe suram memang. Tapi semua berubah saat mengenal behave.
Saya tahu behave pertama kali dari soal technical interview, dan saat diimplementasi sungguh nyaman dan sepertinya mempermudah dev juga karena bisa berbagi tugas dengan PM/User. Kenapa bisa begitu? Ini alasannya
Instalasi

Untuk pemasangan sangat mudah, kita bisa gunakan pip saja

pip install behave

Lalu kita buat struktur folder seperti berikut, dan pastikan terdapat folder “features” dan folder “steps” di dalamnya,

├── features
│ ├── first.feature
│ └── steps
│ └── first.py
├── __init__.py
├── simplecalc.py
Feature file
Feature file ini tempat di mana kita menuliskan skenario sebuah fitur, sesuai namanya dan nama paketnya yaitu behave (dari behaviour) atau perilaku, maka di feature file ini dituliskan perilaku yang akan dilakukan, dan menariknya yang ditulis di sini bukan kode, bukan pseudo code juga, tapi mirip bahasa manusia pada umumnya
Contoh penulisan features yang saya isi di dalam”first.feature” seperti berikut:
Feature: Simple calculator

  Scenario: Run simple addition
     Given we have simple calculator
      When we add 2 and 2
      Then we got 4
Sederhana bukan? bahkan kita jadi bisa meminta bantuan kepada PM atau user untuk membantu menuliskan skenario yang diinginkan. Bahasa di atas mirip bahasa inggris tapi disebut Gherkin Language jika konteksnya di behave ini ya…
Keywords
Walaupun terlihat sederhana, ada beberapa keyword di behave yang perlu diperhatikan, yang paling sering saya gunakan adalah:
Given: Pada bagian ini digunakan untuk menyiapkan sistem sebelum sistem terdapat interaksi dengan luar/user.
When: Digunakan untuk proses sistem saat bekerja atau saat sistem berinteraksi dengan eksternal
Then: Hasil dari proses si sistem tersebut
Steps
Di dalam folder features terdapat folder “steps”, di sinilah testing dimulai:
contoh isi “first.py
from behave import given, when, then
from simplecalc import Simplecalc

@given("we have simple calculator")
def load_calc(context):
    context.simple = Simplecalc()

@when("we add 2 and 2")
def run_addition(context):
    context.simple.addition(2,2)

@then("we got 4")
def get_result(context):
    assert context.simple.get_result() == 4
Untuk menjalankan tesnya kita bisa gunakan perintah “behave” di terminal di root aplikasi, hasilnya
Feature: Simple calculator # features/first.feature:1

  Scenario: Run simple addition     # features/first.feature:3
    Given we have simple calculator # features/steps/first.py:4 0.000s
    When we add 2 and 2             # features/steps/first.py:8 0.000s
    Then we got 4                   # features/steps/first.py:12 0.000s

1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
3 steps passed, 0 f

Scenario Outline

Skenario di atas sudah berhasil di tes, tapi bagaimana jika kita ingin membuat skenario yang sama namun dengan nilai yang berbeda, contoh di atas saya ingin mengetes penjumlahan 2 + 2 adalah 4, lalu kalau ingin menambahkan tes misalnya 10+2 adalah 12 masa kita perlu membuat skenario lagi? Nah di sini lah fungsi skenario outline
Mari buat satu features file lagi, agar lebih fokus aja sih ini, saya beri nama second.feature.
Feature: Simple calculator

  Scenario Outline: Run simple addition
    Given we have simple calculator with dynamic value
    When we add <first> and <second>
    Then we got <result>

    Examples: Run simple example
    |first|second|result|
    |10|2|12|
untuk stepnya yang saya beri nama second.py
from behave import given, when, then
from simplecalc import Simplecalc

@given("we have simple calculator with dynamic value")
def load_calc(context):
    context.simple = Simplecalc()

@when("we add {first} and {second}")
def run_addition(context, first, second):
    context.simple.addition(first,second)

@then("we got {result}")
def get_result(context, result):
    assert context.simple.get_result() == result
Jalan kan perintah “behave -i second“, perintah ini untuk hanya menjalankan feature “second” saja.
Hasilnya
Feature: Simple calculator # features/second.feature:1

  Scenario Outline: Run simple addition -- @1.1 Run simple example  # features/second.feature:10
    Given we have simple calculator with dynamic value              # features/steps/second.py:4 0.000s
    When we add 10 and 2                                            # features/steps/second.py:8 0.000s
    Then we got 12                                                  # features/steps/second.py:12 0.000s
      Traceback (most recent call last):
        File "/~Code/playground/trybehave/venvbheave/lib/python3.7/site-packages/behave/model.py", line 1329, in run
          match.run(runner.context)
        File "/~Code/playground/trybehave/venvbheave/lib/python3.7/site-packages/behave/matchers.py", line 98, in run
          self.func(context, *args, **kwargs)
        File "features/steps/second.py", line 14, in get_result
          assert context.simple.get_result() == result
      AssertionError
Lah Error? Namun sayangnya informasi Error kurang informatif. Saya mengakalinya dengan paket untuk assert yang lain, yang saya pakai “PyHamcrest

Install dulu

pip install PyHamcrest
Lalu ubah kode testing menjadi
from behave import given, when, then
from simplecalc import Simplecalc
from hamcrest import assert_that, equal_to

@given("we have simple calculator with dynamic value")
def load_calc(context):
    context.simple = Simplecalc()

@when("we add {first} and {second}")
def run_addition(context, first, second):
    context.simple.addition(first,second)

@then("we got {result}")
def get_result(context, result):
    assert_that(context.simple.get_result() , equal_to(result) )

Lalu jalankan perintah “behave -i second” lagi, hasilnya

Scenario Outline: Run simple addition -- @1.1 Run simple example  # features/second.feature:10
    Given we have simple calculator with dynamic value              # features/steps/second.py:5 0.000s
    When we add 10 and 2                                            # features/steps/second.py:9 0.000s
    Then we got 12                                                  # features/steps/second.py:13 0.000s
      Assertion Failed: 
      Expected: '12'
           but: was '102'
Masih error tentu saja, tapi informasi lebih jelas, yang kita harapkan “12” yang kita dapatkan “102”, kenapa seperti itu? Jawabanya adalah karena parameter yang kita kirimkan itu dianggap string, sehingga saat string “10” ditambahkan string “2” hasilnya menjadi “102”. Untuk memastikan parameter yang kita kirimkan tipenya sesuai yang kita inginkan kita perlu ubah lagi stepnya menjadi:
@when("we add {first:d} and {second:d}")
def run_addition(context, first, second):
    context.simple.addition(first,second)

@then("we got {result:d}")
def get_result(context, result):
    assert_that(context.simple.get_result() , equal_to(result) )
Perhatikan “:d” di sana, bagian itu mengubah parameter menjadi digit/number, untuk konversi lainnya bisa dilihat di sini :https://behave.readthedocs.io/en/latest/parse_builtin_types.html#predefined-data-types-in-parse

Jalankan lagi “behave -i second” dan hasilnya

Feature: Simple calculator # features/second.feature:1

  Scenario Outline: Run simple addition -- @1.1 Run simple example  # features/second.feature:10
    Given we have simple calculator with dynamic value              # features/steps/second.py:5 0.000s
    When we add 10 and 2                                            # features/steps/second.py:9 0.000s
    Then we got 12                                                  # features/steps/second.py:13 0.000s

1 feature passed, 0 failed, 0 skipped
1 scenario passed, 0 failed, 0 skipped
3 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.000s

Kalau ingin menambahkan contoh lainnya, ubah second.features menjadi

Feature: Simple calculator

  Scenario Outline: Run simple addition
    Given we have simple calculator with dynamic value
    When we add <first> and <second>
    Then we got <result>

    Examples: Run simple example
    |first|second|result|
    |10|2|12|
    |5|2|7|
    |100|900|1000|

Tes lagi dan hasilnya

Feature: Simple calculator # features/second.feature:1

  Scenario Outline: Run simple addition -- @1.1 Run simple example  # features/second.feature:10
    Given we have simple calculator with dynamic value              # features/steps/second.py:5 0.000s
    When we add 10 and 2                                            # features/steps/second.py:9 0.000s
    Then we got 12                                                  # features/steps/second.py:13 0.000s

  Scenario Outline: Run simple addition -- @1.2 Run simple example  # features/second.feature:11
    Given we have simple calculator with dynamic value              # features/steps/second.py:5 0.000s
    When we add 5 and 2                                             # features/steps/second.py:9 0.000s
    Then we got 7                                                   # features/steps/second.py:13 0.000s

  Scenario Outline: Run simple addition -- @1.3 Run simple example  # features/second.feature:12
    Given we have simple calculator with dynamic value              # features/steps/second.py:5 0.000s
    When we add 100 and 900                                         # features/steps/second.py:9 0.000s
    Then we got 1000                                                # features/steps/second.py:13 0.000s

1 feature passed, 0 failed, 0 skipped
3 scenarios passed, 0 failed, 0 skipped
9 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.001s
Untuk pengguna django bisa menggunakan behave django, karena kita gak perlu konfig banyak dan langsung kepake. Cara pakainya di tulisan ke dua. Bhay.
Referensi:
https://behave.readthedocs.io/en/latest/tutorial.html
https://stackoverflow.com/questions/22045592/how-to-see-exactly-what-went-wrong-in-behave