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