回归测试

概念

  1. Suite: 一个测试用例,目前仅用来指代测试用例文件名
  2. Group: 一个测试集,目前仅用于指代测试用例所属的目录
  3. Action: 一个封装好的具体测试行为,比如用于执行sql的sql Action,用于校验结果的test Action,用于导入数据的streamLoad Action等

测试步骤

  1. 需要预先安装好集群
  2. 修改配置文件${DORIS_HOME}/conf/regression-conf.groovy,设置jdbc url、用户等配置项
  3. 创建测试用例文件并编写用例
  4. 如果用例文件包含qt Action,则需要创建关联的的data文件,比如suites/demo/qt_action.groovy这个例子,需要用到data/demo/qt_action.out这个TSV文件来校验输出是否一致
  5. 运行${DORIS_HOME}/run-regression-test.sh测试全部用例,或运行${DORIS_HOME}/run-regression-test.sh --run <suiteName> 测试若干用例,更多例子见”启动脚本例子”章节

目录结构

开发时需要关注的重要文件/目录

  1. run-regression-test.sh: 启动脚本
  2. regression-conf.groovy: 回归测试的默认配置
  3. data: 存放输入数据和输出校验数据
  4. suites: 存放用例
  1. ./${DORIS_HOME}
  2. |-- **run-regression-test.sh** 回归测试启动脚本
  3. |-- regression-test
  4. | |-- conf
  5. | | |-- logback.xml 日志配置文件
  6. | | |-- **regression-conf.groovy** 默认配置文件
  7. | |
  8. | |-- framework 回归测试框架源码
  9. | |-- **data** 用例的输入输出文件
  10. | | |-- demo 存放demo的输入输出文件
  11. | | |-- correctness 存放正确性测试用例的输入输出文件
  12. | | |-- performance 存放性能测试用例的输入输出文件
  13. | | |-- utils 存放其他工具的输入输出文件
  14. | |
  15. | |-- **suites** 回归测试用例
  16. | |-- demo 存放测试用例的demo
  17. | |-- correctness 存放正确性测试用例
  18. | |-- performance 存放性能测试用例
  19. | |-- utils 其他工具
  20. |
  21. |-- output
  22. |-- regression-test
  23. |-- log 回归测试日志

框架默认配置

测试时需要实际情况修改jdbc和fe的配置

  1. /* ============ 一般只需要关注下面这部分 ============ */
  2. // 默认DB,如果未创建,则会尝试创建这个db
  3. defaultDb = "regression_test"
  4. // Jdbc配置
  5. jdbcUrl = "jdbc:mysql://127.0.0.1:9030/?"
  6. jdbcUser = "root"
  7. jdbcPassword = ""
  8. // fe地址配置, 用于stream load
  9. feHttpAddress = "127.0.0.1:8030"
  10. feHttpUser = "root"
  11. feHttpPassword = ""
  12. /* ============ 一般不需要修改下面的部分 ============ */
  13. // DORIS_HOME变量是通过run-regression-test.sh传入的
  14. // 即 java -DDORIS_HOME=./
  15. // 设置回归测试用例的目录
  16. suitePath = "${DORIS_HOME}/regression-test/suites"
  17. // 设置输入输出数据的目录
  18. dataPath = "${DORIS_HOME}/regression-test/data"
  19. // 默认会读所有的组,读多个组可以用半角逗号隔开,如: "demo,performance"
  20. // 一般不需要在配置文件中修改,而是通过run-regression-test.sh来动态指定和覆盖
  21. testGroups = ""
  22. // 默认会读所有的用例, 同样可以使用run-regression-test.sh来动态指定和覆盖
  23. testSuites = ""
  24. // 其他自定义配置
  25. customConf1 = "test_custom_conf_value"

编写用例的步骤

  1. 进入${DORIS_HOME}/regression-test目录
  2. 根据测试的目的来选择用例的目录,正确性测试存在suites/correctness,而性能测试存在suites/performance
  3. 新建一个groovy用例文件,增加若干Action用于测试,Action讲在后续章节具体说明

Action

Action是一个测试框架默认提供的测试行为,使用DSL来定义。

sql action

sql action用于提交sql并获取结果,如果查询失败则会抛出异常

