Mentimun JVM - Bukan Hanya BDD

Halo semuanya! Ada banyak artikel tentang Mentimun di Habré dan di Internet, tetapi saya ingin memasukkan lima sen saya.



Banyak orang menggunakan Mentimun, dan saya sendiri menemukannya sebagai perpustakaan yang sangat indah. Pendapat pribadi saya adalah berguna bagi setiap pengembang Java untuk mengenal perpustakaan ini bersama dengan kerangka kerja JUnit.


Jika pengetahuan Anda tentang Mentimun sama dengan nol, ada artikel bagus tentang Habré yang lebih baik untuk mulai berkenalan dengan alat ini



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