想对 ClickHouse 进行开发,不能只懂得如何编写代码,更要懂得如何做好测试,这样才能保证自己开发的功能是可用的。所以如何编写测试用例是非常重要的,本文介绍一下 ClickHouse 如何编写测试用例。
ClickHouse 的测试分很多种,这里主要介绍两种最常用的测试,即 stateless 测试和 integration 测试。
stateless 测试
这个是一种功能性测试,功能性测试包含 stateless 测试和 stateful 测试,即无状态测试和有状态测试。大多数的功能开发都可以通过编写功能测试来进行验证。无状态测试是指在没有预加载任何测试数据的情况下运行查询,可以在测试中建表、写入数据以及执行各类 SQL 操作来验证功能。有状态测试需要从 ClickHouse 预加载测试数据,一般很少使用,这里不过多介绍。
现在用一个例子来说明如何编写 stateless 测试用例。
stateless 测试用例都在 ClickHouse/tests/queries/0_stateless
目录下,要添加测试用例也需要在这个目录下添加。比如我们想创建一个 MergeTree 表,然后写入一条数据,验证数据写入成功。示例如下:
创建一个 SQL 文件,例如 00000_check_mergetree.sql:
select 'check MergeTree insert 1';
create table if not exists 00000_check_mergetree (id Int32) engine = MergeTree() order by id;
insert into 00000_check_mergetree values (1);
select id from 00000_check_mergetree;
drop table 00000_check_mergetree;
这里解释一下为什么要用 00000_check_mergetree 作为表的名字,是因为 stateless 测试用例都会使用一个 server 来测试,使表名和文件夹名字一致可以避免和其他用例冲突。
然后在 ClickHouse 目录下执行如下命令:
./build/programs/clickhouse-client --port 9000 --multiquery < tests/queries/0_stateless/00000_check_mergetree.sql > tests/queries/0_stateless/00000_check_mergetree.reference
其中 ./build/programs/clickhouse-client
是我们编译好的 clickhouse-client 二进制的路径,如何编译 ClickHouse 请参考 ClickHouse 最简单的编译环境搭建方法。在执行上面语句之前需要先把 ClickHouse 启动,因为上述命令需要真正访问 ClickHouse。在执行完毕上述语句之后,就会在我们指定的文件 tests/queries/0_stateless/00000_check_mergetree.reference
内生成执行结果。
执行结果如下:
1
这里注意,最后会生成一个空行,所以在编写测试用例时一定要用上述语句来生成 .reference
文件,不要用手写的方式。
至此,一个简单的测试用例就写完了。
其实有些人会对此有些疑问,.reference
文件只有一个 1,不明白是个什么意思,如果要加个说明怎么加呢?
我们可以将 00000_check_mergetree.sql
文件做个修改:
select 'check MergeTree insert 1';
create table if not exists 00000_check_mergetree (id Int32) engine = MergeTree() order by id;
insert into 00000_check_mergetree values (1);
select id from 00000_check_mergetree;
drop table 00000_check_mergetree;
最终 00000_check_mergetree.reference
文件为:
check MergeTree insert 1
1
可能还有的人觉得这样好 low 啊,不急,ClickHouse 提供了更优雅的方式,下面我们把测试用例改写一下:
-- {echo}
create table if not exists 00000_check_mergetree (id Int32) engine = MergeTree() order by id;
insert into 00000_check_mergetree values (1);
select id from 00000_check_mergetree;
drop table 00000_check_mergetree;
通过 -- {echo}
可以将 SQL 语句与结果都输出出来,结果如下:
-- {echo}
create table if not exists 00000_check_mergetree (id Int32) engine = MergeTree() order by id;
insert into 00000_check_mergetree values (1);
select id from 00000_check_mergetree;
1
drop table 00000_check_mergetree;
这样我们很容易对照测试语句和执行结果了。
此外,stateless 测试还支持添加 Tag,比如我们需要创建 ReplicatedMergeTree 时需要依赖 zk,那么我们就需要添加 zookeeper 标签,例如:
-- Tags: zookeeper
除此之外 ClickHouse 还支持很多标签,请参照官网:测试标签。
integration 测试
这个是集成测试,有时功能测试不能满足需要,比如需要模拟一个 2 分片 2 副本的集群的测试场景,或者模拟集群某个节点无法联通等场景等。
集成测试等测试用例都在 ClickHouse/tests/integration
目录下。一般一个完整的测试用例的目录结构是这样的:
.
├── configs
│ └── servers.xml
├── __init__.py
└── test.py
同样,我们在这个目录下添加一个用例,模拟创建一个 2 分片 2 副本的集群,并查询 system.clusters 表。首先先创建一个目录 test_cluster,并按照上述目录结构先创建空文件。
在 servers.xml 中来配置集群拓扑:
<clickhouse>
<remote_servers>
<two_shards_two_replicas>
<shard>
<replica>
<host>node1</host>
<port>9000</port>
</replica>
<replica>
<host>node2</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>node3</host>
<port>9000</port>
</replica>
<replica>
<host>node4</host>
<port>9000</port>
</replica>
</shard>
</two_shards>
</remote_servers>
</clickhouse>
在 test.py 中编写测试方法:
import pytest
from helpers.cluster import ClickHouseCluster
cluster = ClickHouseCluster(__file__)
node1 = cluster.add_instance(
"node1", main_configs=["configs/servers.xml"], with_zookeeper=True
)
node2 = cluster.add_instance(
"node2", main_configs=["configs/servers.xml"], with_zookeeper=True
)
node3 = cluster.add_instance(
"node3", main_configs=["configs/servers.xml"], with_zookeeper=True
)
node4 = cluster.add_instance(
"node4", main_configs=["configs/servers.xml"], with_zookeeper=True
)
@pytest.fixture(scope="module")
def start_cluster():
try:
cluster.start()
yield cluster
finally:
cluster.shutdown()
def test_system_cluster(start_cluster):
node1.query("select * from system.clusters;")
都编写完毕后,进入 ClickHouse/tests/integration
目录,执行如下语句:
./runner --binary /data/ClickHouse/build/programs/clickhouse --base-configs-dir /data/ClickHouse/build/programs --command bash
其中,/data/ClickHouse/build/programs/clickhouse
是编译完的 clickhouse 二进制的路径,/data/ClickHouse/build/programs
是 ClickHouse 基础配置文件的路径,所以在该路径下需要存在一个 config.xml 和 一个 users.xml 文件,具体参照:ClickHouse 快速搭建 中的配置文件。
执行完上述命令后,会启动一个 docker,这里就包含了集成测试所需要的所有环境依赖。但由于现在 docker 下载镜像的限制,需要使用 docker login 来登陆一个自己的 docker 账号,因为集成测试都是通过 docker 来启动节点进行测试的。接下来就可以执行测试命令跑起来我们的测试了,命令如下:
pytest -ss test_cluster/test.py -vv
正常输出如下:
========================================================== test session starts ==========================================================
platform linux -- Python 3.8.10, pytest-7.2.0, pluggy-1.0.0 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /ClickHouse/tests/integration, configfile: pytest.ini
plugins: order-1.0.0, repeat-0.9.1, timeout-2.1.0, xdist-3.0.2
timeout: 900.0s
timeout method: signal
timeout func_only: False
collected 1 item
test_cluster/test.py::test_system_cluster Copy common default production configuration from /clickhouse-config. Files: config.xml, users.xml
Copy common default production configuration from /clickhouse-config. Files: config.xml, users.xml
Copy common default production configuration from /clickhouse-config. Files: config.xml, users.xml
Copy common default production configuration from /clickhouse-config. Files: config.xml, users.xml
PASSED
========================================================== 1 passed in 12.36s ===========================================================
如果输出如下错误:
FAILED test_cluster/test.py::test_system_cluster - helpers.client.QueryRuntimeException: Client failed! Return code: 210, stderr: Code: 210. DB::NetException: Connection reset by peer...
需要在改下宿主机的 iptables 设置,执行:
sudo iptables -P FORWARD ACCEPT
如果想测试一个异常情况可以将 test.py 中的 test_system_cluster 改为如下形式:
def test_system_cluster(start_cluster):
node1.query("select * from system.clusters;")
error = node1.query_and_get_error("select * from system.clustersxxx;")
assert "UNKNOWN_TABLE" in error
如果想测试一个正常情况,可以这样修改:
def test_system_cluster(start_cluster):
assert (
node1.query("select cluster from system.clusters where cluster = 'two_shards_two_replicas';")
== "two_shards_two_replicas\ntwo_shards_two_replicas\ntwo_shards_two_replicas\ntwo_shards_two_replicas\n"
)
error = node1.query_and_get_error("select * from system.clustersxxx;")
assert "UNKNOWN_TABLE" in error
所以可以根据实际需要选择对应的验证方式。
至此,两种主要的测试方法就介绍完毕了。
欢迎添加微信:xiedeyantu,讨论技术问题。