参数如下

  • String sql: 输入的sql字符串
  • return List<List>: 查询结果,如果是DDL/DML,则返回一行一列,唯一的值是updateRowCount

下面的样例代码存放于${DORIS_HOME}/regression-test/suites/demo/sql_action.groovy:

  1. // execute sql and ignore result
  2. sql "show databases"
  3. // execute sql and get result, outer List denote rows, inner List denote columns in a single row
  4. List<List<Object>> tables = sql "show tables"
  5. // assertXxx() will invoke junit5's Assertions.assertXxx() dynamically
  6. assertTrue(tables.size() >= 0) // test rowCount >= 0
  7. // syntax error
  8. try {
  9. sql "a b c d e"
  10. throw new IllegalStateException("Should be syntax error")
  11. } catch (java.sql.SQLException t) {
  12. assertTrue(true)
  13. }
  14. def testTable = "test_sql_action1"
  15. try {
  16. sql "DROP TABLE IF EXISTS ${testTable}"
  17. // multi-line sql
  18. def result1 = sql """
  19. CREATE TABLE IF NOT EXISTS ${testTable} (
  20. id int
  21. )
  22. DISTRIBUTED BY HASH(id) BUCKETS 1
  23. PROPERTIES (
  24. "replication_num" = "1"
  25. )
  26. """
  27. // DDL/DML return 1 row and 1 column, the only value is update row count
  28. assertTrue(result1.size() == 1)
  29. assertTrue(result1[0].size() == 1)
  30. assertTrue(result1[0][0] == 0, "Create table should update 0 rows")
  31. def result2 = sql "INSERT INTO test_sql_action1 values(1), (2), (3)"
  32. assertTrue(result2.size() == 1)
  33. assertTrue(result2[0].size() == 1)
  34. assertTrue(result2[0][0] == 3, "Insert should update 3 rows")
  35. } finally {
  36. /**
  37. * try_xxx(args) means:
  38. *
  39. * try {
  40. * return xxx(args)
  41. * } catch (Throwable t) {
  42. * // do nothing
  43. * }
  44. */
  45. try_sql("DROP TABLE IF EXISTS ${testTable}")
  46. // you can see the error sql will not throw exception and return
  47. try {
  48. def errorSqlResult = try_sql("a b c d e f g")
  49. assertTrue(errorSqlResult == null)
  50. } catch (Throwable t) {
  51. assertTrue(false, "Never catch exception")
  52. }
  53. }
  54. // order_sql(sqlStr) equals to sql(sqlStr, isOrder=true)
  55. // sort result by string dict
  56. def list = order_sql """
  57. select 2
  58. union all
  59. select 1
  60. union all
  61. select null
  62. union all
  63. select 15
  64. union all
  65. select 3
  66. """
  67. assertEquals(null, list[0][0])
  68. assertEquals(1, list[1][0])
  69. assertEquals(15, list[2][0])
  70. assertEquals(2, list[3][0])
  71. assertEquals(3, list[4][0])

qt action

qt action用于提交sql,并使用对应的.out TSV文件来校验结果

  • String sql: 输入sql字符串
  • return void

下面的样例代码存放于${DORIS_HOME}/regression-test/suites/demo/qt_action.groovy:

  1. /**
  2. * qt_xxx sql equals to quickTest(xxx, sql) witch xxx is tag.
  3. * the result will be compare to the relate file: ${DORIS_HOME}/regression_test/data/qt_action.out.
  4. *
  5. * if you want to generate .out tsv file for real execute result. you can run with -genOut or -forceGenOut option.
  6. * e.g
  7. * ${DORIS_HOME}/run-regression-test.sh --run qt_action -genOut
  8. * ${DORIS_HOME}/run-regression-test.sh --run qt_action -forceGenOut
  9. */
  10. qt_select "select 1, 'beijing' union all select 2, 'shanghai'"
  11. qt_select2 "select 2"
  12. // order result by string dict then compare to .out file.
  13. // order_qt_xxx sql equals to quickTest(xxx, sql, true).
  14. order_qt_union_all """
  15. select 2
  16. union all
  17. select 1
  18. union all
  19. select null
  20. union all
  21. select 15
  22. union all
  23. select 3
  24. """

