集成测试

此文档主要介绍 TuGraph 的集成测试框架如何使用

1.TuGraph集成测试的意义

在单元测试与功能测试中,有部分用例直接开启galaxy或statemachine来进行测试,这并不是一个完整的流程。在完整的cs架构中,用户请求是通过客户端发往服务端,网络通信是必不可少的,为了避免单元测试不完整带来的bug,针对这种情况,使用集成测试框架进行全链路的完整测试。

2.TuGraph集成测试框架

TuGraph采用pytest框架作为自己的集成测试框架,pytest框架作为目前使用最广泛的cs端集成测试框架,以其灵活简单,容易上手,并且支持参数化的使用方式而著称,TuGraph基于pytest提供的功能,抽象出了不同的工具,通过参数来控制各个工具的处理逻辑,以方便大家进行高效的测试代码开发。

更多pytest信息请参考官网: https://docs.pytest.org/en/7.2.x/getting-started.html

2.1.组件描述

组件名称

组件功能

实现方式

server

TuGraph单机服务

开启子进程并在子进程中启动服务

client

TuGraph Rpc Client

当前进程中开启TuGraph Python Rpc Client发送请求

importor

TuGraph Importor

开启子进程并在子进程中处理导入请求

exportor

TuGraph Exportor

开启子进程并在子进程中处理导出请求

backup_binlog

TuGraph Backup Binlog

开启子进程并在子进程中处理备份binlog的请求

backup_copy_dir

TuGraph Backup

开启子进程并在子进程中处理备份完整db的请求

build_so

编译c++动态连接库的组件

开启子进程并在子进程中处理gcc编译逻辑

copy_snapshot

TuGraph Copy Snapshot

当前进程中处理备份snapshot的请求

copydir

文件夹拷贝

当前进程中处理文件夹拷贝请求

exec

执行c++/java可执行文件

开启子进程并在子进程中启动C++可执行文件

algo

执行算法

开启子进程并在子进程中执行算法

bash

执行bash命令

开启子进程并在子进程中执行bash命令

rest

TuGraph Python Rest Client

当前进程中开启TuGraph Python Rest Client发送请求

2.2.组件用法

2.2.1.server

2.2.1.1.启动参数

采用python字典传入

  • cmd是启动命令

  • cleanup_dir是执行完成后需要清理的目录,可以是多个,通过python列表传入

SERVEROPT = {"cmd":"./lgraph_server -c lgraph_standalone.json --directory ./testdb --license _FMA_IGNORE_LICENSE_CHECK_SALTED_ --port 7072 --rpc_port 9092",
             "cleanup_dir":["./testdb"]}
2.2.1.2.启动命令

通过fixtures组件引入工具,并通过启动参数来控制不同的处理逻辑,函数开始执行前会启动server,函数执行完成后会停止server,并清理cleanup_dir指定的目录

@pytest.mark.parametrize("server", [SERVEROPT], indirect=True)
def test_server(self, server):
    pass

2.2.2.client

2.2.2.1.启动参数

采用python字典传入

  • host是TuGraph Server的ip和端口

  • user是TuGraph Server的用户名

  • password是TuGraph Server 中user对应的密码

CLIENTOPT = {"host":"127.0.0.1:9092", "user":"admin", "password":"73@TuGraph"}
2.2.2.2.启动命令

通过fixtures组件引入工具,并通过启动参数来控制不同的处理逻辑,函数开始执行前会启动客户端,函数执行结束后会结束客户端

@pytest.mark.parametrize("server", [SERVEROPT], indirect=True)
@pytest.mark.parametrize("client", [CLIENTOPT], indirect=True)
def test_client(self, server, client):
    ret = client.callCypher("CALL db.createEdgeLabel('followed', '[]', 'address', string, false, 'date', int32, false)", "default")
    assert ret[0]
    ret = client.callCypher("CALL db.createEdgeLabel('followed', '[]', 'address', string, false, 'date', int32, false)", "default")
    assert ret[0] == False

2.2.3.importor

2.2.3.1.启动参数

采用python字典传入

  • cmd是启动命令

  • cleanup_dir是执行完成后需要清理的目录,可以是多个,通过python列表传入

