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/










2 ELM 327

orginalny vs podrobione
https://www.elektroda.pl/rtvforum/topic3214509.html

dobre:
W miarę sprawdzony jest STN1110 (zwany przez nich ELM327 v2) od Viaken


Na Beagle Bone Black  obsugiwany przez RS

na BBB pdf

obsuga RS232 w linux C

http://xanthium.in/Serial-Port-Programming-on-Linux



sprawdzanie który tty to RS:
$ dmesg | grep tty


ELM

ELM327 instaluje się w Linuksie jako RS323

/dev/ttyUSB#

Emulacja podłączonego ELM w linuxie

$ apt-get install obdgpslogger

uruchom:
$ obdsim


mozna to odczytać:

$ screen /dev/ttyUSB# 38400   (albo bez prędkości)
$ screen /dev/pts/1                    (dla emulacji obdsim)

instalacja screen

sudo apt install screen

polecenia po nawiązaniu komunikacji:

>AT L1 # enable line feed
OK
> AT I # information
ELM327 v1.4
>AT @1 # device description
OBDII to RS232 Interpreter
> AT DP # current protocol
AUTO
> AT RV # car is obviously not connected, no voltage on OBDII port
0.0V


lista polecen AT:
https://www.elmelectronics.com/wp-content/uploads/2016/07/AT_Command_Table.pdf

http://www.obdtester.com/elm-usb-commands


>0100
41 FF ...
lista polecen OBDII PIDS
https://en.wikipedia.org/wiki/OBD-II_PIDs
https://python-obd.readthedocs.io/en/latest/Command%20Tables/


Kod
Działający przykład oparty o komunikację z RS232, odczytujący z emulowanego OBD
przeliczający zwrocone wartosci hex na int.

#include <stdio.h>
#include <fcntl.h>   /* File Control Definitions           */
#include <termios.h> /* POSIX Terminal Control Definitions */
#include <unistd.h>  /* UNIX Standard Definitions     */
#include <errno.h>   /* ERROR Number Definitions           */
#include <stdlib.h>  //strtol do zamiany char na hex

void main(void)
{

         /*------------------------------- Ask for data to write to serial port ------------------*/

printf("Podaj kod PID 4 cyfry np 0100: ");
char tempp[6];
fgets(tempp,6,stdin);
printf("\n");
tempp[4]='\n';
tempp[5]='\0';


 
 
int fd;/*File Descriptor*/

printf("\n +----------------------------------+");
printf("\n |        Serial Port read & write  |");
printf("\n +----------------------------------+");

/*------------------------------- Opening the Serial Port -------------------------------*/

/* Change /dev/ttyUSB0 to the one corresponding to your system */

fd = open("/dev/pts/1",O_RDWR | O_NOCTTY | O_SYNC); 
/* ttyUSB0 is the FT232 based USB2SERIAL Converter   */
/* O_RDWR   - Read/Write access to serial port       */
/* O_NOCTTY - No terminal will control the process   */

/* bez 3 parametru (read) - Open in blocking mode,read will wait              */

//write  dodany 3 parametr(o_sync byl probnie dodany przezemnie)
/* O_NDELAY -Non Blocking Mode,Does not care about-  */
/* -the status of DCD line,Open() returns immediatly */



        if(fd == -1)      /* Error Checking */
           printf("\n  Error! in Opening tty  ");
        else
           printf("\n  tty Opened Successfully ");


/*---------- Setting the Attributes of the serial port using termios structure --------- */

struct termios SerialPortSettings; /* Create the structure                          */

tcgetattr(fd, &SerialPortSettings); /* Get the current attributes of the Serial port */

/* Setting the Baud rate */
cfsetispeed(&SerialPortSettings,B38400); /* Set Read  Speed                       */
cfsetospeed(&SerialPortSettings,B38400); /* Set Write Speed                        */

/* 8N1 Mode */
SerialPortSettings.c_cflag &= ~PARENB;   /* Disables the Parity Enable bit(PARENB),So No Parity   */
SerialPortSettings.c_cflag &= ~CSTOPB;   /* CSTOPB = 2 Stop bits,here it is cleared so 1 Stop bit */
SerialPortSettings.c_cflag &= ~CSIZE;  /* Clears the mask for setting the data size             */
SerialPortSettings.c_cflag |=  CS8;      /* Set the data bits = 8                                 */

SerialPortSettings.c_cflag &= ~CRTSCTS;       /* No Hardware flow Control                         */
SerialPortSettings.c_cflag |= CREAD | CLOCAL; /* Enable receiver,Ignore Modem Control lines       */


SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY);          /* Disable XON/XOFF flow control both i/p and o/p */
SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);  /* Non Cannonical mode                            */

