大家好!在Habré和Internet上有很多关于黄瓜的文章,但是我想插入我的5美分。
许多人都使用Cucumber,而我自己也发现它是一个非常出色的库。我个人的观点是,对于每个Java开发人员来说,熟悉此库以及JUnit框架都是很有用的。
如果您对黄瓜的知识为零,那么在Habré上有很多不错的文章,最好是与该工具开始熟悉
. , , , 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:
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) {
if(dataTable.get(0).get(0) != null && dataTable.get(0).get(0).trim().equals("bid")){
dataTable = dataTable.subList(1, dataTable.size());
}
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 , , , , - . .
— . - . - .
.
, .