Precise Testing Is Wrong — It's Just a Byproduct of Incremental Builds

There’s a popular idea in the testing world called precise testing (精准测试). In short, it is the ability to run only the tests affected by a change, instead of running the entire suite every time. I think the idea is right — but the way our industry usually implements it is wrong. In this post I’ll first explain what precise testing is and why the mainstream approach is heading in the wrong direction, and then show a different path: precise testing turns out to be nothing more than a byproduct of incremental builds. ...

2026-06-29 · 9 min · 1818 words · 翟志军 Jack Zhai

Setting up EKS with Bazel, Jsonnet and Terraform

Overview In this document, I’ll describe my solution from the following parts: Part1 Architecture: describe the desired state of the architecture Part2 Implementation: Code Structure Introduce How to Build it Deploy Nginx Controller using Helm Part1: Architecture We assume that the project has a project named: health. Here’s the architecture graph, which draw by Excalidraw Network Architecture I created 4 subnets that are evenly distributed to 2 Availability Zones. Each availability zone has 2 subnets, one is public subnet,and another one is private subnet. The public subnet goes out through the Internet gateway and the private subnet goes out through the NAT gateway. ...

2024-01-20 · 7 min · 1300 words · 翟志军 Jack Zhai

Bazel使用案例:构建Springboot工程

本文是关于如何使用Bazel搭建Springboot 3.1.0工程(基于JDK17)。为什么使用Bazel,而不是使用Maven或者Gradle?可以看我之前关于Bazel的介绍文章。 前期准备 在根目录加入.bazelversion文件,并加入6.2.0,指定当前工程使用的Bazel的版本。这样,Bazel命令自动使用该版本的Bazel进行构建。 在根目录加入.bazelrc文件,并指定构建和测试时使用JDK17,内容如下: build --java_language_version=17 --java_runtime_version=17 --tool_java_language_version=17 --tool_java_runtime_version=17 test --java_language_version=17 --java_runtime_version=17 --tool_java_language_version=17 --tool_java_runtime_version=17 外部依赖准备 在根目录中创建以下两个文件: WORKSPACE:在Bazel中,所有的外部依赖统一定义WORKSPACE文件中; BUILD.bazel:内容留空即可,用于告诉Bazel当前目录也是一个Package。 Bazel本身是支持多语言的。所以,我们需要特定语言的rule来帮助我们在WORKSPACE中定义外部依赖。 对于Java工程,我们使用rules_jvm_external进行外部依赖的管理。它的使用步骤如下: 步骤1:在WORKSPACE中增加rules_jvm_external配置 以下配置指定了rules_jvm_external的下载位置,并进行rule的初始化: load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") RULES_JVM_EXTERNAL_TAG = "4.5" RULES_JVM_EXTERNAL_SHA = "<sha hash value>" http_archive( name = "rules_jvm_external", strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, sha256 = RULES_JVM_EXTERNAL_SHA, url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, ) load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps") rules_jvm_external_deps() load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup") rules_jvm_external_setup() load("@rules_jvm_external//:defs.bzl", "maven_install") maven_install( artifacts = [ # The project's dependencies "junit:junit:4.12", "org.hamcrest:hamcrest-library:1.3", ], repositories = [ # Private repositories are supported through HTTP Basic auth # "http://username:password@localhost:8081/artifactory/my-repository", "https://maven.aliyun.com/repository/public", ], ) 以上采用了非Bzlmod的管理rule。 ...

2024-01-19 · 3 min · 461 words · 翟志军 Jack Zhai

Bazel作为构建工具之王,将会颠覆你对CI的认知

