Cucumber JVM - Not Just BDD

Hello everyone! There are many articles about Cucumber on Habré and on the Internet, but I want to insert my five cents.



Many use Cucumber, and I myself find it a pretty wonderful library. My personal opinion is that it is useful for every Java developer to be familiar with this library along with the JUnit framework.


If your knowledge about Cucumber is equal to zero, there are excellent articles on Habré with which it is better to begin acquaintance with this tool



. , , , Cucumber .


Cucumber BDD Cucumber. , Cucumber , BDD. . Cucumber .


: matching engine


, , - open source ( matching engine), , Cucumber.


An order matching system – , / -.


Github . , JUnit.


basicFullCycleTest.


, ( / ).


- 100 , , , .


Cucumber.


Cucumber Maven. 5.4.2.


<dependency>
   <groupId>io.cucumber</groupId>
   <artifactId>cucumber-java</artifactId>
   <version>${cucumber.version}</version>
   <scope>test</scope>
</dependency>

<dependency>
   <groupId>io.cucumber</groupId>
   <artifactId>cucumber-junit</artifactId>
   <version>${cucumber.version}</version>
   <scope>test</scope>
</dependency>

<dependency>
   <groupId>io.cucumber</groupId>
   <artifactId>cucumber-picocontainer</artifactId>
   <version>${cucumber.version}</version>
   <scope>test</scope>
</dependency>

JUnit :



