Spring Boot+cucumber+契约测试

简介: Spring Boot+cucumber+契约测试

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信息。

目录
相关文章
|
24天前
|
Java 测试技术 开发者
必学!Spring Boot 单元测试、Mock 与 TestContainer 的高效使用技巧
【10月更文挑战第18天】 在现代软件开发中,单元测试是保证代码质量的重要手段。Spring Boot提供了强大的测试支持,使得编写和运行测试变得更加简单和高效。本文将深入探讨Spring Boot的单元测试、Mock技术以及TestContainer的高效使用技巧,帮助开发者提升测试效率和代码质量。
130 2
|
1月前
|
XML Java 测试技术
【SpringBoot系列】初识Springboot并搭建测试环境
【SpringBoot系列】初识Springboot并搭建测试环境
73 0
|
1月前
|
安全 Java 数据库
shiro学习一:了解shiro,学习执行shiro的流程。使用springboot的测试模块学习shiro单应用(demo 6个)
这篇文章是关于Apache Shiro权限管理框架的详细学习指南,涵盖了Shiro的基本概念、认证与授权流程,并通过Spring Boot测试模块演示了Shiro在单应用环境下的使用,包括与IniRealm、JdbcRealm的集成以及自定义Realm的实现。
42 3
shiro学习一:了解shiro,学习执行shiro的流程。使用springboot的测试模块学习shiro单应用(demo 6个)
|
27天前
|
存储 人工智能 Java
将 Spring AI 与 LLM 结合使用以生成 Java 测试
AIDocumentLibraryChat 项目通过 GitHub URL 为指定的 Java 类生成测试代码,支持 granite-code 和 deepseek-coder-v2 模型。项目包括控制器、服务和配置,能处理源代码解析、依赖加载及测试代码生成,旨在评估 LLM 对开发测试的支持能力。
34 1
|
1月前
|
XML Java Maven
在 Cucumber 测试中自动将 Cucumber 数据表映射到 Java 对象
在 Cucumber 测试中自动将 Cucumber 数据表映射到 Java 对象
53 7
|
1月前
|
监控 Java Maven
springboot学习二:springboot 初创建 web 项目、修改banner、热部署插件、切换运行环境、springboot参数配置,打包项目并测试成功
这篇文章介绍了如何快速创建Spring Boot项目,包括项目的初始化、结构、打包部署、修改启动Banner、热部署、环境切换和参数配置等基础操作。
120 0
|
2月前
|
JavaScript 前端开发 Java
一文让你了解微服务契约测试
谈到微服务,大家都想到契约测试,到底什么是契约测试呢,为什么要使用契约测试呢,关于这样的文章很多,本文将结合Spring Boot让你了解微服务契约测试。
30 0
一文让你了解微服务契约测试
|
2月前
|
JavaScript 前端开发 Java
Spring Boot+cucumber
本文介绍了使用 Spring Boot 和 Cucumber 进行行为驱动开发的过程。首先,通过 start.spring.io 创建一个包含 Web 依赖的项目,并修改 `pom.xml` 文件以添加相关依赖。接着,展示了如何编写和运行简单的 Hello World 示例。然后,详细描述了一个更复杂的 ATM 服务示例,包括定义功能、编写测试文件、实现服务类以及验证 PIN 码的功能。最后,通过 JUnit 运行测试以确保功能正确。
33 0
Spring Boot+cucumber
|
6天前
|
JSON Java 测试技术
SpringCloud2023实战之接口服务测试工具SpringBootTest
SpringBootTest同时集成了JUnit Jupiter、AssertJ、Hamcrest测试辅助库,使得更容易编写但愿测试代码。
34 3
|
1月前
|
JSON 算法 数据可视化
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
这篇文章是关于如何通过算法接口返回的目标检测结果来计算性能指标的笔记。它涵盖了任务描述、指标分析(包括TP、FP、FN、TN、精准率和召回率),接口处理,数据集处理,以及如何使用实用工具进行文件操作和数据可视化。文章还提供了一些Python代码示例,用于处理图像文件、转换数据格式以及计算目标检测的性能指标。
57 0
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)