SerialPortSettings.c_oflag &= ~OPOST;/*No Output Processing*/

       /* Setting Time outs no Blocking for writing*/
 SerialPortSettings.c_cc[VMIN] = 1; /*  nr signs to  Read  */
 SerialPortSettings.c_cc[VTIME] = 1; // 0.1 seconds read timeout

if((tcsetattr(fd,TCSANOW,&SerialPortSettings)) != 0) /* Set the attributes to the termios structure*/
 printf("\n  ERROR ! in Setting attributes");
else
 printf("\n  BaudRate = 38400 \n  StopBits = 1 \n  Parity   = none");




         /*------------------------------- Write data to serial port -----------------------------*/

char write_buffer[6];
for(int i=0;i<6;i++)      //="0100\n"; /* Buffer containing characters to write into port      */
 write_buffer[i]=tempp[i];

int  bytes_written  = 0;   /* Value for storing the number of bytes written to the port */

bytes_written = write(fd,write_buffer,sizeof(write_buffer)-1);/* use write() to send data to port                                            */

  printf("\n  %s written to tty",write_buffer);
  printf("\n  %d Bytes written to tty", bytes_written);
  printf("\n +----------------------------------+\n\n");


//usleep (1);             // sleep enough to transmit the 4 plus
                                     // receive 15:  approx 100 uS per char transmit
tcdrain(fd);                         //delay for output
        /*------------------------------- Read data from serial port -----------------------------*/

        /* Setting Time outs - Blocking for reading*/
// SerialPortSettings.c_cc[VMIN] = 10; // Read at least 10 characters 
// SerialPortSettings.c_cc[VTIME] = 0; // Wait indefinetly   

//if((tcsetattr(fd,TCSANOW,&SerialPortSettings)) != 0) // Set the attributes to the termios structure
// printf("\n  ERROR ! in Setting attributes");
//else
// printf("\n  BaudRate = 38400 \n  StopBits = 1 \n  Parity   = none");



tcflush(fd, TCIFLUSH);   /* Discards old data in the rx buffer            */

char read_buffer[20];   /* Buffer to store the data received              */
int  bytes_read = 0;    /* Number of bytes read by the read() system call */

int licz=3;
do{

char read_buf[20];   /* Buffer to store the data received              */
int  bytes_r = 0;    /* Number of bytes read by the read() system call */


bytes_r = read(fd,&read_buf,19); /* Read the data                   */

//printf("\n  Bytes Rxed przed pt %d:", bytes_r); /* Print the number of bytes read */


for(int i=0;i<bytes_r;i++)  /*printing only the received characters*/
{
// printf("%c",read_buf[i]);
 read_buffer[i]=read_buf[i];
}
bytes_read=bytes_r;
}while(licz--);





printf("\n  Bytes Rxed za petla %d:\n  ", bytes_read); /* Print the number of bytes read */


for(int i=0;i<bytes_read;i++)  /*printing only the received characters*/
 printf("%c",read_buffer[i]);

read_buffer[bytes_read]='\0';
printf("  ");

char A[3];
A[0]=read_buffer[0];
A[1]=read_buffer[1];
A[2]='\0';
int a=(int)strtol(A,NULL,16);


printf("pierwsza =: %d\n",a);



 printf("\n +----------------------------------+\n\n\n");

 close(fd); /* Close the serial port */


}





korzystanie z libftdi

https://codeseekah.com/2012/02/22/elm327-to-rs232-in-linux/

instalacja biblioteki do kodu
sudo apt-get install libftdi libftdi-dev

sprawdzanie:
apt-cache policy libffi-dev

mozna dodac:
sudo apt-get install build-essential

dokumentacja  dla libftdi
https://www.intra2net.com/en/developer/libftdi/documentation/group__libftdi.html


kod (nie działa):

#include <stdio.h>
#include <ftdi.h>
#include <string.h>

