3 BDD Behave in Python

BDD

Behavior-driven development  :  proces wytwórczy oprogramowania który powstał na podstawie procesu Test driven development TDD.
Zgodnie z metodyką TDD, dla każdej jednostki oprogramowania programista musi:

-najpierw zdefiniować zestaw testów dla danego modułu;
-doprowadzić do niezaliczenia testów;
-zaimplementować moduł;
-na końcu upewnić się, że wdrożenie modułu sprawiło, iż testy zostały wykonane pomyślnie
(przykład zadania TDD LINK )

Ta definicja jest raczej niespecyficzna, z tego powodu, BDD można postrzegać jako ciągły rozwój TDD, który dokonuje bardziej szczegółowych wyborów niż TDD.

BDD określa, że testy dowolnej jednostki oprogramowania powinny być opisane pod kątem pożądanego zachowania urządzenia (pożądanego zachowania oznacza :  ma wartość biznesową dla dowolnego podmiotu, który zlecił stworzenie danej jednostki oprogramowania )

 BDD to połączenie wspólnych metod i zasad TDD z cechami procesu domain-driven design
(DDD -  podejście do tworzenia oprogramowania kładące nacisk na takie definiowanie obiektów i komponentów systemu oraz ich zachowań, aby wiernie odzwierciedlały rzeczywistość, zaleca stosowanie wzorców projektowych ).


BDD skupia się na następujących aspektach:

-Od czego zacząć w procesie
-Co testować, a czego nie testować
-Ile można przetestować za jednym razem
-Jak nazwać testy
-Jak zrozumieć przyczynę ewentualnego niepowodzenia testu

Głównym celem BDD jest ponowna analiza podejścia testów jednostkowych i akceptacyjnych, które naturalnie pojawiają się w tych kwestiach.
Przykładowo, BDD sugeruje, aby nazwy testów jednostkowych były całymi zdaniami zaczynającymi się od warunkowego czasownika (np. w języku angielskim - od czasownika "should" - "powinien") i powinny być pisane w kolejności ich wartości biznesowej.
Testy akceptacyjne powinny być napisane za pomocą tzw. "historyjek użytkowników" o następującej strukturze: "jako [rola] chcę [opis oczekiwanej funkcji], aby [opis oczekiwanej korzyści]".
Kryteria akceptacyjne powinny być pisane w kategoriach scenariuszy i zaimplementowane w postaci klas: Given [początkowy kontekst], When [opis występującego zdarzenia], then [potwierdzenie wystąpienia oczekiwanego rezultatu] .

Od tego momentu wiele osób opracowało ramy BDD przez kilka lat, definiując je ostatecznie jako platformę komunikacji i współpracy dla programistów, testerów i nietechnicznych lub biznesowych uczestników projektu.









INSTALACJA Behave:

Na Wsl:
Instalacja:
sudo apt update && upgrade
sudo apt install python3 python3-pip ipython3

Lokacja:
which python3

Start:
python3

dostęp do dysku C windowsa na WSL:
cd /mnt/c/

lub folder domowy (cd ~ ) ubuntu dostęp w windows:
C:\Users\marcin.belzowski2\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu18.04onWindows_79rhkp1fndgsc\LocalState\rootfs\home\user

pip:
sudo apt install python-pip

behave:
pip install behave
sudo apt install python3-behave



Na Windows:
MS Store zainstaluj Windows Terminal
https://www.python.org/downloads/

where python3 - wskaże folder instalacji (ale działa tylko z cmd nie powershel)
python - uruchomi

polecane IDE: https://www.jetbrains.com/pycharm/

pip:
instaluje się razem z pythonem3

behave:
pip install behave




Behave:

Step definition:
w projekcie w folderze  "steps".

to funkcje pythona defioniowane jako dekoratory
https://chyla.org/blog/Python_-_Dekoratory/

zaczynają się @given , @then, @when

może być więcej plików .py, nie będą steps ale mogą zawierać funkcje importowane przez steps


Struktura plików:


zawartość pliku test_case_1.feature:

Feature: Test Cases Group 1

Scenario: Running Test Case 1

    Given I am the main directory
    Then I should also be in the main directory


identycznie case 2 tylko zamiast 1 jest 2.



Zawartość pliku steps.py

from behave import given, when, then
# import login
from login import login

@given("I am the main directory")
def in_main_dir(context):
    print("1111111")
    print("1111111")
    print("1111111")

@then("I should also be in the main directory")
def also_in_main_dir(context):
    pass


uruchamianie 1 pliku:
> behave  test_case_1.feature

