What's Wrong with Existing Cucumber?
Because we realized what we loved was...
Huge, messy codebase
MADNESS in steps
Always global all the code
Hard to maintain and reuse
Coupled scenarios/step definitions
Complex code in step definitions
Step description not corresponding to actions
Hard to read and understand features for business
Just to be clear,
take a look on this...
When(/^I (?:create|add)\s?\w* payment\s((?:with|to) conversion)?\s?using options:$/) do |with_conversion, options|
@options_for_pay_create = options.hashes.first.symbolize_keys
params_for_removal = @options_for_pay_create.reject{|k,v| v != 'remove'}.keys
@options_for_pay_create[:conversion_id] = @conversion_id if @conversion_id
beneficiary_options = {}
if @options_for_pay_create.has_key?(:on_behalf_of)
if @options_for_pay_create[:on_behalf_of] =~ (/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]*\Z/)
@on_behalf_of = @options_for_pay_create[:on_behalf_of]
else
client_type = @options_for_pay_create[:on_behalf_of]
@on_behalf_of = (TCC_ENV[:users][client_type]).contact_id
end
@options_for_pay_create[:on_behalf_of] = @on_behalf_of
beneficiary_options[:on_behalf_of] = @on_behalf_of unless @beneficiary_id
end
unless @beneficiary_id
beneficiary_options.merge!({:currency => @options_for_pay_create[:currency], :bank_country => 'GB'})
@beneficiary_id = V2ApiServiceHelper.create_beneficiary(OptionsHelper.options_for_beneficiary_api_v2(beneficiary_options))['id']
end
@options_for_pay_create.merge!(:beneficiary_id => @beneficiary_id) if @beneficiary_id
@options_for_pay_create = OptionsHelper.options_for_payment(@options_for_pay_create)
if with_conversion
@conversion_id = @options_for_pay_create.fetch(:conversion_id, V2ApiServiceHelper.create_conversion(OptionsHelper.options_for_conversion)['id'])
@options_for_pay_create.merge!({:conversion_id => @conversion_id})
end
@options_for_pay_create.each do |k, v|
@options_for_pay_create.delete(k) if v == 'remove'
@options_for_pay_create[k] = SecureRandom.random_number(999999).to_s if v == 'random'
@options_for_pay_create[k] = SecureRandom.hex(10).to_s if v == 'random_string'
end
params = OptionsHelper.options_for_payer(@options_for_pay_create).merge(@options_for_pay_create)
if params['payer_entity_type']
params.delete('payer_type')
else
params['payer_entity_type'] = params.delete('payer_type') if params['payer_type']
end
params.delete_if{|k,_| params_for_removal.include?(k.to_sym)}
params["payment_date"] = (Date.today).strftime('%Y-%m-%d') if params["payment_date"] == 'today'
conversion_date = @conversion ? Date.parse(@conversion["conversion_date"]) : nil
params["payment_date"] = (StaticDataServiceHelper.get_next_date_of('Saturday', conversion_date)).strftime('%Y-%m-%d') if params["payment_date"] == 'Saturday'
@v2_response = V2ApiServiceHelper.create_payment(params)
@options_for_pay_create.delete(:on_behalf_of)
@payment = @v2_response
@payment_id = @v2_response['id']
@empty_timestamps = ['transferred_at']
@payer_id = @v2_response['payer_id']
@payments_for_submissions ||= []
@payments_for_submissions << @payment_id
@response = @v2_response
end
More problems in details ...
Test author has to provide around 15!!! parameters to create beneficiary, payments etc.
Most of them are not “interesting” for particular scenario
Existing code for generation of required options is total mess and is not maintainable at all
Hard to read big tables in scenarios
And I create payment using options:
| currency | amount | reason | reference | payment_type | payer_entity_type | payer_address | payer_city |..
| <currency> | 200 | Test | paymentgroup 1 | regular | individual | 221b, backer street | London |..
..| payer_country | payer_first_name | payer_last_name | payer_date_of_birth | payer_identification_type | payer_identification_value |
..| GB | John | Smith | 1990-11-11 | passport | 123456 |
They are:
Hard to maintain
Hard to find method you want (if one is not found duplicate methods are created)
They are not only "transport" classes including only methods with calls, but also helper methods that should not be there:
Service helpers include too many methods
Example:
PaymentsServiceHelper class contains 136 methods
They are randomly added without any grouping
Many of those methods are not used at all
def self.create_settlement_in_status_part_paid(principal_identifier)
conversions = []
options_for_conversion = OptionsHelper.options_for_conversion
conversions << V2ApiServiceHelper.create_conversion(options_for_conversion)
options_for_conversion = options_for_conversion.merge({:buy_currency => options_for_conversion[:sell_currency],
:sell_currency => options_for_conversion[:buy_currency]})
conversions << V2ApiServiceHelper.create_conversion(options_for_conversion)
settlement = create_settlement_in_status_funds_arrived(conversions)
conversions.each do |conversion|
TransactronServiceHelper.close_hedges_for_trade(conversion['short_reference'], conversion['currency_pair'], conversion['buy_currency'], conversion['client_buy_amount'])
end
currency_entry = TradingServiceHelper.find_settlement_entry(settlement['short_reference'], conversions.first['sell_currency'])
TradingServiceHelper.make_entry_payment({:entry_id=>currency_entry["settlement_entry"]["uuid"], :currency => conversions.first['sell_currency'], :override_status_check => "true"}, principal_identifier)
V2ApiServiceHelper.get_settlement(settlement['id'])
end
Usage of instance variables to maintain state
No registry objects that store all the created/retrieved entities during the test
Most of step definitions include statements that share and set state in instance variables
All step definitions are very dependent on each other
Find suitable existing step definition that uses proper instance variable takes too much time
Examples:
When(/^I (?:create|add)\s?\w* payment\s((?:with|to) conversion)?\s?using options:$/) do |with_conversion, options|
:
:
@v2_response = V2ApiServiceHelper.create_payment(params)
@options_for_pay_create.delete(:on_behalf_of)
@payment = @v2_response
@payment_id = @v2_response['id']
@empty_timestamps = ['transferred_at']
@payer_id = @v2_response['payer_id']
@payments_for_submissions ||= []
@payments_for_submissions << @payment_id
@response = @v2_response
end
Then(/^I should receive the success response$/) do
@response.code.should == 200
end
Then(/^I should get successful response with created payment from API v2$/) do
@v2_response['id'].should_not be_nil
end
Hard to implement complex and flexible scenarios
We can not do things like this:
Scenario: Generation of different currency payments
Given I am signed for v2 API as Cash Manager account user
And I have beneficiary created using API V2 with options:
| currency | bank_country | payment_types |
| USD | US | regular, priority |
| GBP | GB | regular, priority |
| SGD | SG | regular, priority |
| EUR | DE | regular, priority |
When I create standalone payments with following parameters:
| currency | amount | payment_type |
| USD | 2000 | priority |
| GBP | 1000 | regular |
| SGD | 3000 | regular |
| EUR | DE | priority |
We can not create bunch of beneficiares and bunch of payments for them
All the entities are selected implicitly(e.g. from last values stored in instance variables like @beneficiary_id, @conversion_id). There is no easy way to store and retrieve them.
There is no way to specify explicitly what you want to use(e.g. what conversion for payment)
As result such scenarios take much more time to prepare them, ugly and hard to understand
Example of existing scenario
Scenario Outline: Compliance checks should be triggered after update only when payer_id or beneficiary_id had changed
Given the Compliance Engine database is clear
And I am signed in as api user
And I have beneficiary created with options:
|bank_account_holder_name |payment_types |currency |beneficiary_country |::
|GOOD GUY |priority, regular |GBP |GB |::
When I create conversion via APIv2 using options:
| buy_currency | sell_currency | amount | fixed_side | term_agreement | reason |
| GBP | USD | 7000 | buy | true | test |
And I create payment with conversion using options:
|currency|amount |reason |::
|GBP |2345.67|cucumber_test|::
And I have beneficiary created with options:
|bank_account_holder_name |payment_types |currency |beneficiary_country |::
|BAD GUY |regular |USD |GB |::
When I create conversion via APIv2 using options:
| buy_currency | sell_currency | amount | fixed_side | term_agreement | reason |
| USD | EUR | 7000 | buy | true | test |
And I create payment with conversion using options:
|currency|amount |reason |::
|USD |2345.67|cucumber_test|::
New reusable step definitions must be prepared
- Many Conjunctive Steps as result of using instance variables for sharing state
- Often steps are duplicated in order to use another instance variable in it
- Many step definitions have description that does not correspond to actions
- There are steps that do a lot of additional actions
- In many “action” steps missing verification that action was successful. As result harder to debug failed tests
- A lot of technical steps
Mess with existing step definitions
- Hard to find wanted step definitions for new tests
- Many duplicate steps that do the same actions
- No grouping of steps by business objects, model
- Some files containing step definitions are really huge, find something there is really hard.
- Some files contain step definitions that do not correspond to the file name.
Example : payments_steps.rb” with 1280 lines of code has huge number of steps related to trades creation, response validations, beneficiaries, legal entities etc
Mess with existing feature files
- We do not have proper structure of feature files
- Hard find tests for particular feature
- Sometimes scenarios for one feature are located in different directories
- Sometimes one test covers two diffrent features
- Huge feature file with too many scenarios(e.g. ....)
References
Whats wrong with Cucumber
By Igor Sosnovskyy
Whats wrong with Cucumber
- 79