Tutorial 4: Helpers and libraries¶
This section will show you the concept of helpers and libraries.
Concept¶
Sample test script implemented in previous tutorials is very simple. Real automated tests require advanced coding. Basic programming principle is to decompose complex code the simpler tasks.
Yoda extension recommends decomposition to 3 levels.
scripts: Embedded code with basic complexity and usage of helpers.
helpers: High level coding with usage of libraries
libraries: Low level coding
Note
Each level has its own directory: yoda-tests, helpers, lib.
We will show the concept on example used on real project.
Library¶
Libraries provides low level methods and classes. In our example they access tested application using HydraTK network library.
This library implements interface to Oracle database. Provides methods to connect, disconnect and read customer entity.
Modules are located in test_repo_root/lib/yodalib by default.
from hydratk.lib.network.dbi.client import DBClient
from yodalib.config import params as config
from yodalib.ba.entities import Customer
class DB_INT:
_client = None
def __init__(self):
self._client = DBClient('ORACLE')
def connect(self):
cfg = config['ba']
result = self._client.connect(cfg['db_host'], cfg['db_port'], cfg['db_sid'],
config['common']['user'], cfg['db_passw'])
return result
def disconnect(self):
result = self._client.disconnect()
return result
def read_customer(self, cu_ref_no):
query = """
SELECT a.customer_ref_no, a.account_id, a.status as action_customer, a.document_id, a.document_type, a.segment, a.sfa_id,
a.type, b.status as action_subject, b.first_name, b.surname, to_char(b.birth_date, \'YYYY-MM-DD\') as birth_date,
b.birth_number, b.registration_number, b.tax_number, b.company_name, c.status as action_address, c.street,
c.orientation_number, c.street_number, c.city, c.district, c.zip_code, c.country
FROM ba.customer a, ba.subject b, ba.customer_address c
WHERE a.customer_ref_no = ? AND a.technical_status <> \'H\'
AND a.account_id = b.account_id (+) AND nvl(b.technical_status, \'X\') <> \'H\'
AND a.account_id = c.account_id (+) AND c.technical_status <> \'H\'
"""
result, row = self._client.exec_query(query, [cu_ref_no], fetch_one=True)
if (not result or len(row) == 0):
return None
customer_info = CustomerInfo(row['document_id'], row['document_type'], row['segment'],
row['type'], row['action_customer'], row['sfa_id'])
subject = Subject(row['action_subject'], row['first_name'], row['surname'], row['birth_date'],
row['birth_number'], row['registration_number'], row['tax_number'], row['company_name'])
address = Address(row['street'], row['street_number'], row['city'], row['zip_code'], row['country'],
row['action_address'], row['orientation_number'], row['district'])
customer = Customer(row['customer_ref_no'], row['account_id'], customer_info, subject, address)
return customer
This library implements interface to Weblogic JMS queue. Provides methods to connect, disconnect and create customer entity.
from hydratk.lib.network.jms.client import JMSClient from hydratk.lib.array.operation import subdict from yodalib.config import params as config from lxml.etree import Element, SubElement, tostring class JMS_INT: _client = None nsmap = None ns = None destination = None jms_type = None def __init__(self): self._client = JMSClient(); self.nsmap = { 'int' : 'http://o2.cz/cip/svc/IntegrationMessage-3.0', 'ba_cm': 'http://o2.cz/systems/billing/BA-GF/BA-GF_BillingCustomerManagement/2.0', 'ba_pm': 'http://o2.cz/systems/billing/BA-GF/BA-GF_BillingProductManagement/1.0' } self.ns = { 'int' : '{%s}' % self.nsmap['int'], 'ba_cm': '{%s}' % self.nsmap['ba_cm'], 'ba_pm': '{%s}' % self.nsmap['ba_pm'] } self.destination = 'cipesb/gf/cip2ba/queue/request' self.jms_type = { 'manageCustomer' : 'BA-GF.BillingCustomerManagement-2.0.manageCustomer.Request', 'manageCustomerAccount': 'BA-GF.BillingCustomerManagement-2.0.manageCustomerAccount.Request', 'manageProduct' : 'BA-GF.BillingProductManagement-1.0.manageProduct.Request' } def connect(self): cfg = config['esb'] properties = {'initial_context_factory': cfg['initial_context_factory'], 'provider_url': cfg['provider_url']} result = self._client.connect(cfg['connection_factory'], properties) return result def disconnect(self): result = self._client.disconnect() return result def manage_customer(self, header, cu_ref_no, customer_info=None, subject=None, address=None, operator='O2_CZ', action='activate'): ns = self.ns['ba_cm'] root = Element(ns+'ManageCustomerRequest', nsmap=subdict(self.nsmap, {'int', 'ba_cm'})) root.append(header.toxml(self.ns['int'])) body = SubElement(root, ns+'requestBody') SubElement(body, ns+'cuRefNo').text = cu_ref_no SubElement(body, ns+'operator').text = operator SubElement(body, ns+'action').text = action if (customer_info != None): body.append(customer_info.toxml(ns)) if (subject != None): body.append(subject.toxml(ns)) if (address != None): body.append(address.toxml(ns)) message = tostring(root, xml_declaration=True, encoding='UTF-8') headers = {'JMSType': self.jms_type['manageCustomer'], 'JMSCorrelationID': header.correlation_id} result = self._client.send(self.destination, message, headers=headers) return result
Helper¶
Helpers provides high level methods. In our example they use library methods, provide simpler interface and debug messages, prepare data.
This helper prepares customer entity (entity classes are simple, so they are not shown in example).
Modules are located in test_repo_root/helpers/yodahelpers by default.
from hydratk.lib.data.randgen import gen_id from yodalib.ba.entities import Customer def customer_complex(): cu_ref_no = gen_id() customer_info = CustomerInfo(document_id='1426', document_type='ID_CARD', segment='R', sfa_id=cu_ref_no, type='Person') subject = Subject(first_name='Charlie', surname='Bowman', birth_date='1970-01-01', birth_num='7001010001', registration_number='1234', tax_number='2345', company_name='Bowman') address = Address(street_name='Tomickova', orientation_num='2144', street_num='1', city='Praha', district='Chodov', zip_code='14800', country='CZE') customer = Customer(cu_ref_no=cu_ref_no, customer_info=customer_info, subject=subject, address=address) return customer
This helper provides simpler interface to library methods.
from yodalib.ba.db_int import DB_INT from yodalib.ba.jms_int import JMS_INT from yodalib.utils.interface import wait, gen_header db = DB_INT() jms = JMS_INT() def db_connect(): print 'Connecting to DB' return db.connect() def db_disconnect(): print 'Disconnecting from DB' return db.disconnect() def jms_connect(): print 'Connecting to JMS' return jms.connect() def jms_disconnect(): print 'Disconnecting from JMS' return jms.disconnect() def connect(): res_db = db_connect() res_jms = jms_connect() return res_db and res_jms def disconnect(): res_db = db_disconnect() res_jms = jms_disconnect() return res_db and res_jms def read_customer(cu_ref_no): print 'Reading customer: {0}'.format(cu_ref_no) customer = db.read_customer(cu_ref_no) return customer def manage_customer(customer, action='activate'): header = gen_header('ESB', 'BA') print 'Sending message manageCustomer: {0}'.format(header.correlation_id) res = jms.manage_customer(header, customer.cu_ref_no, customer.customer_info, customer.subject, customer.address, action=action) wait() return res, header.correlation_id
Script¶
Scripts use helper methods, the embedded code is simple. The complexity is hidden in used methods.
This script prepares customer entity, sends it as JMS message and reads created customer from database.
Test-Scenario-1: id: ts_01 path: SOC/SYS/BA/manageCustomer/manageCustomer_complex.jedi name: manageCustomer_complex desc: Create customer with complex configuration author: Petr Rasek <bowman@hydratk.org> version: 1.0 pre-req: | import yodahelpers.o2.soc.ba.entities as ent import yodahelpers.o2.soc.ba.interface as int this.test_result = int.connect() assert (this.test_result == True) Test-Case-1: id: tc_01 name: create_customer_complex desc: Create complex customer Test-Condition-1: id: tco_01 name: send_msg desc: Send message manageCustomer test: | customer = ent.customer_complex() cu_ref_no = customer.cu_ref_no this.test_result, corr_id = int.manage_customer(customer) validate: | assert (this.test_result == True) Test-Condition-2: id: tco_02 name: read_customer desc: Read created customer test: | cu = int.read_customer(cu_ref_no) this.test_result = (cu != None) validate: | assert (this.test_result == True) print cu post-req: | this.test_result = int.disconnect() assert (this.test_result == True)
This example shows new tags:
- pre-req: pre-requirements executed before first test case.
- post-req: post-requirements executed after last test case.
The purpose of pre-requirements is to import helpers, load data, initialize clients, connections etc. The purpose of post-requirements is to clean data, close clients, connections etc.