1.使用start.spring.io创建一个“web”项目。在“依赖项”对话框中搜索并添加“web”依赖项,为了后面的契约文件,再加入“Config Client ”和“Contract Stub Runner依赖项。点击“生成”按钮,下载zip,并将其解压缩到计算机上的文件夹中。
2.pom.xml
代码语言:javascript复制<?xml version="1.0"
encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>com.example</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<java.version>11</java.version>
<cucumber.version>6.8.1</cucumber.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-spring</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>4.1.0</version>
<extensions>true</extensions>
<configuration>
<testFramework>JUNIT5</testFramework>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3 目录结构如下
4 ATMService.feature
代码语言:javascript复制功能:验证密码
作为银行储户
我想要在 ATM 上验证密码
以便我可以安全地进行操作
场景:验证密码成功
假如储户拥有一张卡号为"1111222233"的借记卡
并且密码为"123456"
并且储户借记卡账户余额为"100.00"元
当储户将卡插入ATM
并且储户选择查询余额
那么提示储户输入密码
并且输入密码"123456"
那么储户可以看到密码正确的提示
场景:验证密码失败
假如储户拥有一张卡号为"1111222233"的借记卡
并且密码为"123456"
并且储户借记卡账户余额为"100.00"元
当储户将卡插入ATM
并且储户选择查询余额
那么提示储户输入密码
并且输入密码"654321"
那么储户可以看到密码错误的提示
5 先来看看测试文件
代码语言:javascript复制MyDemoApplicationTests.java
package com.example.ATMService;
import io.cucumber.junit.platform.engine.Cucumber;
import io.cucumber.spring.CucumberContextConfiguration;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@Cucumber
@CucumberContextConfiguration
@SpringBootTest
class MyDemoApplicationTests {
@Test
void contextLoads() {
}
}
VerifyPINStepDefinitions.java
代码语言:javascript复制package com.example.ATMService;
//mvn spring-cloud-contract:convert;mvn spring-cloud-contract:run
//http://localhost:8080/verify_pin/1111222233/123456
import com.example.ATMService.domain.model.Account;
import com.example.ATMService.domain.model.DebitCard;
import com.example.ATMService.performer.ATM;
import com.example.ATMService.performer.Customer;
import io.cucumber.java.zh_cn.假如;
import io.cucumber.java.zh_cn.当;
import io.cucumber.java.zh_cn.那么;
import io.cucumber.junit.platform.engine.Cucumber;
import org.junit.jupiter.api.Assertions;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@Cucumber
public class VerifyPINStepDefinitions {
private final Customer customer = new Customer();
private final ATM atm = new ATM();
@假如("储户拥有一张卡号为"{int}"的借记卡")
public void 储户拥有一张卡号为_的借记卡(Integer cardIdInteger){
Long cardId = cardIdInteger.longValue();
this.customer.haveCard(new DebitCard(cardId));
}
@假如("密码为"{int}"")
public void 密码为(Integer PIN){
this.customer.setDebitCardPIN(PIN);
}
@假如("储户借记卡账户余额为"{double}"元")
public void 储户借记卡账户余额为_元(Double balance){
this.customer.setCardAccount(new Account(balance));
}
@假如("ATM已经初始化")
public void ATM已经初始化(){
this.atm.init();
}
@当("储户将卡插入ATM")
public void 储户将卡插入atm(){
this.customer.insertCardToATM(atm);
}
@当("储户选择查询余额")
public void 储户选择查询余额(){
this.customer.queryBalanceOn(atm);
}
@那么("提示储户输入密码")
public void 提示储户输入密码(){
Assertions.assertEquals("Please input PIN:", this.atm.getScreenMessage());
}
@那么("输入密码"{int}"")
public void 输入密码(Integer pin){
this.customer.enterPIN(this.atm, pin);
}
@那么("储户可以看到密码正确的提示")
public void 储户可以看到密码正确的提示(){
Assertions.assertEquals("your PIN is invalid.", this.atm.getScreenMessage());
}
@那么("储户可以看到密码错误的提示")
public void 储户可以看到密码错误的提示(){
Assertions.assertEquals("your PIN is invalid.", this.atm.getScreenMessage());
}
}
com.example.ATMService.domain.model目录下
Account.java
代码语言:javascript复制package com.example.ATMService.domain.model;
public class Account {
private final Double balance;
public Account(Double balance) {
this.balance = balance;
}
public double getBalance() {
return balance;
}
}
com.example.ATMService.domain.model目录下
DebitCard.java
代码语言:javascript复制package com.example.ATMService.domain.model;
public class DebitCard {
private Integer PIN= -1;
private final Long cardId;
private Account account;
public DebitCard(Long cardId){
this.cardId = cardId;
}
public void setPIN(Integer pin){
this.PIN = pin.intValue();
}
public void setAccount(Account account) {
this.account = account;
}
public double getBalance() {
return this.account.getBalance();
}
public boolean verifyPIN(Integer pin) {
return this.PIN.intValue()== pin;
}
public Long getCardID() {
return this.cardId;
}
}
com.example.ATMService.domain.service目录下
DebitCardService.java
代码语言:javascript复制package com.example.ATMService.domain.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Service
public class DebitCardService {
@Value("${card-service.host}")
private String cardServiceHost;
public boolean verifyPIN(Long cardID, Integer pin){
RestTemplate template = new RestTemplate();
try{
String requestURL= String.format("http://%s/verify_pin/%d/%d",this.cardServiceHost, cardID,pin);
ResponseEntity
Map body= entity.getBody();
return "OK".equals(body.get("result"))&&(entity.getStatusCode()== HttpStatus.ACCEPTED);
}catch (Exception e) {
return false;
}
}
}
com.example.ATMService.performer目录下
ATM.java
代码语言:javascript复制package com.example.ATMService.performer;
import com.example.ATMService.domain.model.DebitCard;
import com.example.ATMService.domain.service.DebitCardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ATM{
@Autowired
private DebitCardService debitCardService = new DebitCardService();
private DebitCard card;
private String screenMessage;
private boolean verifiedPIN = false;
public void init() {
this.verifiedPIN = false;
this.card = null;
this.screenMessage = null;
}
public void insertCard(DebitCard debitCard) {
this.card = debitCard;
}
public void queryBalance() {
if(this.verifiedPIN){
this.screenMessage = String.format("Your balance is: %f", this.card.getBalance());
}else {
this.screenMessage = String.format("Please input PIN:");
}
}
public String getScreenMessage() {
return this.screenMessage;
}
public void enterPlN(Integer pin) {
this.verifiedPIN = this.debitCardService.verifyPIN(this.card.getCardID(), pin);
if (!this.verifiedPIN) {
this.screenMessage ="your PIN is invalid.";
}else {
this.queryBalance();
}
}
}
com.example.ATMService.performer目录下
Customer.java
代码语言:javascript复制package com.example.ATMService.performer;
import com.example.ATMService.domain.model.Account;
import com.example.ATMService.domain.model.DebitCard;
public class Customer {
private DebitCard debitCard;
public void haveCard(DebitCard debitCard) {
this.debitCard = debitCard;
}
public void setDebitCardPIN(Integer pin){
this.debitCard.setPIN(pin);
}
public void setCardAccount(Account account) {
this.debitCard.setAccount(account);
}
public void insertCardToATM(ATM atm){
//atm.reset();
atm.insertCard(this.debitCard);
}
public void queryBalanceOn(ATM atm) {
atm.queryBalance();
}
public void enterPIN(ATM atm, Integer pin) {
atm.enterPlN(pin);
}
}
6 用JUnit运行VerifyPINStepDefinitions.Java
7 在/ATMService/src/test/resources/contracts目录下建立
verify_pin.yml
代码语言:javascript复制request:
method: GET
url: /verify_pin/1111222233/123456
response:
status: 200
headers:
Content-Type: application/json;charset=UTF-8
body:
result: "OK"
verify_pin_fail.yml
代码语言:javascript复制reguest:
method: GET
url: /verify_pin/1111222233/654321
respouse:
status: 400
headers:
Contentlype: Appliction/json;chatset=utf-8
body:
result:"Your PlN is apnalnd"
8 运行
代码语言:javascript复制mvn spring-cloud-contract:convert&&mvn spring-cloud-contract:run
在浏览器中输入
代码语言:javascript复制http://127.0.0.1:8080/verify_pin/1111222233/123456
代码语言:javascript复制http://127.0.0.1:8080/verify_pin/1111222233/654321
9.建立另一个Spring Boot card
在/card/target/generated-test-sources/contracts/org/springframework/cloud/contract/verifier/tests/ContractVerifierTest.java中产生
代码语言:javascript复制import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import io.restassured.module.mockmvc.specification.MockMvcRequestSpecification;
import io.restassured.response.ResponseOptions;
import static org.springframework.cloud.contract.verifier.assertion.SpringCloudContractAssertions.assertThat;
import static org.springframework.cloud.contract.verifier.util.ContractVerifierUtil.*;
import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson;
import static io.restassured.module.mockmvc.RestAssuredMockMvc.*;
@SuppressWarnings("rawtypes")
public class ContractVerifierTest {
@Test
public void validate_verify_pin() throws Exception {
// given:
MockMvcRequestSpecification request = given();
// when:
ResponseOptions response = given().spec(request)
.get("/verify_pin/1111222233/123456");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).isEqualTo("application/json;charset=UTF-8");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("['result']").isEqualTo("OK");
}
}
10. 实现卡片controller
代码语言:javascript复制package com.example.card.interfaces;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CardController {
@GetMapping("/verify_pin/{id}/{pin}")
public ResponseEntityverifyPIN(@PathVariable("id") Long id,
@PathVariable("pin") Integer pin) {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("Content-Type", "Application/json;charset-utf-8");
ResponseEntityresponse;
if(id==1111222233 && pin==123456)
response = new ResponseEntity("{"result":"OK"}", responseHeaders, HttpStatus.ACCEPTED);
else
response = new ResponseEntity("{"result":"Your PIN is invalid"}",responseHeaders,HttpStatus.BAD_REQUEST);
return response;
}
}
在卡片项目中运行
代码语言:javascript复制mvn spring-boot:run
启动卡片项目
然后运行amt下的mvn test测试通过
11 我的问题,如何测试在浏览器中输入http://127.0.0.1:8080/verify_pin/1111222233/123456显示pass信息,123456为其他字符显示fail信息。