前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >真香系列之2-自动录制回放的Hoverfly-java-Junit5

真香系列之2-自动录制回放的Hoverfly-java-Junit5

作者头像
Antony
发布2021-06-28 10:40:39
1.2K0
发布2021-06-28 10:40:39
举报

@HoverflyCapture

在之前的文章《真香系列之1-Hoverfly服务虚拟化,你不2的选择》中简单介绍了Hoverfly。本文将介绍如何在JUnit5中使用Hoverfly,并讨论入参匹配、延迟、特性增强等话题。

利用Hoverfly作为HTTP代理的原理,可以非常轻松地将HTTP网络流量抓取下来进行录制并保存为文件。有如下来自官网的案例,

代码语言:javascript
复制
@HoverflyCapture(path = "src/test/resources/hoverfly",
            filename = "captured-simulation.json",
  config = @HoverflyConfig(captureAllHeaders = true, 
  proxyLocalHost = true))
            @ExtendWith(HoverflyExtension.class)
            class CaptureTests {
    // ...
    }

这其中,如果不写path的话,将会默认保存到"src/test/resources/hoverfly"目录下。实际项目中,可以考虑将录制的文件保存到与测试类相同的包内,以便于查找和维护关联关系。

这里要注意的是,默认情况下处于录制状态时,如果指定文件已经存在,Hoverfly将会直接将文件内容根据本次录制的结果进行覆盖,而不是在文末进行增补。

@HoverflySimulate

录制得来或者自行编写的模拟文件可以用来回放,

代码语言:javascript
复制
@HoverflySimulate(source = @HoverflySimulate.Source(
value = "test-service-https.json", 
type = HoverflySimulate.SourceType.CLASSPATH))
@ExtendWith(HoverflyExtension.class)
class SimulationTests {
    // ...
    }

其中的source用于指定模拟文件名称和类型。

代码语言:javascript
复制
enum SourceType {    
DEFAULT_PATH, //一般是"src/test/resources/hoverfly"    
CLASSPATH,     
URL,    
FILE
}

CASSPATH, URL, FILE}

其中使用比较多得的是CLASSPATH或者是FILE。

enableAutoCapture

如前一小节中所介绍的,Hoverfly相对于一般的API模拟工具一个易用性提升的地方就是支持自动的录制加回放。只需要在@HoverflySimulate的注解中额外增加一个配置即可。

代码语言:javascript
复制
@HoverflySimulate(source = @Source(
value = simulation.json", type = SourceType.FILE),
    enableAutoCapture = true)

配置项enableAutoCapture 为true的话,就会在用例执行前判断

1)如果指定文件不存在,则当前为录制状态,Hoverfly将记录往来流量,并在指定目录保存为指定文件。

2)如果指定文件存在,则认为当前处于回放状态。Hoverfly将根据该文件提供的内容进行回放。

处于模拟状态时,如果发生了用例中的请求与模拟文件中的任一请求均不匹配的,Hoverfly也不会再将请求转发给真实的目标,而是直接抛出无法匹配的异常。

另外,Hoverfly还提供了Diff、Spy、Synthesize、Modify等模式。详细的各个模式介绍可以参见刘冉的《软件测试中的服务虚拟化(Service Virtualization)》一文

以下是笔者整理的一个Hoverfly工作模式简表,可以看到JUnit5或者Junit4目前只是提供了一部分Hoverfly的功能。

介绍完了Hoverfly在Junit5中的基本使用,再就几个实践中遇到的话题简单介绍一下。

参数匹配

Hoverfly支持三种简单的参数匹配模式,分别是精确匹配(Exact)、模糊匹配(Glob)和正则匹配(Regex)

默认情况下,Hoverfly采用的精确匹配,如下例

代码语言:javascript
复制
"path": [   {      
"matcher": "exact",      
"value": "/api/bookings/1"   }],
"method": [   {      
"matcher": "exact",      
"value": "GET"   }],

这是对某个HTTP接口请求的模拟案例,业务含义为查询ID为1的书籍信息。如果模拟的接口的入参从1变成了2,也就是path的值变成了“/api/bookings/2”,或者是请求方法从GET变成了POST,那么Hoverfly就会认为是匹配失败,而不再使用上述模拟数据了。

