集成测试通常是一项艰难的流动,特地是在波及到分布式系统时。即使正在构建单体利用,也可能须要启动数据库,来进行集成测试。这种事件在晚期很容易做到,但随着代码库的减少,难度将呈指数级增长。
值得庆幸的是,Docker Compose 使咱们可能在运行 Docker 的任何环境中,进行集成测试。开始假如从一个单体体制开始,领有一个服务和一个数据库。你能够像 1999 年那样,从源代码构建应用服务和数据库;或应用 brew install 解决所有依赖关系。但最终你的零碎看起来是这样的:
待测试端点是 /create,它做的全副事件是在数据库中存储一些数据。看起来非常简单。因而,能够编写如下 Bash 脚本 - CURL 端点;而后查询数据库(退出码 0 代表胜利;退出码 1 代表失败)。该脚本很简略,但最重要的是它无效。
curl http://localhost:8000/createCOUNT = `mysql --user="$user" --password="$password" --database="$database" \ --execute="SELECT COUNT(*) FROM table_name;"`if [[ $COUNT -ne 1 ]]; then exit 1fi
然而有很多暗藏的依赖项:必须装置和运行数据库必须装置单体利用框架必须运行单体利用须要 PATH 中有 CURL 的操作系统依据测试,数据库中的任何数据都可能导致测试后果不精确。假如在 Bash 脚本中增加一行,重置数据。
mysql --user="$user" --password="$password" --database="$database" \ --execute="TRUNCATE table table_name"curl http://localhost:8000/createCOUNT = `mysql --user="$user" --password="$password" --database="$database"\ --execute="SELECT COUNT(*) FROM table_name;"`if [[ $COUNT -ne 1 ]]; then exit 1fi
这样做能够打消最初一个暗藏依赖项(数据库中存在数据),但也带来十分重大的副作用,因为本地开发数据库与测试数据库共享。因而,每次运行集成测试,都会失落全副开发数据 。这仿佛不言而喻,但实际上这种体制依然存在。然而不肯定非要这样做。从此处开始,我将通过一个构建在 Docker Compose 上的示例,解决下面列出的所有问题。在本例中,将应用 Node 作为应用程序框架,应用 RethinkDB 作为数据库,然而你也能够抉择其它技术栈。制订策略咱们从 Martin Fowler 的微服务测试手册中学习集成测试。咱们将在被测试的零碎内部启动一个容器,使容器运行一些测试,而后查看测试容器的 run 命令的退出代码。
为清晰起见,上面列出文件构造,因为该我的项目中有多个 Dockerfile。
integration-test/ Dockerfile index.js package.json test.sh docker-compose.ymlindex.jspackage.jsonDockerfile
接下来走查集成测试的每个组件。长期数据库有时抛弃所有数据是坏事,在运行测试时,抛弃数据是必要的。应用 Docker compose 实现这一点非常容易,只需启动数据库,无需挂载数据卷。这意味着当销毁容器时,数据也随之隐没。还意味着如果不销毁容器,那么能够进入容器外部,对数据库运行查问,进行调试。上面是一个示例 Docker Compose 文件,它只启动一个长期数据库(RethinkDB)。
integration-test/docker-compose.ymlversion: '2'services: rethinkdb: image: rethinkdb expose: - "28015"
记住这个概念,因为咱们很快就会用到它。应用程序容器下一步是容器化将要测试的应用程序。须要构建/运行应用程序,连贯数据库,以及裸露用于测试的端口。
DockerfileFROM mhart/alpine-nodeWORKDIR /serviceCOPY package.json .RUN npm installCOPY index.js .integration-test/docker-compose.ymlversion: '2'services: my-service: build: .. command: npm start links: - rethinkdb ports: - "8080:8080" rethinkdb: image: rethinkdb expose: - "28015"
此时,能够应用 docker-compose up 查看服务,以及拜访 http://localhost:8080(只有你领有服务器,并且线路已连贯)。集成测试容器当初,咱们已领有数据库和应用程序,接下来构建测试容器。该容器须要向 my-service 上的 /create 端点发送 POST 申请,并且查看数据库中的变更。为实现这一点,这里应用 tape 和 request-promise 查看端点。
integration-test/index.jsimport test from 'tape';import requestPromise from 'request-promise';const before = test;const after = test;const beforeEach = () => {/*test setup*/};const afterEach = () => {/*test cleanup*/};before('before', (t) => {/*one time setup*/});test('POST /create', (t) => { beforeEach() .then(() => ( requestPromise({ method: 'POST', // yes! we can use the service name in the docker-compose.yml file uri: 'http://my-service:8080/create', body: { thing: 'this thing', }, }) )) .then((response) => { // inspect the response t.equal(response.statusCode, 200, 'statusCode: 200'); }) .then(() => ( // inspect the database rethinkdb.table('table_name') .filter({ thing: 'this thing', }) .count() .run(connection) .then((value) => { t.equal(value, 1, 'have data'); }) )) .catch((error) => t.fail(error)) .then(() => afterEach()) .then(() => t.end());});after('after', (t) => {/*one time setup*/});
测试 Dockerfile 看起来与应用程序 Dockerfile 雷同。
integration-test/DockerfileFROM mhart/alpine-nodeWORKDIR /integrationCOPY package.json .RUN npm installCOPY index.js .
当初,将测试应用程序增加到 docker-compose.yml 文件。
integration-test/docker-compose.ymlversion: '2'services: integration-tester: build: . links: - my-service my-service: build: .. command: npm start links: - rethinkdb ports: - "8080:8080" rethinkdb: image: rethinkdb expose: - "28015"
这里是很酷的局部,当运行 docker-compose up 时,将产生如下事件构建 my-service 和 integration-tester 容器连贯及运行 my-service、integration-tester 和 rethinkdb 容器integration-tester 运行所有测试,直到进行在 integration-tester 进行后,docker-compose 敞开所有容器这正是须要在 CI 中运行的集成测试。
到目前为止,咱们尚未查看 integration-tester 容器的退出码,接下来马上讲述。将所有货色联合起来在所有自动化工作就绪后,咱们须要将所有货色联合起来,并且在测试实现后,执行清理工作。
为此,咱们应用 docker wait 阻塞脚本,获取测试的退出码。咱们应用该退出码输入音讯(通过/失败),并且应用雷同的退出码退出主脚本。这很有用因为大多数(并非全副)CI 环境应用退出码确定测试胜利与否。
咱们还将获取测试容器的日志,并且将它们打印进去,以便在测试失败时提供上下文。上面是一个(极其简短的)脚本,它实现咱们在本地或 CI 中运行集成测试所需的所有。
integration-test/test.sh# define some colors to use for outputRED='\033[0;31m'GREEN='\033[0;32m'NC='\033[0m'# kill and remove any running containerscleanup () { docker-compose -p ci kill docker-compose -p ci rm -f --all}# catch unexpected failures, do cleanup and output an error messagetrap 'cleanup ; printf "${RED}Tests Failed For Unexpected Reasons${NC}\n"'\ HUP INT QUIT PIPE TERM# build and run the composed servicesdocker-compose -p ci build && docker-compose -p ci up -dif [ $? -ne 0 ] ; then printf "${RED}Docker Compose Failed${NC}\n" exit -1fi# wait for the test service to complete and grab the exit codeTEST_EXIT_CODE=`docker wait ci_integration-tester_1`# output the logs for the test (for clarity)docker logs ci_integration-tester_1# inspect the output of the test and display respective messageif [ -z ${TEST_EXIT_CODE+x} ] || [ "$TEST_EXIT_CODE" -ne 0 ] ; then printf "${RED}Tests Failed${NC} - Exit Code: $TEST_EXIT_CODE\n"else printf "${GREEN}Tests Passed${NC}\n"fi# call the cleanup fuctioncleanup# exit the script with the same code as the test service code
exit $TEST_EXIT_CODE示例如欲取得残缺示例,请查看 auth-service。想要看到它的实际效果,你须要做:
git clone https://github.com/hharnisc/auth-service.gitcd auth-servicenpm test
对于更简单的示例(多层微服务),请查看 login-service。
git clone https://github.com/hharnisc/login-service.gitcd login-servicenpm test
总结这种形式在实践中成果很好,我曾经应用该形式为一些微服务执行集成测试。每当我在 CI 中遇到失败时,同样的 Bug 必定能够在本地复现。我遇到的最大问题是,因为应用程序没有齐全启动,而导致的测试失败。为解决该问题,我在应用程序上实现一个 /health API 端点,并且在测试的 before 块外部增加重试。自从修复该问题后,再没遇到其它乖僻的问题,并且始终应用该形式在 CI 中运行集成测试。这真的很有用,并且曾经捕捉一些可能在部署过程中呈现的理论 Bug,我心愿你也能发现它有用。
Read Also
https://docs.docker.com/compose/startup-order/
https://docs.docker.com/compose/networking/#link-containers
https://docs.docker.com/compose/compose-file/#depends_on
https://docs.docker.com/compose/compose-file/#healthcheck
https://docs.docker.com/engine/