Gurke JVM - nicht nur BDD

Hallo alle zusammen! Es gibt viele Artikel über Gurken auf Habré und im Internet, aber ich möchte meine fünf Cent einfügen.



Viele benutzen Gurke, und ich selbst finde es eine ziemlich wundervolle Bibliothek. Meine persönliche Meinung ist, dass es für jeden Java-Entwickler nützlich ist, mit dieser Bibliothek zusammen mit dem JUnit-Framework vertraut zu sein.


Wenn Ihr Wissen über Gurke gleich Null ist, gibt es ausgezeichnete Artikel über Habré, mit denen Sie sich besser mit diesem Tool vertraut machen sollten



. , , , 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