uruchamianie z printoutem:
> behave  test_case_1.feature --no-capture


zasady uruchamiania:

.
pliki testu i folder Steps muszą być w tym samym folderze. Jeśli pliki testu są w podfolderze wtedy w pliku steps.py trzeba zainportować ten folder (np folder "login" )
from login import login

zaby uruchomić test będąc w (pod)folderze login (i wykorzystać steps.py który jest w folderze powyzej) steps.py musi zawierac:
import login    (bo startujemy juz z tego folderu więc nie potrzeba "from login")

co ważne nasz dodany (pod)folder login (który jest na tym samym poziomie co steps- zaburza zasade) nie może mieć wewnątrz swojego folderu "steps" jeśli by miał, behave nie będzie szukał tego folderu powyżej.




Uruchamianie wielu plików na raz:
>behave



Moduły:
jeśli jakiś folder ma być traktowany jako moduł, musi zawierać plik
__init__.py           (dwa podkreślenia przed i po)



Wyświetlanie na konsoli:
Standardowo ustawione jest logowanie, jeśli chcemy wyświetlić na konsoli dodaj --no-capture

ale jeśli będziemy mieli błąd, to wyświetli na cout na konsoli!

Przykład (fragment pliku  steps.py)

@then("I should see 'Preferences'")
def see_prefernces(context):

    print("Should see preferences")
    assert 1 == 2, "one is not same as two"

powyższa asercja nie spełnia warunku, powstaje błąd, jak widać spowoduje wyświetlenie stdout (Captured stdout: ) nawet bez parametru --no-capture


Dwa pierwsze przeszły (I am the home page, I click on my account - zielone) I should see 'Preferences - nie przeszło

w takim przyadku dodanie --no-capture, spowoduje klasyczne wyświetlenie coutów w miejscu ich wystąpienia, nie zbiorowo pod Captured stdout:



Given’, ‘Then’, ‘When’, ‘And’, ‘But’

given - wstępne warunki, ustawianie akcji, bez interakcji
when - interakcja z aplikacją, działanie na czymś
then - weryfikacja, oczekiwanie
and&but - reprezentują poprzednie słowo kluczowe




Passing Parameters to Steps 

steps to funkcje,  argumenty do nich (tekst) możemy parsować za pomocą pythona


Jeśli chcemy by jedno słowo było definiowaną zmienną ( tu French) wygląda to tak:



i wtedy zawartość pliku feature wygląda następująco:

Feature: Test Cases Group 1

Scenario: Running Test Case 1

    Given I go to the "English" version of the site


a kod pliku steps wygląda następująco:

@given("I go to the {lang} version of the site")
def go_to_site_version(context, lang):

    print("The language is: {}".format(lang))


Ważne, aby poprawnie wyświetlał printy uruchamiaj
na Windows:
behave --no-capture
na Linux:
behave --no-capture -f plain


inne przykłady kodu:

@then('I should see "{txt}"')
def i_should_see_text(context, txt):

    if txt not in ['success', 'error']:                     //1
        raise Exception("Unexpected text passed in.")

    if txt.lower() == 'success':                            //2
        print("Yeyyyyyyy")
    else:
        print("NOOOOOOOOOO")


//1    jeśli zmienna txt  podana w pliku feature:

Feature: Passing parameters to steps

Scenario: method 1 of passing step parameters

    Then I should see "errrrror"

nie zawiera się w  ['success', 'error']  podnieś wyjątek, spowoduje to Fail i wyświetlenie coutów

//2  jeśli txt pomniejszony do małych liter równa się success wyświetl...


wyświetlanie typu zmiennej qty, zmiana typu:

   print (type(qty))
   new_qty = int(qty)
   print (type(new_qty))




Sharing Data Between Steps

tworzymy zmienna dostępną globalnie(za pomocą context- instancja klasy behave):


ostrożnie z używaniem zmiennych, możesz sprawdzić czy twoja zmienna znajduje się w context

>>>'your_var' in context

przykład dostępu do wspolnej zmiennej ( context.order_num )przez kolejne kroki:

@given('I find recent order from database')
def find_order_from_db(context):

    print("Finding an order from the database....")
    context.order_num = '112233'

    print("Found an orders. Order number: {}".format(context.order_num))

#----------------------------------------------------------------------------------------------------------------------#
@when('I issue a refund for the order')
def issue_refund(context):

    print("Trying to issue a refund for order number: {}".format(context.order_num))





Practical Examples