test action

test action可以使用更复杂的校验规则来测试,比如验证行数、执行时间、是否抛出异常

可用参数

  • String sql: 输入的sql字符串
  • List<List> result: 提供一个List对象,用于校验真实查询结果对比是否相等
  • String exception: 校验抛出的异常是否包含某些字符串
  • long rowNum: 验证结果行数
  • long time: 验证执行时间是否小于这个值,单位是毫秒
  • Closure<List<List>, Throwable, Long, Long> check: 自定义回调校验,可传入结果、异常、时间。存在回调函数时,其他校验方式会失效。

下面的样例代码存放于${DORIS_HOME}/regression-test/suites/demo/qt_action.groovy:

  1. test {
  2. sql "abcdefg"
  3. // check exception message contains
  4. exception "errCode = 2, detailMessage = Syntax error"
  5. }
  6. test {
  7. sql """
  8. select *
  9. from (
  10. select 1 id
  11. union all
  12. select 2
  13. ) a
  14. order by id"""
  15. // multi check condition
  16. // check return 2 rows
  17. rowNum 2
  18. // execute time must <= 5000 millisecond
  19. time 5000
  20. // check result, must be 2 rows and 1 column, the first row is 1, second is 2
  21. result(
  22. [[1], [2]]
  23. )
  24. }
  25. test {
  26. sql "a b c d e f g"
  27. // other check will not work because already declared a check callback
  28. exception "aaaaaaaaa"
  29. // callback
  30. check { result, exception, startTime, endTime ->
  31. // assertXxx() will invoke junit5's Assertions.assertXxx() dynamically
  32. assertTrue(exception != null)
  33. }
  34. }
  35. test {
  36. sql """
  37. select 2
  38. union all
  39. select 1
  40. union all
  41. select null
  42. union all
  43. select 15
  44. union all
  45. select 3
  46. """
  47. check { result, ex, startTime, endTime ->
  48. // same as order_sql(sqlStr)
  49. result = sortRows(result)
  50. assertEquals(null, result[0][0])
  51. assertEquals(1, result[1][0])
  52. assertEquals(15, result[2][0])
  53. assertEquals(2, result[3][0])
  54. assertEquals(3, result[4][0])
  55. }
  56. }

explain action

explain action用来校验explain返回的字符串是否包含某些字符串

可用参数:

  • String sql: 查询的sql,需要去掉sql中的explain
  • String contains: 校验explain是否包含某些字符串,可多次调用校验同时多个结果
  • String notContains: 校验explain是否不含某些字符串,可多次调用校验同时多个结果
  • Closure check: 自定义校验回调函数,可以获取返回的字符串,存在校验函数时,其他校验方式会失效
  • Closure<String, Throwable, Long, Long> check: 自定义校验回调函数,可以额外获取异常和时间

下面的样例代码存放于${DORIS_HOME}/regression-test/suites/demo/explain_action.groovy:

  1. explain {
  2. sql("select 100")
  3. // contains("OUTPUT EXPRS:<slot 0> 100\n") && contains("PARTITION: UNPARTITIONED\n")
  4. contains "OUTPUT EXPRS:<slot 0> 100\n"
  5. contains "PARTITION: UNPARTITIONED\n"
  6. }
  7. explain {
  8. sql("select 100")
  9. // contains(" 100\n") && !contains("abcdefg") && !("1234567")
  10. contains " 100\n"
  11. notContains "abcdefg"
  12. notContains "1234567"
  13. }
  14. explain {
  15. sql("select 100")
  16. // simple callback
  17. check { explainStr -> explainStr.contains("abcdefg") || explainStr.contains(" 100\n") }
  18. }
  19. explain {
  20. sql("a b c d e")
  21. // callback with exception and time
  22. check { explainStr, exception, startTime, endTime ->
  23. // assertXxx() will invoke junit5's Assertions.assertXxx() dynamically
  24. assertTrue(exception != null)
  25. }
  26. }

streamLoad action