说到构建工具,不同语言技术栈的人,想起的构建工具不同。 Java程序员想到的是Maven,前端程序员想的是NPM或者Webpack、Android程序员想到的是Gradle、Rust程序想到的是Cargo、C++程序员想到的是Make等等。 然而这些工具在Bazel面前,层次有些低。所以,我愿称Bazel是构建工具之王。 P.S. Android平台的构建,2020年已经开始了迁移到Bazel的工作。 具体地址:https://blog.bazel.build/2020/11/12/aosp_migrating_to_bazel.html Bazel介绍 Bazel是Google在2015年开源的一款构建工具。 目前使用Bazel的知名公司有:Esty、Canva、Databricks、Dropbox、Huawei、Line、LinkedIn、Stripe、Twitter、Tinder、Uber、VMware、Wix等。具体可以看:https://bazel.build/community/users 。 其中Twitter是从自家的Pants迁移到的Bazel的,具体迁移过程介绍:https://opensourcelive.withgoogle.com/events/bazelcon2020/watch?talk=day1-talk2 Facebook使用的是其自研的Buck2,但是,其与Bazel使用的是相同的远程执行的API。 除了公司,某些著名的开源软件也使用Bazel构建,包括自动化测试领域的Selenium,AI领域的TensorFlow,容器编排领域的Kubernetes等。具体还有:https://bazel.build/community/users#open-source-projects-using-Bazel 相对于其它构建工具,它的显著的特点有: 支持多语言; 支持远程分布式构建; 支持增量构建; 支持强大的密闭性; 支持构建缓存; 支持并行构建。 假设存在一个复杂的软件工程 假设存在一个软件工程中,它包含5部分:Web前端、Android端、Java后端、Go后端、嵌入式端。 作为Java后端的程序员,他们修改了一个API。但是他作为个人,他无法预知到底发生了哪些影响。 所以,他把这个问题交给了持续集成(CI),让它去发现集成问题。 在过去很长一段时间里,行业里只有一种CI模式,我称之为传统的CI模式。 殊不知,还有另一种模式。 传统的CI模式 目前行业里比较传统的CI架构,通常如下: 在这样的架构下,实现CI的步骤如下: 开发人员提交代码; Gitlab检测到开发人员提交代码,然后触发Jenkins controller执行; Jenkins controller根据该代码仓库预先设计的pipeline执行; Jenkins controller根据pipeline中的任务所需要的构建环境,将任务分配给不同的Jenkins agent; 在agent构建完成后,将制品release到制品仓库中。 如果开发者希望验证自己写的代码,就必须将代码commit到Gitlab中。因为整个验证环境被定义在CI环境的Pipeline中。而且这个过程,越大的工程,集成速度越慢。开发者也无法在本地进行全量验证。 作为Pipeline的维护者,他需要清楚知道哪些任务是可以并行执行的,并手工配置并行,这样才能加快构建速度。比如前端构建和后端构建可以并行进行。 也就是说在传统的CI模式下,开发者的效率会随着软件的规模越大而降低。换句话,这样的模式,开发效率无法scale。 案例 希望以下案例可以给你一个感性的认知。下图是Google在2010年到2015年的周commit数量。绿线代表commit总数,黄线是人数。我们取离我们最近的2015年的数据来讨论。2015年的代码量如下: 在这个代码量下,每周能达到300左右的commit。如下图: 根据持续集成的原则,每一个commit都必须构建通过。20亿行代码一次全量构建需要多久? 我们以一个开源项目作参考。apitable是一个开源的数据表格项目,它有200万左右的代码,全量构建一次需要20分钟左右。那么,根据不准确的类推,20亿行代码,全量构建一次需要:20/2,000,000 * 2000,000,000=200,000,000分钟,也就是13天左右。 在传统的CI模式下,是尽量避免执行全量构建这样庞大的代码量的。所以,传统CI模式下,通常是多仓库模式管理代码。 那么Bazel呢?Bazel如果真要构建这样庞大的代码量,估计也够呛。但是由于Bazel天然支持并行构建、构建缓存和增量构建,所以,Bazel通常不会遇到真正意义的全量构建的情况。 为什么其它公司不使用Bazel 也许有人会问:为什么阿里2018年新增的代码行(https://zhuanlan.zhihu.com/p/54435171)就有12亿,不也没有使用Bazel吗? 这个是一个好问题。 但是,无法简单的回答这个问题,而是需要深入到各自组织内部才能分析清楚。个人觉得可以从以下维度分析: 在代码仓库上工作的人员的规模:同样的代码量,不同的组织需要不同数量的人维护; 代码管理方式:阿里使用多仓库的管理办法,不需要统一的版本号; 持续集成的程度不同:阿里可能不需要对每一个commit跑一次全量。 为什么Bazel会颠覆你对CI的认知 Bazel是如何解决传统CI模式下开发效率无法scale的问题呢?其主要通过它的六个特性来解决。 首先,Bazel支持远程分布式构建。 在一个使用Bazel构建的仓库中,开发者写好代码后,不用commit代码到Git仓库,只要在本地命令行执行bazel run --remote_executor=grpc://localhost:8980 //... ,代码仓库中所有构建和测试任务都将运行在远程执行服务器。远程执行服务器越多,构建速度越快。 这一特性可以明显地提高开发者本地的开发效率。因为开发者在本地就可以执行全量构建和全量测试。 传统CI模式下,无法提升开发者本地的开发效率。 第二,Bazel支持增量构建和增量测试(精准测试)。 开发者在本地执行build命令时,Bazel检测出修改了a.java文件,所以,Bazel只将构建a.java的任务及其相关的构建任务给远程执行服务器执行。这就是增量构建。 如果开发者执行test命令,Bazel则能检测出被影响的测试,然后只运行这些测试。其实这就是精准测试了。在Bazel中,精准测试实现起来并不难。 传统CI模式下,它是不关心增量构建和增量测试的。所以,每次运行都是全量。这是一种极大的浪费。 ...

