{"id":24745818,"url":"https://github.com/qianmiopen/interface-test","last_synced_at":"2025-10-10T13:32:21.286Z","repository":{"id":72322779,"uuid":"77043145","full_name":"QianmiOpen/interface-test","owner":"QianmiOpen","description":"接口测试工具，支持Dubbo、HTTP接口","archived":false,"fork":false,"pushed_at":"2016-12-22T08:14:59.000Z","size":435,"stargazers_count":85,"open_issues_count":1,"forks_count":56,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-07-27T12:46:37.507Z","etag":null,"topics":["dubbo","edges","testcase","testsuit"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/QianmiOpen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-12-21T10:46:32.000Z","updated_at":"2025-06-13T02:26:29.000Z","dependencies_parsed_at":"2023-05-18T01:15:33.619Z","dependency_job_id":null,"html_url":"https://github.com/QianmiOpen/interface-test","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/QianmiOpen/interface-test","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QianmiOpen%2Finterface-test","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QianmiOpen%2Finterface-test/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QianmiOpen%2Finterface-test/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QianmiOpen%2Finterface-test/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/QianmiOpen","download_url":"https://codeload.github.com/QianmiOpen/interface-test/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/QianmiOpen%2Finterface-test/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279004062,"owners_count":26083667,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dubbo","edges","testcase","testsuit"],"created_at":"2025-01-28T03:29:53.428Z","updated_at":"2025-10-10T13:32:21.280Z","avatar_url":"https://github.com/QianmiOpen.png","language":"Java","readme":"接口测试工具\n==============\n[![Build Status](https://travis-ci.org/QianmiOpen/interface-test.svg?branch=master)](https://travis-ci.org/QianmiOpen/interface-test)\n\n目标：降低接口测试难度、帮助开发人员快速测试接口。\u003cbr/\u003e\n目前已支持dubbo接口测试（需要借助[Edge](https://github.com/qianmiopen/edge)将dubbo接口以http形式发布），后期可以经过简单修改就能支持rest接口测试。\n测试人员通过json、js语言编写用例文件，通过maven插件执行用例，并生成报告；\n![image](report-demo.png)\n\n\u003e 与Edge比较\u003cbr/\u003e\n\u003e Edge擅长的是以可视化的方式、实时的对接口进行测试，测试结果返回后需要人为的check。比较适合在开发、联调阶段使用。\u003cbr/\u003e\n\u003e interface-test将用例以脚本的方式保存下来，通过脚本自定义check规则，程序自动判断接口测试结果，用例可以回放执行。比较适合用在服务重构后接口的兼容检查、版本上线前的接口检测。\u003cbr/\u003e\n\u003e 测试dubbo接口时，interface-test工具依赖Edge工具的测试接口。\u003cbr/\u003e\n\n## 使用介绍\n\n添加依赖\n```xml\n\u003cdependencies\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003ecom.qianmi\u003c/groupId\u003e\n        \u003cartifactId\u003einterface-test\u003c/artifactId\u003e\n        \u003cversion\u003e1.0.5-RELEASE\u003c/version\u003e\n        \u003cscope\u003eruntime\u003c/scope\u003e\n    \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n指定build plugin\n```xml\n\u003cplugin\u003e\n    \u003cgroupId\u003eorg.codehaus.mojo\u003c/groupId\u003e\n    \u003cartifactId\u003eexec-maven-plugin\u003c/artifactId\u003e\n    \u003cversion\u003e1.5.0\u003c/version\u003e\n    \u003cexecutions\u003e\n        \u003cexecution\u003e\n            \u003cid\u003erun-testcase\u003c/id\u003e\n            \u003cgoals\u003e\n                \u003cgoal\u003ejava\u003c/goal\u003e\n            \u003c/goals\u003e\n        \u003c/execution\u003e\n    \u003c/executions\u003e\n    \u003cconfiguration\u003e\n        \u003cclasspathScope\u003eruntime\u003c/classpathScope\u003e\n        \u003cmainClass\u003eorg.springframework.boot.loader.JarLauncher\u003c/mainClass\u003e\n        \u003carguments\u003e\n            \u003cargument\u003e-Dspring.config.location=${basedir}/application.properties\u003c/argument\u003e \u003c!--指定运行配置文件--\u003e\n        \u003c/arguments\u003e\n    \u003c/configuration\u003e\n\u003c/plugin\u003e\n```\n\napplication.properties\n```properties\nlogging.level.root=info\nlogging.level.com.qianmi=debug\nlogging.level.com.qianmi.tda.report=info\n\n## 测试用例根目录\ntest-suit-home=${user.dir}/testcase/com/qianmi/pc/stock/api\n## 测试用例文件扩展名\ntest-case-file-extensions=.ts.json, .ts.js\n## 测试报告根目录\ntest-reports-home=${user.dir}/reports\n## 默认dubbo测试服务器地址(Edge服务地址)\ndefault-test-server-url=http://172.19.65.96:8080/executeTest.do\n\nhttp.client.connection-manager-default-max-per-route=1000\nhttp.client.request-connect-timeout=2000\nhttp.client.connection-manager-max-total=1000\nhttp.client.request-read-timeout=100000\n\n```\n\n使用demo: [interface-test-demo](https://github.com/qianmiopen/inderface-demo)\n\n### 创建测试套（TestSuit）\n#### TestCase模板：\n```json\n   {\n      \"name\": \"case1\",\n      \"params\": [],\n      \"expects\": [\n        {\n          \"operator\": \"=\",\n          \"path\": \"$\",\n          \"value\": {}\n        }\n      ]\n    }\n```\n| Field              |Type      | Description                                                        |\n| :------------------|:-------- | :----------------------------------------------------------------- |\n| name               |String    | 用例名称，建议根据测试场景命名，不可为空                                  |\n| params             |Object[]  | 参数列表，待测接口的JSON格式                                            |\n| expects            |Expect[]  | 断言数组。|\n\n\u003e\n operator: 期待值与真实值的比较操作符，支持“=、!=、\u003c、\u003c=、\u003e、\u003e=、contains、!contains、match、!match”;\u003cbr/\u003e\n path: 从接口返回结果取值的路径，参见 [JsonPath表达式](#jsonpath表达式); \u003cbr/\u003e\n value: 期待值\u003cbr/\u003e\n\n\n#### TestSuit模板：\n```json\n{\n  \"dubboServiceURL\": \"dubbo://172.19.65.199:20880\",\n  \"execOrder\": 1,\n  \"intfName\": \"com.qianmi.pc.api.app.AppProductProvider:1.0.0@addFromOwner\",\n  \"testCases\": [{}],\n  \"testServerURL\": null\n}\n```\n\n| Field              |Type      | Description                                                        |\n| :------------------|:-------- | :----------------------------------------------------------------- |\n| dubboServiceURL    |String    | dubbo服务地址，可为空，未指定时从dubbo注册中心上自动查找                 |\n| execOrder          |Number    | 测试套执行顺序，默认为1，值越小优先级越高                                |\n| intfName           |String    | 待测接口名，不填写时将根据文件名猜测                                    |\n| testCases          |TestCase[]| 测试用例数组，参见[TestCase模板](#testcase模板)                               |\n| testServerURL      |String    | 测试目标服务器地址，测试dubbo接口时，此处配置[Edge](https://github.com/qianmiopen/edge)服务器代理地址           |\n\n\u003e intfName为空时，系统会根绝文件路径与文件名进行猜测。例如：在%TEST_SUIT_HOME%目录下放置\"com/qianmi/pc/api/app/AppProductProvider#1.0.0@addFromOwner.ts.json\"文件，intfName将会自动猜测为：\"com.qianmi.pc.api.app.AppProductProvider:1.0.0@addFromOwner\"\n\n\n完整TestSuit示例：\n```json\n{\n  \"dubboServiceURL\": \"dubbo://172.19.65.199:20880\",\n  \"execOrder\": 1,\n  \"intfName\": \"com.qianmi.pc.api.app.AppProductProvider:1.0.0@addFromOwner\",\n  \"testCases\": [\n    {\n      \"name\": \"成功从供应商添加商品\",\n      \"params\": [\n        {\n          \"chainMasterId\": \"A1295139\",\n          \"commissionType\": 1,\n          \"cost\": 0,\n          \"optUserCode\": null,\n          \"optUserName\": null,\n          \"price\": 0,\n          \"productId\": \"1300607\",\n          \"shopStatus\": 0,\n          \"stock\": 0,\n          \"supplierId\": \"A1437887\"\n        }\n      ],\n      \"expects\": [\n        {\n          \"operator\": \"=\",\n          \"path\": \"$.sourceChainMasterId\",\n          \"value\": \"A1295139\"\n        },{\n          \"operator\": \"=\",\n          \"path\": \"$.productId\",\n          \"value\": \"1300607\"\n        },{\n          \"operator\": \"\u003e=\",\n          \"path\": \"$.goodsIds.length()\",\n          \"value\": 1\n        }\n      ]\n    }\n  ],\n  \"testServerURL\": \"http://172.19.65.96:8080/executeTest.do\"\n}\n```\n\u003e TestSuit文件存放路径以及命名规则:\u003cbr/\u003e\n\u003e 默认TEST_SUIT_HOME目录在System.getProperty(\"user.dir\")下的testcase目录。\u003cbr/\u003e\n\u003e src/test/java/com/qianmi/tda/TestCaseGenerator.java提供了根据ES接口日志自动生成TestSuit的方法。\n\n### 执行测试\n\u003e打开com.qianmi.tda.InterfaceTestApplication，执行main方法即可。测试报告存放在TEST_SUIT_HOME下。\n\n\n### [JsonPath](https://github.com/jayway/JsonPath)表达式\nJsonPath expressions can use the dot–notation\n\n`$.store.book[0].title`\n\nor the bracket–notation\n\n`$['store']['book'][0]['title']`\n\n#### Operators\n\n| Operator                  | Description                                                        |\n| :------------------------ | :----------------------------------------------------------------- |\n| `$`                       | The root element to query. This starts all path expressions.       |\n| `@`                       | The current node being processed by a filter predicate.            |\n| `*`                       | Wildcard. Available anywhere a name or numeric are required.       |\n| `..`                      | Deep scan. Available anywhere a name is required.                  |\n| `.\u003cname\u003e`                 | Dot-notated child                                                  |\n| `['\u003cname\u003e' (, '\u003cname\u003e')]` | Bracket-notated child or children                                  |\n| `[\u003cnumber\u003e (, \u003cnumber\u003e)]` | Array index or indexes                                             |\n| `[start:end]`             | Array slice operator                                               |\n| `[?(\u003cexpression\u003e)]`       | Filter expression. Expression must evaluate to a boolean value.    |\n\n\n#### Functions\n\nFunctions can be invoked at the tail end of a path - the input to a function is the output of the path expression.\nThe function output is dictated by the function itself.\n\n| Function                  | Description                                                        | Output    |\n| :------------------------ | :----------------------------------------------------------------- |-----------|\n| min()                    | Provides the min value of an array of numbers                       | Double    |\n| max()                    | Provides the max value of an array of numbers                       | Double    |\n| avg()                    | Provides the average value of an array of numbers                   | Double    |\n| stddev()                 | Provides the standard deviation value of an array of numbers        | Double    |\n| length()                 | Provides the length of an array                                     | Integer   |\n\n\n#### Filter Operators\n\nFilters are logical expressions used to filter arrays. A typical filter would be `[?(@.age \u003e 18)]` where `@` represents the current item being processed. More complex filters can be created with logical operators `\u0026\u0026` and `||`. String literals must be enclosed by single or double quotes (`[?(@.color == 'blue')]` or `[?(@.color == \"blue\")]`).   \n\n| Operator                 | Description                                                       |\n| :----------------------- | :---------------------------------------------------------------- |\n| ==                       | left is equal to right (note that 1 is not equal to '1')          |\n| !=                       | left is not equal to right                                        |\n| \u003c                        | left is less than right                                           |\n| \u003c=                       | left is less or equal to right                                    |\n| \u003e                        | left is greater than right                                        |\n| \u003e=                       | left is greater than or equal to right                            |\n| =~                       | left matches regular expression  [?(@.name =~ /foo.*?/i)]         |\n| in                       | left exists in right [?(@.size in ['S', 'M'])]                    |\n| nin                      | left does not exists in right                                     |\n| size                     | size of left (array or string) should match right                 |\n| empty                    | left (array or string) should be empty                            |\n\n\nPath Examples\n-------------\n\nGiven the json\n\n```json\n{\n    \"store\": {\n        \"book\": [\n            {\n                \"category\": \"reference\",\n                \"author\": \"Nigel Rees\",\n                \"title\": \"Sayings of the Century\",\n                \"price\": 8.95\n            },\n            {\n                \"category\": \"fiction\",\n                \"author\": \"Evelyn Waugh\",\n                \"title\": \"Sword of Honour\",\n                \"price\": 12.99\n            },\n            {\n                \"category\": \"fiction\",\n                \"author\": \"Herman Melville\",\n                \"title\": \"Moby Dick\",\n                \"isbn\": \"0-553-21311-3\",\n                \"price\": 8.99\n            },\n            {\n                \"category\": \"fiction\",\n                \"author\": \"J. R. R. Tolkien\",\n                \"title\": \"The Lord of the Rings\",\n                \"isbn\": \"0-395-19395-8\",\n                \"price\": 22.99\n            }\n        ],\n        \"bicycle\": {\n            \"color\": \"red\",\n            \"price\": 19.95\n        }\n    },\n    \"expensive\": 10\n}\n```\n\n| JsonPath (click link to try)| Result |\n| :------- | :----- |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$.store.book[*].author\" target=\"_blank\"\u003e$.store.book[*].author\u003c/a\u003e| The authors of all books     |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$..author\" target=\"_blank\"\u003e$..author\u003c/a\u003e                   | All authors                         |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$.store.*\" target=\"_blank\"\u003e$.store.*\u003c/a\u003e                  | All things, both books and bicycles  |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$.store..price\" target=\"_blank\"\u003e$.store..price\u003c/a\u003e             | The price of everything         |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$..book[2]\" target=\"_blank\"\u003e$..book[2]\u003c/a\u003e                 | The third book                      |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$..book[0,1]\" target=\"_blank\"\u003e$..book[0,1]\u003c/a\u003e               | The first two books               |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$..book[:2]\" target=\"_blank\"\u003e$..book[:2]\u003c/a\u003e                | All books from index 0 (inclusive) until index 2 (exclusive) |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$..book[1:2]\" target=\"_blank\"\u003e$..book[1:2]\u003c/a\u003e                | All books from index 1 (inclusive) until index 2 (exclusive) |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$..book[-2:]\" target=\"_blank\"\u003e$..book[-2:]\u003c/a\u003e                | Last two books                   |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$..book[2:]\" target=\"_blank\"\u003e$..book[2:]\u003c/a\u003e                | Book number two from tail          |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$..book[?(@.isbn)]\" target=\"_blank\"\u003e$..book[?(@.isbn)]\u003c/a\u003e          | All books with an ISBN number         |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$.store.book[?(@.price \u003c 10)]\" target=\"_blank\"\u003e$.store.book[?(@.price \u003c 10)]\u003c/a\u003e | All books in store cheaper than 10  |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$..book[?(@.price \u003c= $['expensive'])]\" target=\"_blank\"\u003e$..book[?(@.price \u003c= $['expensive'])]\u003c/a\u003e | All books in store that are not \"expensive\"  |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$..book[?(@.author =~ /.*REES/i)]\" target=\"_blank\"\u003e$..book[?(@.author =~ /.*REES/i)]\u003c/a\u003e | All books matching regex (ignore case)  |\n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$..*\" target=\"_blank\"\u003e$..*\u003c/a\u003e                        | Give me every thing   \n| \u003ca href=\"http://jsonpath.herokuapp.com/?path=$..book.length()\" target=\"_blank\"\u003e$..book.length()\u003c/a\u003e                 | The number of books                      |\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqianmiopen%2Finterface-test","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fqianmiopen%2Finterface-test","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqianmiopen%2Finterface-test/lists"}