int main(int argc, char * argv[]) {

  struct ftdi_context ftdic; /* FTDI context */
  /* line properties */
  enum ftdi_bits_type bits = BITS_8; // 8 data bits
  enum ftdi_stopbits_type sbit = STOP_BIT_1; // 1 stop bit
  enum ftdi_parity_type parity = NONE; // no parity
  enum ftdi_break_type lineend = BREAK_OFF;

  int ret;
  unsigned short modem_status = 0;
  unsigned char * tx;
  unsigned char * rx;
  int i,j; /* general purpose indices */

  if ((tx = malloc(128)) == NULL || (rx = malloc(128)) == NULL) {
    fprintf(stderr, "Error: could not allocate memory\n");
    return EXIT_FAILURE;
  }

  if (ftdi_init(&ftdic) < 0) {
    fprintf(stderr, "Error: could not initialize FTDI\n");
    return EXIT_FAILURE;
  }

  if ((ret = ftdi_usb_open(&ftdic, 0x0403, 0x6001)) < 0) {
    fprintf(stderr, "unable to open ftdi device: %d (%s)\n", ret, ftdi_get_error_string(&ftdic));
    return EXIT_FAILURE;
  }

  if ((ret = ftdi_usb_reset(&ftdic)) < 0) {
    fprintf(stderr, "unable to reset ftdi device: %d (%s)\n", ret, ftdi_get_error_string(&ftdic));
    return EXIT_FAILURE;
  }

  if ((ret = ftdi_usb_purge_buffers(&ftdic)) < 0) {
    fprintf(stderr, "unable to purge buffers on ftdi device: %d (%s)\n", ret, ftdi_get_error_string(&ftdic));
    return EXIT_FAILURE;
  }

  if ((ret = ftdi_set_baudrate(&ftdic, 38400)) < 0) {
    fprintf(stderr, "unable to set ftdi device baud rate: %d (%s)\n", ret, ftdi_get_error_string(&ftdic));
    return EXIT_FAILURE;
  }

  if ((ret = ftdi_set_line_property2(&ftdic, bits, sbit, parity, lineend)) < 0) {
    fprintf(stderr, "unable to set ftdi device properties: %d (%s)\n", ret, ftdi_get_error_string(&ftdic));
    return EXIT_FAILURE;
  }

  ftdi_poll_modem_status(&ftdic, &modem_status);
  printf("R232 status: 0x%x\n", modem_status);

  memcpy(tx, "AT E0\r", 6); /* turn echoing off */
  if (ftdi_write_data(&ftdic, tx, sizeof(unsigned char) * 6) < 0) {
    fprintf(stderr, "unable to send ftdi device data: %d (%s)\n", ret, ftdi_get_error_string(&ftdic));
    return EXIT_FAILURE;
  }

  while (1) {
    j = 0;

    while ( ( ret = getchar() ) != 0x0a && j < 128) tx[j++] = ret;
    if (!j) continue;
    tx[j++] = 0x0d; /* end */
    ret = 0;
    ftdi_usb_purge_tx_buffer(&ftdic);
    ftdi_usb_purge_rx_buffer(&ftdic);
    for (i = 0; i < j; i++) {
      ret += ftdi_write_data(&ftdic, tx+i, sizeof(unsigned char) * 1);
    }

    printf("Written %d bytes of data: ", ret);
    for (i = 0; i < ret; i++) printf("0x%x ", tx[i]);

    printf("\n");

    while (1) {
      ret = ftdi_read_data(&ftdic, rx, 128);
      if (ret > 0) {
        printf("Read %d bytes of data\n", ret);
        ret -= 3; /* remove > prompt */
        printf("\tDATA: ");
        for (i = 0; i < ret; i++) printf("0x%x ",rx[i]);
        printf("\n\t(");
        for (i = 0; i < ret; i++) printf("%c",rx[i]);
        printf(")\n");
        break;
      } else if (ret < 0) {
        fprintf(stderr, "unable to read from ftdi device: %d (%s)\n", ret, ftdi_get_error_string(&ftdic));
        return EXIT_FAILURE;
      }
    }
  }

  if ((ret = ftdi_usb_close(&ftdic)) < 0) {
    fprintf(stderr, "unable to close ftdi device: %d (%s)\n", ret, ftdi_get_error_string(&ftdic));
    return EXIT_FAILURE;
  }

  ftdi_deinit(&ftdic);

  free(tx);
  free(rx);

  return EXIT_SUCCESS;
}

kompilowanie:
gcc -Wall -ggdb -lftdi kodelm327toRS.c -o obd