try (final ExchangeTestContainer container = new ExchangeTestContainer()) {
    container.initBasicSymbols();
    container.initBasicUsers();

- JUnit matching engine. Cucumber , .



import io.cucumber.java.After;
import io.cucumber.java.Before;

@Slf4j
public class OrderStepdefs {

   private final ExchangeTestContainer container;

   private List<MatcherTradeEvent> matcherEvents;
   private Map<Long, ApiPlaceOrder> orders = new HashMap<>();

   public OrderStepdefs(ExchangeTestContainer container) {
       this.container = container;
   }

   @Before
   public void before() throws Exception{
       log.info("before");
       container.initBasicSymbols();
       container.initBasicUsers();
   }

   @After
   public void after(){
       if(container != null){
           container.close();
       }
   }


Cucumber (, , feature , ) , . dependency injection, , , . .


- , . - , . , .


JUnit:


// ### 1. first user places limit orders
final ApiPlaceOrder order101 = ApiPlaceOrder.builder().uid(UID_1).id(101).price(1600)
    .size(7).action(ASK).orderType(GTC).symbol(symbolSpec.symbolId).build();

log.debug("PLACE 101: {}", order101);
container.submitCommandSync(order101, cmd -> {
   assertThat(cmd.resultCode, is(CommandResultCode.SUCCESS));
   assertThat(cmd.orderId, is(101L));
   assertThat(cmd.uid, is(UID_1));
   assertThat(cmd.price, is(1600L));
   assertThat(cmd.size, is(7L));
   assertThat(cmd.action, is(ASK));
   assertThat(cmd.orderType, is(GTC));
   assertThat(cmd.symbol, is(symbolSpec.symbolId));
   assertNull(cmd.matcherEvent);
});

, matching engine, , , match .


. , , , .


final int reserve102 = symbolSpec.type == SymbolType.CURRENCY_EXCHANGE_PAIR ? 1561 : 0;

final ApiPlaceOrder order102 = ApiPlaceOrder.builder().uid(UID_1).id(102).price(1550)
   .reservePrice(reserve102).size(4).action(OrderAction.BID).orderType(GTC)
    .symbol(symbolSpec.symbolId).build();

log.debug("PLACE 102: {}", order102);

container.submitCommandSync(order102, cmd -> {
   assertThat(cmd.resultCode, is(CommandResultCode.SUCCESS));
   assertNull(cmd.matcherEvent);
});

, .


final L2MarketDataHelper l2helper = new L2MarketDataHelper()
    .addAsk(1600, 7).addBid(1550, 4);

assertEquals(l2helper.build(), container.requestCurrentOrderBook(symbolSpec.symbolId));

, Cucumber


Scenario Outline: basic full cycle test

When A client 1440001 places an ASK order 101 at 1600@7 (type: GTC, symbol: <symbol>)
And A client 1440001 places an BID order 102 at 1550@4 (type: GTC, symbol: <symbol>, reservePrice: 1561)
Then An "<symbol>" order book is:
  |  bid | price  | ask  |
  |      | 1600  |  7    |
  |  4   | 1550  |       |
And No trade events

, JUnit , Cucumber Scenario Outline.


, <symbol>, , .


Examples:
| symbol        |
| EUR_USD   |
| ETH_XBT      |

Glue-


@When(value = "A client {long} places an {word} order {long} at {long}@{long} \\(type: {word}, symbol: {symbol})")
public void aClientPlacesAnOrderAtTypeGTCSymbolEUR_USD(long clientId, String side, long orderId, long price, long size, String orderType, CoreSymbolSpecification symbol) throws InterruptedException {
   aClientPassAnOrder(clientId, side, orderId, price, size, orderType, symbol, 0);
}

@When(value = "A client {long} places an {word} order {long} at {long}@{long} \\(type: {word}, symbol: {symbol}, reservePrice: {long})")
public void aClientPlacesAnOrderAtTypeGTCSymbolEUR_USD(long clientId, String side, long orderId, long price, long size, String orderType, CoreSymbolSpecification symbol, long reservePrice) throws InterruptedException {
   aClientPassAnOrder(clientId, side, orderId, price, size, orderType, symbol, reservePrice);
}

private void aClientPassAnOrder(long clientId, String side, long orderId, long price, long size, String orderType, CoreSymbolSpecification symbol, long reservePrice) throws InterruptedException {

   ApiPlaceOrder.ApiPlaceOrderBuilder builder = ApiPlaceOrder.builder().uid(clientId)
      .id(orderId)   .price(price).size(size).action(OrderAction.valueOf(side))
      .orderType(OrderType.valueOf(orderType)).symbol(symbol.symbolId);

   if(reservePrice > 0){
       builder.reservePrice(reservePrice);
   }

   final ApiPlaceOrder order = builder.build();

   orders.put(orderId, order);

   log.debug("PLACE : {}", order);

   container.submitCommandSync(order, cmd -> {
       assertThat(cmd.resultCode, is(CommandResultCode.SUCCESS));
       assertThat(cmd.orderId, is(orderId));
       assertThat(cmd.uid, is(clientId));
       assertThat(cmd.price, is(price));
       assertThat(cmd.size, is(size));
       assertThat(cmd.action, is(OrderAction.valueOf(side)));
       assertThat(cmd.orderType, is(OrderType.valueOf(orderType)));
       assertThat(cmd.symbol, is(symbol.symbolId));

       OrderStepdefs.this.matcherEvents = cmd.extractEvents();
   });
}

, matcher events, JUnit, .


@And("No trade events")
public void noTradeEvents() {
   assertEquals(0, matcherEvents.size());
}

, , ( , ) Cucumber dependency injection.


.


Cucumber - . .


(, :-), ) — .


container . , OrderStepdefs. Cucumber - . Cucumber ExchangeTestContainer, OrderStepdefs OrderBookStepdefs ExchangeTestContainer.


public class OrderBookStepdefs {

   private final ExchangeTestContainer container;

   public OrderBookStepdefs(ExchangeTestContainer container) {
       this.container = container;
   }

   @Then("An {symbol} order book is:")
   public void an_order_book_is(CoreSymbolSpecification symbol, List<List<String>> dataTable) {

       //skip a header if it presents
       if(dataTable.get(0).get(0) != null && dataTable.get(0).get(0).trim().equals("bid")){
           dataTable = dataTable.subList(1, dataTable.size());
       }

       //format | bid | price | ask |
       final L2MarketDataHelper l2helper = new L2MarketDataHelper();
       for(List<String> row : dataTable){
           int price = Integer.parseInt(row.get(1));

           String bid = row.get(0);
           if(bid != null && bid.length() > 0){
               l2helper.addBid(price, Integer.parseInt(bid));
           } else {
               l2helper.addAsk(price, Integer.parseInt(row.get(2)));
           }
       }
       assertEquals(l2helper.build(), container.requestCurrentOrderBook(symbol.symbolId));
   }

Cucumber :


Feature: An exchange accepts bid\ask orders, manage and publish order book and match cross orders

 Scenario Outline: basic full cycle test

   When A client 1440001 places an ASK order 101 at 1600@7 (type: GTC, symbol: <symbol>)
   And A client 1440001 places an BID order 102 at 1550@4 (type: GTC, symbol: <symbol>, reservePrice: 1561)
   Then An "<symbol>" order book is:
     | bid | price  | ask |
     |     | 1600   |  7  |
     |  4  | 1550   |     |
   And No trade events

   When A client 1440002 places an BID order 201 at 1700@2 (type: IOC, symbol: <symbol>, reservePrice: 1800)
   Then The order 101 is partially matched. LastPx: 1600, LastQty: 2
   And An "<symbol>" order book is:
     |     |  1600   |  5  |
     |  4  |  1550   |     |

   When A client 1440002 places an BID order 202 at 1583@4 (type: GTC, symbol: <symbol>, reservePrice: 1583)
   Then An "<symbol>" order book is:
     |     |  1600   |  5  |
     |  4  |  1583   |     |
     |  4  |  1550   |     |
   And No trade events

   When A client 1440001 moves a price to 1580 of the order 101
   Then The order 202 is fully matched. LastPx: 1583, LastQty: 4
   And An "<symbol>" order book is:
     |     |  1580  |  1  |
     |  4  |  1550  |     |

   Examples:
   | symbol     |
   | EUR_USD    |
   | ETH_XBT    |

- — , {symbol}. github exchange-core Pull Request, .



, ucumber , JUnit. , QA .


Cucumber JUnit. .


Java , , .


Cucumber , - API, .


API, REST API. glue- REST API. Cucumber .


Cucumber — IDE, JetBrains Idea. .


, Idea , :


  • Ctrl + B — Java
  • , regexp. Idea ,
  • — , . , .

- , Cucumber , , , , - . .


— . - . - .


.


, .




All Articles