IMPORTOPT = {"cmd":"./lgraph_import --config_file ./data/yago/yago.conf --dir ./testdb --user admin --password 73@TuGraph --graph default --overwrite 1",
             "cleanup_dir":["./testdb", "./.import_tmp"]}
2.2.3.2.启动命令

通过fixtures组件引入工具,并通过启动参数来控制导入不同的数据,函数开始执行前会导入数据到指定的目录,函数执行完成后会清理cleanup_dir指定的目录

@pytest.mark.parametrize("importor", [IMPORTOPT], indirect=True)
def test_importor(self, importor):
    pass

2.2.4.exportor

2.2.4.1.启动参数

采用python字典传入

  • cmd是启动命令

  • cleanup_dir是执行完成后需要清理的目录,可以是多个,通过python列表传入

EXPORT_DEF_OPT = {"cmd":"./lgraph_export -d ./testdb -e ./export/default -g default -u admin -p 73@TuGraph",
                  "cleanup_dir":["./export"]}
2.2.4.2.启动命令

通过fixtures组件引入工具,并通过启动参数来控制导出不同的数据,函数开始执行前会导出数据到指定的目录,函数执行完成后会清理cleanup_dir指定的目录

@pytest.mark.parametrize("exportor", [EXPORT_DEF_OPT], indirect=True)
def test_exportor(self, exportor):
    pass

2.2.5.backup_binlog

2.2.5.1.启动参数

采用python字典传入

  • cmd是启动命令

  • cleanup_dir是执行完成后需要清理的目录,可以是多个,通过python列表传入

BINLOGOPT = {"cmd" : "./lgraph_binlog -a restore --host 127.0.0.1 --port 9093 -u admin -p 73@TuGraph -f ./testdb/binlog/*",
             "cleanup_dir":[]}
2.2.5.2.启动命令

通过fixtures组件引入工具,并通过启动参数来控制备份不同的binlog,函数开始执行前会拷贝binlog到指定的目录,函数执行完成后会清理cleanup_dir指定的目录

@pytest.mark.parametrize("backup_binlog", [BINLOGOPT], indirect=True)
def test_backup_binlog(self, backup_binlog):
    pass

2.2.6.backup_copy_dir

2.2.6.1.启动参数

采用python字典传入

  • cmd是启动命令

  • cleanup_dir是执行完成后需要清理的目录,可以是多个,通过python列表传入

BACKUPOPT = {"cmd" : "./lgraph_backup --src ./testdb -dst ./testdb1",
             "cleanup_dir":[]}
2.2.6.2.启动命令

通过fixtures组件引入工具,并通过启动参数来控制备份不同的db,函数开始执行前会拷贝db到指定的目录,函数执行完成后会清理cleanup_dir指定的目录

@pytest.mark.parametrize("backup_copy_dir", [BACKUPOPT], indirect=True)
def test_backup_copy_dir(self, backup_copy_dir):
	pass

2.2.7.build_so

2.2.7.1.启动参数

采用python字典传入

  • cmd是启动命令,采用python列表传入,可以一次编译多个so

  • so_name是执行完成后需要清理的so,可以是多个,通过python列表传入

BUILDOPT = {"cmd":["g++ -fno-gnu-unique -fPIC -g --std=c++17 -I ../../include -I ../../deps/install/include -rdynamic -O3 -fopenmp -DNDEBUG -o ./scan_graph.so ../../test/test_procedures/scan_graph.cpp ./liblgraph.so -shared",
                       "g++ -fno-gnu-unique -fPIC -g --std=c++17 -I ../../include -I ../../deps/install/include -rdynamic -O3 -fopenmp -DNDEBUG -o ./sortstr.so ../../test/test_procedures/sortstr.cpp ./liblgraph.so -shared"],
                "so_name":["./scan_graph.so", "./sortstr.so"]}
2.2.7.2.启动命令

通过fixtures组件引入工具,并通过启动参数来控制编译不同的so,函数开始执行前会生成so到指定的目录,函数执行完成后会清理so_name列表指定的动态库

@pytest.mark.parametrize("build_so", [BUILDOPT], indirect=True)
def test_build_so(self, build_so):
    pass

