ClickHouse 如何编写测试用例

想对 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,讨论技术问题。

猜你喜欢

转载自blog.csdn.net/weixin_39992480/article/details/129604384