在某些场景中,如果希望无论”/api/bookings”这个接口所附带的参数是什么,也就是客户端无论发送查询什么书籍的请求,都希望可以匹配并返回相同的信息。这就需要使用到模糊匹配了。”path”部分修改如下,

代码语言:javascript
复制
"path": [   {      
"matcher": "glob",      
"value": "/api/bookings/*"   }],

将”matcher”从”exact”修改为”glob”,同时在”value”中通过”*”来通配所有值。这样,类似“/api/bookings/2”这样的请求也可以匹配了。

那从业务逻辑的角度,可能书籍ID规定只是数字,因此通过通配符来匹配有些过于宽泛,希望能只匹配数字。Hoverfly也可以通过正则表达式来实现上述需求。相应的“path”匹配也可以修改为,

代码语言:javascript
复制
"path": [   {      
"matcher": "exact",      
"value": "/api/bookings/1"   }],
"method": [   {      
"matcher": "exact",      
"value": "GET"   }],

这样,Hoverfly只会匹配类似“/api/bookings/123”这样的请求,而类似“/api/bookings/a23”这样的请求则会被忽略。

模糊匹配和正则匹配还可以用于如日期、序号等接口请求中常见的场景,也通过这些匹配模式可以进一步提升Hoverfly在实际项目中的适用程度。

除了上述三种匹配方式之外,Hoverfly还支持XML和JSON格式的匹配,包括严格匹配以及部分匹配等逻辑。

模拟延迟

模拟接口的延迟也是接口测试中一个常见的场景。以下是Hoverfly的模拟文件中对某个指定接口实现固定的延迟。

代码语言:javascript
复制
{

    "request": {

        "path": [

            {"matcher": "exact", "value": "/api/profile"}

        ],

        "headers": {

            "X-API-Version": [

                {"matcher": "exact", "value": "v1"}

            ]

        }

    },

    "response": {

        "status": 404,

        "body": "Page not found",

        "fixedDelay": 3000

    }}

除了固定延迟之外,还可以通过logNormalDelay 来指定随机延迟。除了指定某个接口之外,还可以指定全局的延迟

无法录制的问题

在引入Hoverfly进行试点的初期,发现虽然示例用例可以跑通,但是在内部项目中无法对HTTP服务间调用进行录制。通过排查发现,内部项目使用了HttpClient来进行服务间调用,不过这个HttpClient来自于

代码语言:javascript
复制
HttpClient httpClient = HttpClients.createDefault()

而根据Hoverfly官网的说法,应该使用如下的方式来初始化HttpClient,

代码语言:javascript
复制
HttpClient httpClient = HttpClients.createSystem();
// orHttpClient httpClient = HttpClientBuilder.create().useSystemProperties().build();

回顾一下Hoverfly的工作原理,可以知道,Hoverfly-Java将启动Hoverfly服务,并作为HTTP Proxy,将HttpClient与服务端的往来通信拦截之后,进行流量的录制或者回放。

在Hoverfly-java中,有io.specto.hoverfly.junit.core.ProxyConfigurer类来负责相关的这些设置。

代码语言:javascript
复制
setProxySystemProperties() {    
//...    
keepOriginalProxyProperties(HTTP_NON_PROXY_HOSTS, HTTP_PROXY_HOST, HTTP_PROXY_PORT, HTTPS_PROXY_HOST, HTTPS_PROXY_PORT);    
LOGGER.info("Setting proxy host to {}", hoverflyConfig.getHost());    
System.setProperty(HTTP_PROXY_HOST, hoverflyConfig.getHost());    
System.setProperty(HTTPS_PROXY_HOST, hoverflyConfig.getHost());   
//...    
LOGGER.info("Setting proxy proxyPort to {}", hoverflyConfig.getProxyPort());    
System.setProperty(HTTP_PROXY_PORT, String.valueOf(hoverflyConfig.getProxyPort()));    
System.setProperty(HTTPS_PROXY_PORT, String.valueOf(hoverflyConfig.getProxyPort()));}

可以看到,在这个setProxySystemProperties方法中,为JVM设置了HTTP_PROXY_HOST、HTTP_PROXY_PORT的系统变量。因此,如果使用了

代码语言:javascript
复制
HttpClientBuilder.create().useSystemProperties().build();

来获取HTTPClient实例,就可以将上述属性设置到HTTPClient之中,也就是将Hoverfly设置为HttpClient的代理服务器,从而发挥作用了。如果使用了