Jeśli mamy projekt/folder i w nim folder z modułami np: BDDCommon, który ma podfoldery (3 x Common...)


to, żeby zawartości tych folderów mogły być importowane jako moduły to:
- muszą zawierać pusty plik o nazwie  __init__.py
- w głownym projektu folderze musi być plik który określa  packkages= 
tu setup.py

setup.py
from setuptools import setup
setup(name='PythonBDDtutorial',
      version='1.0',
      description='Python BDD Practical Examples',
      author='name',
      author_email='ad@...',
      url='https://www.',
      packages=[
            'BDDCommon',
            'BDDCommon.CommonFuncs',
            'BDDCommon.CommonSteps',
            'BDDCommon.CommonConfigs'
      ],
     )

żeby stworzyć w Pythonie opisane powyżej moduły (3 x Common...) trzeba je zbudować.

$ python setup.py install

Po każdej zmianie trzeba ponownie budować,  można dać:  python setup.py develop
wtedy automatycznie jest tworzone po każdej zmianie.

Foldery które tworzy to widoczne na rysunku folderów:
build
dist
PythonBDDtutorial.egg-info


żeby potem zaimportować w oryginalnym pliku steps.py dodajemy:

#from behave import given, when, then
from BDDCommon.CommonSteps.webstepscommon import *
from BDDCommon.CommonConfigs import locatorsconfig
from BDDCommon.CommonFuncs import webcommon


@then('the "{nav_bar}" bar should be visible')
def verify_nav_bars_visible(context, nav_bar):
...
.

zainstaluj:

1) firefoxa

2)
pip install -U selenium

3)
https://github.com/mozilla/geckodriver/releases
dodaj zmienną PATCH do geckodrivera i zresetuj windows


uruchom behave (folder steps w test) ale nie działa z wsl (brak mozliwosci uruchomienia przeglądarki)  działa z PowerShell i CMD windows.




Konfiguracje

Dodaj folder w packages (tu w pliku setup.py który po zmianie przeładować python setup.py install

packages=[
            'BDDCommon',
            'BDDCommon.CommonFuncs',
            'BDDCommon.CommonSteps',
            'BDDCommon.CommonConfigs'

następnie w pliku steps.py zaimportuj plik tu locatorconfig

from BDDCommon.CommonConfigs import locatorsconfig

przykladowe konfiguracje w pliku locatorconfig:

LOCATORS = {
    'main navigation' : {'type': 'id', 'locator':'mainnav'},
    'top navigation' : {'type': 'id', 'locator':'top'},
    'options' : {'type': 'css selector', 'locator':'.options-bar'}
}


URLCONFIG = {

        'python.org': 'https://www.python.org/ '

}




Setup - Teardown
przed i po Feature, Scenario, Step


.




Background

słowo kluczowe jak Feature, tyle, że jeden Background na featurefile





Scenario Outlines




żeby poniższy przykład działał uzyj słowa kluczowego:  Scenario Outlines



dwa scenariusze:


i więcej:




Przyklad:

 steps.py

from behave import given, when, then


@given("I login as a new user")
def login_as_new_user(context):

    print("I am logged in as a new user. ")


@when('I add a "{state}" shipping address')
def add_shipping_address(context, state):

    print('Adding address in state: {}'.format(state))



@when("I add a $40 book to the cart")
def add_book_to_cart(context):
    pass

@then("tax should be calculated")
def tax_should_calculate(context):
    pass



scenariu_outline_demo.feature

Feature: example/demo of scenario outline


Scenario Outline: Users in with shipping address in <state> should get charged sales tax

 Given I login as a new user
 When I add a $40 book to the cart
 And I add a "<state>" shipping address
 Then tax should be calculated

 Examples:
  | state |
  |  CA   |
  |  NY   |
  |  TX   |

Wynik 3 różnych scenariuszy:





Tags






Przykład pliku feature:

@regression
Feature: Navigation panels in home page

    @smoke
    Scenario: The home page should have all navigation panels

        Given I navigate to the home page
        Then I should see all navigation panels


    @smoke
    Scenario: The left navigation panel should have all options

        Given I navigate to the home page
        Then the left navigation panel should have all options


    Scenario: The top navigation panel should have all options

        Given I navigate to the home page
        Then the top navigation panel should have all options

uruchamianie Tagów:




uruchomienie z tags smoke uruchomi tylko scenaiusze z tagami smoke.

wynik: od góry bez tagów, dolna połowa z tagiem  "smoke"




łączenie Tagów:








Przykłady LINK:

Udemy Kurs




https://tohtml.com/