2.2.8.copy_snapshot

2.2.8.1.启动参数

采用python字典传入

  • src是原db

  • dst是拷贝后的snapshot

COPYSNAPOPT = {"src" : "./testdb", "dst" : "./testdb1"}
2.2.8.2.启动命令

通过fixtures组件引入工具,并通过启动参数来控制拷贝不同的snapshot,函数开始执行前会拷贝src中的snapshot到dst指定的目录

@pytest.mark.parametrize("copy_snapshot", [COPYSNAPOPT], indirect=True)
def test_copy_snapshot(self, copy_snapshot):
    pass

2.2.9.copy_dir

2.2.9.1.启动参数

采用python字典传入

  • src是原db

  • dst是拷贝后的snapshot

COPYSNAPOPT = {"src" : "./testdb", "dst" : "./testdb1"}
2.2.9.2.启动命令

通过fixtures组件引入工具,并通过启动参数来控制拷贝不同的目录,函数开始执行前会拷贝src到dst指定的目录

@pytest.mark.parametrize("copy_dir", [COPYDIR], indirect=True)
def test_copy_dir(self, copy_dir):
    pass

2.2.10.exec

2.2.10.1.启动参数

采用python字典传入

  • cmd是启动命令

EXECOPT = {
        "cmd" : "test_rpc_client/cpp/CppClientTest/build/clienttest"
    }
2.2.10.2.启动命令

通过fixtures组件引入工具,并通过启动参数来控制执行不同的逻辑,函数开始执行前会开启子进程执行通过cmd参数传入的命令

@pytest.mark.parametrize("exec", [EXECOPT], indirect=True)
def test_exec(self, exec):
        pass

2.2.11.algo

2.2.11.1.启动参数

采用python字典传入

  • cmd是启动命令

  • result是算法预期的执行结果,执行完成会通过实际结果与预期结果进行比较,不同则测试失败

BFSEMBEDOPT = {
        "cmd" : "algo/bfs_embed ./testdb",
        "result" : ["found_vertices = 3829"]
    }
2.2.11.2.启动命令

通过fixtures组件引入工具,并通过启动参数来控制执行不同的算法逻辑,函数开始执行前会开启子进程执行通过cmd参数传入的算法,函数主体等待算法执行完成后对比结果

@pytest.mark.parametrize("algo", [BFSEMBEDOPT], indirect=True)
def test_exec_bfs_embed(self, algo):
    pass

2.2.12.bash

2.2.12.1.启动参数

采用python字典传入

  • cmd是启动命令

BASHOPT = {
        "cmd" : "sh ./test_rpc_client/cpp/CppClientTest/compile.sh"
    }
2.2.12.2.启动命令

通过fixtures组件引入工具,并通过启动参数来控制执行不同的bash命令,函数开始执行前会开启子进程执行通过cmd参数传入的bash命令,函数主体等待算法执行完成

@pytest.mark.parametrize("bash", [BASHOPT], indirect=True)
def test_bash(self, bash):
    pass

2.2.13.rest

2.2.13.1.启动参数

采用python字典传入

  • port是TuGraph Server的端口

  • user是TuGraph Server的用户名

  • password是TuGraph Server 中user对应的密码

RESTTOPT = {"port":"7073", "user":"admin", "password":"73@TuGraph"}
2.2.13.2.启动命令

通过fixtures组件引入工具,并通过启动参数来链接不同的TuGraph Rest Server,函数开始执行前会启动客户端,函数执行结束后会结束客户端

@pytest.mark.parametrize("rest", [RESTTOPT], indirect=True)
def test_get_info(self, server, rest):
	pass

2.3.测试样例

2.3.1.rest

样例代码中在test_get_info函数执行之前先启动server,server启动后启动了rest client,进入test_get_info函数后获取server的一些信息,并通过assert判断是否有获取到cpu的信息。

SERVEROPT = {"cmd":"./lgraph_server -c lgraph_standalone.json --directory ./testdb --license _FMA_IGNORE_LICENSE_CHECK_SALTED_ --port 7073 --rpc_port 9093",
               "cleanup_dir":["./testdb"]}