HttpClients.createDefault()

则不会携带上述系统变量,Hoverfly也就无从下手,发挥作用了。

因此,如果发现Hoverfly能顺利运行,但是没有录制成功,可以考虑排查一下服务间调用所使用的HTTP客户端是否携带了上述配置项。

增强:如何对录制结果进行修改

在实际的项目中,当服务间进行内部服务调用时,出于鉴权的需要,会在请求体中带上timeStamp,token等信息。这些信息经过录制之后会存放在指定的JSON文件之中。为了能够在用例执行时,可以让用例能够正确执行,需要手工将JSON文件中的匹配模式修改为glob,并将中的timeStamp,token的具体值修改为通配符*。如以下的案例,

代码语言:javascript
复制
"body": [    {       
"matcher": "glob",        
"value": "timeStamp=*,token=*"    }],

由于手工修改较为繁琐,因此考虑通过客制化Hoverfly来实现这个目标。根据JUnit5的扩展机制,可以了解到Hoverfly-java-junit5是在HoverflyExtension中管理JSON文件导出的。

代码语言:javascript
复制
@Overridepublic void afterAll(ExtensionContext context) {    
if (isRunning()) {                
try {            
verifyHoverflyValidate(context);            
if (this.capturePath != null) {                
  this.hoverfly.exportSimulation(this.capturePath);
              } 
               }  finally {
              this.hoverfly.close();            
this.hoverfly = null;       
 }    
 }}

而Hoverfly的exportSimulation方法的具体定义如下,

代码语言:javascript
复制
/** * Exports a simulation and stores it on the filesystem at the given path 
* * @param path the path on the filesystem to where the simulation should be stored 
*/
public void exportSimulation(Path path) {    
if (path == null) {        
throw new IllegalArgumentException("Export path cannot be null.");    
}    
LOGGER.info("Exporting simulation data from Hoverfly");
    try {
            Files.deleteIfExists(path);
            final Simulation simulation = hoverflyClient.getSimulation();
                    persistSimulation(path, simulation);
                        } catch (Exception e) {        
        LOGGER.error("Failed to export simulation data", e);
    }}

其中的hoverflyClient.getSimulation就是负责从Hoverfly获取模拟的数据。因此可以有两个方案

1)修改已经生成的JSON文件

2)修改从hoverflyClient.getSimulation获取的Simulation数据,并保存成文件

由于Hoverfly-java并没有类似提供类似Middleware的接口来获取Simulation数据并进行修改,第二个方案较为复杂。因此可以考虑第一个方案,也就是

1)继承HoverflyExtension并复写afterAll方法,

2)首先根据现有方法来生成JSON文件,

3)然后根据capturePath来获取已生成的文件,并编写modify方法来修改并保存这个文件。

其余在项目感觉Hoverfly-java特别是JUnit5中需要的feature还可以有,

1)模拟数据聚合

考虑到对于某些请求可能有相同的应答,而某些用例的相同请求需要返回不同的结果。这种模式下可以考虑通过将不同内容的JSON文件通过父类类和子类的方式来实现复用和隔离,或者是通过类和方法上的注解来实现。不过目前来看,@HoverflySimulate注解只能在类上使用,不能注解在方法上,也不支持通过继承关系将两个或者多个@@HoverflySimulate注解提供的JSON文件的内容进行聚合来提供模拟数据。

2) 增量录制

@HoverflySimulate中的自动录制功能非常使用,但是该注解也约定,Hoverfly在发现请求响应文件后,只使用该文件进行匹配,而不是去向实际的对端微服务发送请求。如果在一个测试类中存放多个测试用例,在用例开发过程中,需要分开进行录制,最后进行请求/响应文件内容的合并。由于新用例所需的请求内容未匹配到,因此用例会执行失败。所以用例需要逐条开发并merge到最终的测试类中。由于Hoverfly-core包括中其实是支持增量录制的。

代码语言:javascript
复制
if (hoverfly.getHoverflyConfig().isIncrementalCapture()
 && capturePath != null 
 && Files.isReadable(capturePath)) {
    hoverfly.simulate(SimulationSource.file(capturePath));
    }

因此,也希望Hoverfly团队能将该功能在JUnit5中实现。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-03-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 软件测试那些事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档