streamLoad action用于导入数据 可用参数为

  • String db: db,默认值为regression-conf.groovy中的defaultDb
  • String table: 表名
  • String file: 要导入的文件路径,可以写data目录下的相对路径,或者写http url来导入网络文件
  • Iterator<List> inputIterator: 要导入的迭代器
  • String inputText: 要导入的文本, 较为少用
  • InputStream inputStream: 要导入的字节流,较为少用
  • long time: 验证执行时间是否小于这个值,单位是毫秒
  • void set(String key, String value): 设置stream load的http请求的header,如label、columnSeparator
  • Closure<String, Throwable, Long, Long> check: 自定义校验回调函数,可以获取返回结果、异常和超时时间。当存在回调函数时,其他校验项会失效。

下面的样例代码存放于${DORIS_HOME}/regression-test/suites/demo/streamLoad_action.groovy:

  1. def tableName = "test_streamload_action1"
  2. sql """
  3. CREATE TABLE IF NOT EXISTS ${tableName} (
  4. id int,
  5. name varchar(255)
  6. )
  7. DISTRIBUTED BY HASH(id) BUCKETS 1
  8. PROPERTIES (
  9. "replication_num" = "1"
  10. )
  11. """
  12. streamLoad {
  13. // you can skip declare db, because a default db already specify in ${DORIS_HOME}/conf/regression-conf.groovy
  14. // db 'regression_test'
  15. table tableName
  16. // default label is UUID:
  17. // set 'label' UUID.randomUUID().toString()
  18. // default column_separator is specify in doris fe config, usually is '\t'.
  19. // this line change to ','
  20. set 'column_separator', ','
  21. // relate to ${DORIS_HOME}/regression-test/data/demo/streamload_input.csv.
  22. // also, you can stream load a http stream, e.g. http://xxx/some.csv
  23. file 'streamload_input.csv'
  24. time 10000 // limit inflight 10s
  25. // stream load action will check result, include Success status, and NumberTotalRows == NumberLoadedRows
  26. }
  27. // stream load 100 rows
  28. def rowCount = 100
  29. def rowIt = java.util.stream.LongStream.range(0, rowCount) // [0, rowCount)
  30. .mapToObj({i -> [i, "a_" + i]}) // change Long to List<Long, String>
  31. .iterator()
  32. streamLoad {
  33. table tableName
  34. // also, you can upload a memory iterator
  35. inputIterator rowIt
  36. // if declared a check callback, the default check condition will ignore.
  37. // So you must check all condition
  38. check { result, exception, startTime, endTime ->
  39. if (exception != null) {
  40. throw exception
  41. }
  42. log.info("Stream load result: ${result}".toString())
  43. def json = parseJson(result)
  44. assertEquals("success", json.Status.toLowerCase())
  45. assertEquals(json.NumberTotalRows, json.NumberLoadedRows)
  46. assertTrue(json.NumberLoadedRows > 0 && json.LoadBytes > 0)
  47. }
  48. }

启动脚本例子

  1. # 查看脚本参数说明
  2. ./run-regression-test.sh h
  3. # 查看框架参数说明
  4. ./run-regression-test.sh --run -h
  5. # 测试所有用例
  6. ./run-regression-test.sh
  7. # 删除测试框架编译结果和测试日志
  8. ./run-regression-test.sh --clean
  9. # 测试suiteName为sql_action的用例, 目前suiteName等于文件名前缀,例子对应的用例文件是sql_action.groovy
  10. ./run-regression-test.sh --run sql_action
  11. # 测试suiteName包含'sql'的用例,**注意需要用单引号括起来**
  12. ./run-regression-test.sh --run '*sql*'
  13. # 测试demo和perfomance group
  14. ./run-regression-test.sh --run -g 'demo,performance'
  15. # 测试demo group下的sql_action
  16. ./run-regression-test.sh --run -g demo -s sql_action
  17. # 自定义配置
  18. ./run-regression-test.sh --run -conf a=b

使用查询结果自动生成.out文件

  1. # 使用查询结果自动生成sql_action用例的.out文件,如果.out文件存在则忽略
  2. ./run-regression-test.sh --run sql_action -genOut
  3. # 使用查询结果自动生成sql_action用例的.out文件,如果.out文件存在则覆盖
  4. ./run-regression-test.sh --run sql_action -forceGenOut