RESTTOPT = {"port":"7073", "user":"admin", "password":"73@TuGraph"}
@pytest.mark.parametrize("server", [SERVEROPT], indirect=True)
@pytest.mark.parametrize("rest", [RESTTOPT], indirect=True)
def test_get_info(self, server, rest):
    res = rest.get_server_info()
    log.info("res : %s", res)
    assert('cpu' in res)

2.3.2.client

样例代码中在test_flushdb函数执行之前先执行了数据离线导入逻辑,并启动server后,通过client创建链接,进入test_flushdb函数后,通过查询点的个数判断导入是否成功,导入成功后执行flushDB操作,再次通过assert判断是否能正常清空db

SERVEROPT = {"cmd":"./lgraph_server -c lgraph_standalone.json --directory ./testdb --license _FMA_IGNORE_LICENSE_CHECK_SALTED_ --port 7072 --rpc_port 9092",
             "cleanup_dir":["./testdb"]}

CLIENTOPT = {"host":"127.0.0.1:9092", "user":"admin", "password":"73@TuGraph"}

IMPORTOPT = {"cmd":"./lgraph_import --config_file ./data/yago/yago.conf --dir ./testdb --user admin --password 73@TuGraph --graph default --overwrite 1",
             "cleanup_dir":["./testdb", "./.import_tmp"]}

@pytest.mark.parametrize("importor", [IMPORTOPT], indirect=True)
@pytest.mark.parametrize("server", [SERVEROPT], indirect=True)
@pytest.mark.parametrize("client", [CLIENTOPT], indirect=True)
def test_flushdb(self, importor, server, client):
    ret = client.callCypher("MATCH (n) RETURN n LIMIT 100", "default")
    assert ret[0]
    res = json.loads(ret[1])
    assert len(res) == 21
    ret = client.callCypher("CALL db.flushDB()", "default")
    assert ret[0]
    res = json.loads(ret[1])
     assert res == None

2.3.3.exportor/importor

样例代码中在test_export_default函数执行之前先执行了数据离线导入逻辑,导入成功后将当前db的数据导出,然后再次通过离线导入逻辑将exportor导出的数据导入到新的目录中,以新导入的数据启动db,并且创建链接。在test_export_default函数主体中判断导出后再次导入的数据是否与原始数据一致

SERVEROPT = {"cmd":"./lgraph_server -c lgraph_standalone.json --directory ./testdb1 --license _FMA_IGNORE_LICENSE_CHECK_SALTED_ --port 7073 --rpc_port 9093",
             "cleanup_dir":["./testdb1"]}

CLIENTOPT = {"host":"127.0.0.1:9093", "user":"admin", "password":"73@TuGraph"}

IMPORT_YAGO_OPT = {"cmd":"./lgraph_import --config_file ./data/yago/yago.conf --dir ./testdb --user admin --password 73@TuGraph --graph default --overwrite 1",
             "cleanup_dir":["./.import_tmp", "./testdb"]}

IMPORT_DEF_OPT = {"cmd":"./lgraph_import -c ./export/default/import.config -d ./testdb1",
             "cleanup_dir":["./.import_tmp", "./testdb1"]}

EXPORT_DEF_OPT = {"cmd":"./lgraph_export -d ./testdb -e ./export/default -g default -u admin -p 73@TuGraph",
                  "cleanup_dir":["./export"]}

@pytest.mark.parametrize("importor", [IMPORT_YAGO_OPT], indirect=True)
@pytest.mark.parametrize("exportor", [EXPORT_DEF_OPT], indirect=True)
@pytest.mark.parametrize("importor_1", [IMPORT_DEF_OPT], indirect=True)
@pytest.mark.parametrize("server", [SERVEROPT], indirect=True)
@pytest.mark.parametrize("client", [CLIENTOPT], indirect=True)
def test_export_default(self, importor, exportor, importor_1, server, client):
    ret = client.callCypher("MATCH (n) RETURN n LIMIT 100", "default")
    assert ret[0]
    res = json.loads(ret[1])
    log.info("res : %s", res)
    assert len(res) == 21

2.3.4.其他测试

更多用例请参考集成测试代码 https://github.com/TuGraph-family/tugraph-db/tree/master/test/integration