2023-11-20 · 1 min · 108 words · 翟志军 Jack Zhai

基于Bazel + SQLFluff实现SQL lint

背景 SQL进行版本化控制后,我们希望为SQL加入lint步骤。这样做的好处是我们可以在真正执行SQL前发现问题。 本文中,我们通过Bazel执行SQLFluff以实现SQL的lint。 SQLFluff是一款使用Python语言使用的,支持SQL多方言的SQL lint工具。 它的特点是: 支持多方言。如:Snowflake、PostgreSQL、ClickHouse。所有支持的方言列表:https://docs.sqlfluff.com/en/stable/dialects.html; 可以输出正确的SQL,减少了我们手工修正SQL的工作; 同时支持命令行方式使用和API调用方式。 集成到CI/CD流水线中 在我看来,在CICD流水线中实现SQL lint有两种方式: 方式一:在流水线中增加一个SQL lint步骤; 方式二:将SQL lint的逻辑写在测试代码,执行测试步骤,就自动执行了SQL lint。 方式二是我最爱,我会在本文最后讲原因。 工程结构 . ├── BUILD.bazel ├── WORKSPACE ├── repository-hibernate-impl │ ├── BUILD.bazel │ └── src │ ├── main │ │ └── sql │ │ └── V1__runbook_table.sql │ └── test │ └── python │ ├── BUILD.bazel │ ├── requirements_lock.txt │ └── sql_test.py 步骤1: 在WORKSPACE中增加Python外部依赖 本文中我们使用的是Bazel 5.4.0,所以还在使用WORKSPACE定义外部依赖 http_archive( name = "rules_python", sha256 = "a644da969b6824cc87f8fe7b18101a8a6c57da5db39caa6566ec6109f37d2141", strip_prefix = "rules_python-0.20.0", url = "https://github.com/bazelbuild/rules_python/releases/download/0.20.0/rules_python-0.20.0.tar.gz", ) load("@rules_python//python:repositories.bzl", "py_repositories") py_repositories() load("@rules_python//python:repositories.bzl", "python_register_toolchains") python_register_toolchains( name = "python3_11", python_version = "3.11", ) load("@python3_11//:defs.bzl", interpreter_3_11 = "interpreter") load("@rules_python//python:pip.bzl", "pip_parse") # Create a central repo that knows about the dependencies needed from # requirements_lock.txt. pip_parse( name = "pip_deps", python_interpreter_target = interpreter_3_11, requirements_lock = "//repository-hibernate-impl/src/test/python:requirements_lock.txt", ) # Load the starlark macro which will define your dependencies. load("@pip_deps//:requirements.bzl", "install_deps") # Call it to define repos for your requirements. install_deps() 步骤2: 定义SQLFluff依赖 requirements_lock.txt的内容如下: ...

2023-04-17 · 2 min · 315 words · 翟志军 Jack Zhai