{"id":13564576,"url":"https://github.com/bach-sh/bach","last_synced_at":"2025-04-09T05:09:52.330Z","repository":{"id":64296462,"uuid":"202958509","full_name":"bach-sh/bach","owner":"bach-sh","description":"Bach Testing Framework","archived":false,"fork":false,"pushed_at":"2024-09-19T14:05:19.000Z","size":341,"stargazers_count":551,"open_issues_count":2,"forks_count":24,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-04-02T04:04:17.324Z","etag":null,"topics":["bash","bash-scripting","framework","shell","shell-script","shell-scripting","test","testing","testing-framework","unit-testing","unit-testing-framework"],"latest_commit_sha":null,"homepage":"https://bach.sh","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bach-sh.png","metadata":{"files":{"readme":"README-cn.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-18T04:11:27.000Z","updated_at":"2025-03-10T14:01:13.000Z","dependencies_parsed_at":"2024-01-14T03:48:33.143Z","dependency_job_id":"488069d1-e8ba-44e7-b71c-3292145592e6","html_url":"https://github.com/bach-sh/bach","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bach-sh%2Fbach","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bach-sh%2Fbach/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bach-sh%2Fbach/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bach-sh%2Fbach/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bach-sh","download_url":"https://codeload.github.com/bach-sh/bach/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247980837,"owners_count":21027808,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","bash-scripting","framework","shell","shell-script","shell-scripting","test","testing","testing-framework","unit-testing","unit-testing-framework"],"created_at":"2024-08-01T13:01:33.170Z","updated_at":"2025-04-09T05:09:52.315Z","avatar_url":"https://github.com/bach-sh.png","language":"Shell","readme":"# Bach 单元测试框架\n\n[![Build Status](https://travis-ci.org/bach-sh/bach.svg)](https://travis-ci.org/bach-sh/bach)\n[![GitHub Actions](https://github.com/bach-sh/bach/workflows/Testing%20Bach/badge.svg)](https://github.com/bach-sh/bach)\n[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\n[![License: MPL v2](https://img.shields.io/badge/License-MPL%20v2-blue.svg)](https://www.mozilla.org/en-US/MPL/2.0/)\n\n[![Run on Repl.it](https://repl.it/badge/github/bach-sh/bach)](https://repl.it/github/bach-sh/bach)\n\nBach is a [Bash](https://www.gnu.org/software/bash/) testing framework, can be used to test scripts that contain dangerous commands like `rm -rf /`. No surprises, no pain.\n\n[Bach](https://bach.sh) 是一个 Bash 脚本测试框架，可以用来测试包含了类似 `rm -rf /` 这样危险命令的脚本，不会给你惊喜，不会让你感到痛苦。\n\n- 网站: https://bach.sh\n- 代码: https://github.com/bach-sh/bach\n- [See the English version of this document](README.md)\n\n## Bach 入门\n\nBach 单元测试框架作为一个真正的 Bash 脚本的单元测试框架，意味着任何在 PATH 环境变量中的命令都成为了被测的 Bash 脚本的外部依赖，这些外部命令都不会被真正的执行。换句话说，在 Bach 的测试中，除了部分的内置命令外，所有的命令都是 \"Dry Run\" 的。所以，在 Bach 中，验证的是命令被执行的时候是否使用了期望的参数，而非验证命令的执行结果。毕竟，我们测试的是 Bash 脚本的行为，而非测试那些命令是否可以正常工作。Bach 也提供了一系列的 API 可以用于模拟命令的执行。\n\n### Bach 的依赖\n\nBach 需要 Bash v4.3 或更高版本。在 GNU/Linux 上还需要 Coreutils 和 Diffutils，在常用的发行版中都已经默认安装好了。Bach 在 Linux/macOS/Cygwin/Git Bash/FreeBSD 等操作系统或者运行环境中验证通过。\n\n- [Bash](https://www.gnu.org/software/bash/) v4.3+\n- [Coreutils](https://www.gnu.org/software/coreutils/coreutils.html) (*GNU/Linux*)\n- [Diffutils](https://www.gnu.org/software/diffutils/diffutils.html) (*GNU/Linux*)\n\n### 安装 Bach\n\nBach 的安装很简单，只需要下载 [bach.sh](https://github.com/bach-sh/bach/raw/master/bach.sh) 到你的项目中，在测试脚本中用 `source` 命令导入 Bach 框架的 `bach.sh` 即可。\n\n比如：\n\n    source path/to/bach.sh\n\n#### 一个简单的测试示例\n\n    #!/usr/bin/env bash\n    source bach.sh\n\n    test-rm-rf() {\n        # Bach 的标准测试用例是由两个方法组成\n        #   - test-rm-rf\n        #   - test-rm-rf-assert\n        # 这个方法 `test-rm-rf` 是测试用例的执行\n\n        project_log_path=/tmp/project/logs\n        rm -rf \"$project_log_ptah/\" # 注意，这里有个笔误！\n    }\n    test-rm-rf-assert() {\n        # 这个方法 `test-rm-rf-assert` 是测试用例的验证\n        rm -rf /   # 这就是真实的将会执行的命令\n                   # 不要慌！使用 Bach 测试框架不会让这个命令真的执行！\n    }\n\n    test-rm-your-dot-git() {\n        # 模拟 `find` 命令来查找你的主目录下的所有 `.git` 目录，假设会找到两个目录\n\n        @mock find ~ -type d -name .git === @stdout ~/src/your-awesome-project/.git \\\n                                                    ~/src/code/.git\n\n        # 开始执行！删除你的主目录下的所有 `.git` 目录！\n        find ~ -type d -name .git | xargs -- rm -rf\n    }\n    test-rm-your-dot-git-assert() {\n        # 验证在 `test-rm-your-dot-git` 这个测试执行方法中最终是否会执行以下这个命令。\n\n        rm -rf ~/src/your-awesome-project/.git ~/src/code/.git\n    }\n\n更多的测试示例请看 [tests/bach-testing-framework.test.sh](tests/bach-testing-framework.test.sh)\n\n#### Windows\nshebang 得是\n```\n#!/bin/bash\n```\n而非\n```\n#!/bin/sh\n```\n\n若以 Cygwin 而非 Git Bash 运行，要将 `bach.sh` 的行尾序列改为 `LF`.\n\n### 用 Bach 来写脚本测试\n\n与我们所熟悉的测试框架不同的是，Bach 的标准测试用例是由两个方法组成，这样做的目的是为了让测试用例的验证变得简单。测试用例的执行部分是写在以 `test-` 开头的方法中，然后 Bach 会寻找与这个测试方法名称对应的以 `-assert` 结尾的测试验证方法。所以，每一个 Bach 的测试执行方法都必须不能以 `-assert` 作为后缀。比如，一个名为 `test-rm-rf` 的测试执行方法，对应的测试验证方法是 `test-rm-rf-assert`。\n\n例子：\n\n    source bach.sh\n\n    test-rm-rf() {\n        project_log_path=/tmp/project/logs\n        sudo rm -rf \"$project_log_ptah/\" # 这里写错了变量名，Bash 默认让变量变成空字符串，这可能是个严重的问题！\n    }\n    test-rm-rf-assert() {\n        sudo rm -rf /\n    }\n\nBach 会分别运行两个方法，去验证两个方法中执行的命令及其参数是否是一致的。第一个方法 `test-rm-rf` 是 Bach 的测试用例的执行，与之对应的测试验证方法就是 `test-rm-rf-assert` 这个方法\n\n如果 Bach 没有找到某个测试用例的测试验证方法，Bach 会尝试用传统的一个测试方法的形式。在这种方式下，测试执行方法里面必须要有断言 API 的调用，否则改测试一定会失败。\n\n例子：\n\n    test-single-function-style() {\n        declare i=2\n        @assert-equals 4 \"$((i*2))\"\n    }\n\nBach 的断言 API 有：\n\n- `@assert-equals`\n- `@assert-fail`\n- `@assert-success`\n\n如果 Bach 没有找到对应的测试验证方法，同时在测试执行方法里面也没有断言的调用，这个测试用例就一定是失败的。\n\n如果一个测试用例的方法名称是以 `test-ASSERT-FAIL` 开头，则意味着反转这个测试用例的执行结果。也就是说，如果测试验证成功，反而结果是失败的。如果测试验证失败，则结果是成功的。\n\n### 用 Bach 来模拟命令的调用\n\nBach 测试框架中有一系列的 Mock API，可以用于模拟命令和脚本的执行。\n\nMock API 有：\n\n- `@mock`\n- `@ignore`\n- `@mockall`\n- `@mocktrue`\n- `@mockfalse`\n- `@@mock`\n\n但是仍然有个别内建命令是不允许 mock 的，有：\n\n- `builtin`\n- `declare`\n- `eval`\n- `set`\n- `unset`\n- `true`\n- `false`\n- `read`\n\n如果在测试用例的执行中，试图去模拟这些被禁止模拟的内建命令，测试用例一定会失败的。对于这种情况，通常的解决办法是，把这些被禁止模拟的内建命令用一个方法包装起来，然后用 Bach 来模拟这个方法。\n\n### 在 Bach 的测试用例中调用真实的命令\n\n在测试用例中，为了能够稳定的、重复的、随机的执行测试用例，同时也是为了写出单元测试，我们通常要避免直接调用外部命令。但 Bach 也提供了一系列的 API 用于直接执行命令。\n\n因为 Bach 中可以模拟几乎所有的命令，而且 Bach 的实现代码里面也一定会执行这些真实的命令。如果不可避免的要在测试用例中执行真实的命令，Bach 提供了名为 `@real` 的 API 来执行真实命令。只要在希望执行的命令前面加上 `@real` 就可以跳过 Bach 的限制。\n\nBach 也提供了一些常用命令的 API，这些 API 对应的真实命令都是在 Bach 启动之前从系统的 PATH 环境变量中获取的，有：\n\n- `@cd`\n- `@command`\n- `@echo`\n- `@exec`\n- `@false`\n- `@popd`\n- `@pushd`\n- `@pwd`\n- `@set`\n- `@trap`\n- `@true`\n- `@type`\n- `@unset`\n- `@eval`\n- `@source`\n- `@cat`\n- `@chmod`\n- `@cut`\n- `@diff`\n- `@find`\n- `@env`\n- `@grep`\n- `@ls`\n- `@shasum`\n- `@mkdir`\n- `@mktemp`\n- `@rm`\n- `@rmdir`\n- `@sed`\n- `@sort`\n- `@tee`\n- `@touch`\n- `@which`\n- `@xargs`\n\n由于 `command` 和 `xargs` 命令的特殊性，Bach 中很特别的为这两个命令做了模拟。\n\n因为 Bach 中的模拟命令的实现都是采用创建同名函数的方式，所以用 `command` 命令是无法执行这些被模拟的命令的，`@command` 这个 API 则会根据后面的命令的类型来决定行为。\n\n而 `@xargs` 中，则真的会调用系统中的 `xargs` 命令，但默认也不会真的执行命令。\n\n例如：\n\n    test-xargs-no-dash-dash() {\n        @mock ls === @stdout foo bar\n\n        ls | xargs -n1 rm -v\n    }\n    test-xargs-no-dash-dash-assert() {\n        xargs -n1 rm -v\n    }\n\n\n    test-xargs() {\n        @mock ls === @stdout foo bar\n\n        ls | xargs -n1 -- rm -v\n    }\n    test-xargs-assert() {\n        rm -v foo\n        rm -v bar\n    }\n\n\n    test-xargs-0() {\n        @mock ls === @stdout foo bar\n\n        ls | xargs -- rm -v\n    }\n    test-xargs-0-assert() {\n        rm -v foo bar\n    }\n\n我们还可以模拟测试命令 `[ ... ]`。但如果没有模拟的话，测试将会保持原有的行为。\n\n例如:\n\n    test-if-string-is-empty() {\n        if [ -n \"original behavior\" ] # 没有模拟这个测试，将保持默认行为\n        then\n            It keeps the original behavior by default # 应该看到这一行\n        else\n            It should not be empty\n        fi\n\n        @mockfalse [ -n \"Non-empty string\" ] # 我们可以通过模拟一个测试来反转其结果\n\n        if [ -n \"Non-empty string\" ]\n        then\n            Non-empty string is not empty # 不，我们看不到这个\n        else\n            Non-empty string should not be empty but we reverse its result\n        fi\n    }\n    test-if-string-is-empty-assert() {\n        It keeps the original behavior by default\n\n        Non-empty string should not be empty but we reverse its result\n    }\n\n    # 模拟测试命令 `[ ... ]` 是很有用的，比如当我们想去检查一个有绝对路径的文件是否存在的时候\n    test-a-file-exists() {\n        @mocktrue [ -f /etc/an-awesome-config.conf ]\n        if [ -f /etc/an-awesome-config.conf ]; then\n            Found this awesome config file\n        else\n            Even though this config file does not exist\n        fi\n    }\n    test-a-file-exists-assert() {\n        Found this awesome config file\n    }\n\n### 配置 Bach\n\n有一系列的以 `BACH_` 开头的环境变量用于配置 Bach 测试框架，有\n\n- `BACH_DEBUG`\n  默认为 `false`，如果为 `true` 就会启用 Bach 的 `@debug` API\n- `BACH_COLOR`\n  默认为 `auto`，会根据使用情况来决定是否启用颜色输出；设置为 `always` 始终允许颜色输出；设置为 `no` 则不允许颜色输出。\n- `BACH_TESTS`\n  默认为空，表示执行所有测试。可以使用通配符来匹配之允许执行的测试用例。\n- `BACH_DISABLED`\n  默认为 `false`，如果为 `true` 则会禁用 Bach。如果希望把 Bach 直接集成到程序中，通过特定的开关来决定是否禁用 Bach。\n- `BACH_ASSERT_DIFF`\n  默认为系统中原始 PATH 环境变量中找到的第一个 `diff` 命令。用于比较测试执行方法和测试验证方法的执行结果时使用。\n- `BACH_ASSERT_DIFF_OPTS`\n  默认为 `-u`，用于 `$BACH_ASSERT_DIFF` 命令的选项。\n\n## Bach 的限制\n\n### 不能阻止直接使用绝对路径的命令调用\n\n在这种场景下，直接命令的调用是由系统 API 直接完成，并不会与 Bash 发生任何交互，所以 Bach 无法拦截这样的命令。我们可以在 Bash 脚本中把这些绝对路径调用的命令，用一个方法来包装起来，然后再用 `@mock`/`@@mock` 等 API 来模拟这个方法。\n\n### 在测试中禁止变更 PATH 环境变量\n\n因为 Bach 要拦截所有的命令调用，而重设 `PATH` 会破坏 Bach 的行为，所以 Bach 中默认设置 PATH 环境变量为只读。\n\n对于需要重设 PATH 的场景，建议使用 `declare` 内建命令来避免重设一个只读环境变量而导致的出错。\n\n### 在 Bach 的以 `-assert` 结尾的测试验证方法中不可以模拟任何命令\n\n因为 Bach 的测试验证就是通过分别执行测试执行函数和测试验证函数来达到的。我们已经在测试执行函数中模拟了命令的调用，在测试验证函数中，就应该直接写出期待执行的命令及其参数。在测试验证中模拟命令的调用是没有道理的。\n\n### 在 Bach 中无法阻止 I/O 重定向\n\nBach 中被模拟的命令，已经支持了从标准输入读取命令和管道的调用。但对于使用 `\u003e` `\u003e\u003e` 等等操作符的使用，解决办法一个是把重定向的命令用一个方法包装起来，另一个办法是在加载脚本的时候，用 `sed` 等命令把 `\u003e` `\u003e\u003e` 等操作符用引号包含起来，把重定向的操作转换为普通的参数。\n\n### 必须模拟管道中的每一个命令\n\n在 Bash 中的管道命令事实上是运行在子进程中的，如果没有模拟这些命令，会导致管道命令的执行顺序并不一定是按照脚本中的调用顺序来执行的。为了确保 Bach 测试用例的执行稳定性，所有的管道命令必须按照其参数的使用来精确的模拟。\n\n### 使用空集符号 `∅` 来表示空字符串\n\n因为无法在终端上显示出空字符串, Bach 选择了用红色空集符号 `∅` 来指示这是一个空字符串。\n\n当我们在测试报告中看到这个红色符号 `∅` 的时候，表示这个参数实际上是一个空字符串。\n\n```\n-foobar  ∅\n+foobar\n```\n\n## Bach API 列表\n\nBach 测试框架中提供的 API 都是以 `@` 开头的。\n\n### @assert-capture\n\n这个 API 仅仅可以用于测试验证函数，用于验证命令读取的输入是否为预期。\n\n使用方法：\n\n1. 在测试执行函数中，使用`@capture` API记录命令的输入。\n2. 在验证函数中，使用`@assert-capture` API验证捕获的数据是否与预期输入匹配。\n\n例子：\n\n    test-capture() {\n        @capture foobar foo # 要记录命令 `foobar foo` 读取的数据\n        echo hello | foobar foo # 命令 `foobar foo` 从管道读取数据\n    }\n    test-capture-assert() {\n        @assert-capture foobar foo \u003c\u003c\u003c \"hello\" # 验证命令 `foobar foo` 读取的数据是 `hello`\n    }\n\n### @assert-equals\n\n断言两个值是否相等。\n\n例子：\n\n    @assert-equals \"hello world\" \"HELLO WORLD\"\n    @assert-equals 1 2\n\n### @assert-fail\n\n断言前一个命令会执行失败\n\n例子：\n\n    false # 这个命令的返回值不为 0，则断言会成功\n    @assert-fail\n\n    true # 这个命令的返回值为 0，则断言会失败\n    @assert-fail\n\n### @assert-success\n\n断言前一个命令要执行成功\n\n例子：\n\n    true # 这个命令的返回值为 0，则断言会成功\n    @assert-fail\n\n    false # 这个命令的返回值不为 0，则断言会失败\n    @assert-fail\n\n### @capture\n\n记录命令读取的数据，包括通过管道、Here Document 等 I/O 重定向的方式。\n\n例子请参考 `@assert-capture` API。\n\n### @cd\n\n执行真正的内置 `cd` 命令。\n\n### @command\n\n执行真正的内置 `command` 命令。\n\n### @comment\n\n这个命令用于在执行的结果中输出会被 Bach 忽略的注释，对于在失败的测试结果中定位问题很很有用。\n\n### @debug\n\n这个命令用于在执行测试的调试，如果执行测试的时候没有启用 `xtrace` 则这个命令不会有任何输出。\n\n### @die\n\n这个命令用于立刻终止并非正常退出，显示可选的错误消息\n\n### @do-not-panic\n\n为了尽可能的防止敲错这个 API，Bach 提供了以下的不同名称：\n\n- `donotpanic`\n- `dontpanic`\n- `do-not-panic`\n- `dont-panic`\n- `do_not_panic`\n- `dont_panic`\n\n如果在 Bach 接管运行环境前执行了一个包含危险命令的测试，可能会带来很严重的后果。在包含危险命令的前面用上这个命令，会防止无意中调用这个危险的测试。\n\n### @do-nothing\n\n什么也不做。\n\n通常在验证函数中只有这个 API 的时候来验证在测试汉中没有执行任何命令。\n\n例如：\n\n    test-nothing() {\n        declare i=9\n        if [[ \"$i\" -eq 0 ]]; then\n            do-something\n        fi\n    }\n    test-nothing-assert() {\n        @do-nothing\n    }\n\n### @dryrun\n\n在默认情况下，Bach 会使用 `@dryrun` 来确保任何命令都不会被运行。如果之前已经模拟了一个命令，但又不想真正运行，可以使用 `@dryrun` 来防止该命令被执行。\n\n例子：\n\n    @mock ls === @stdout file1 file2 # 做了了一个 `ls` 命令的模拟\n    ls # 这个命令会返回 `file1` 和 `file2`\n    @dryrun ls # 不运行 ls\n\n### @echo\n\n执行真正的内置 `echo` 命令。\n\n### @err\n\n显示一个错误消息\n\n### @exec\n\n执行真正的内置 `exec` 命令。\n\n### @fail\n\n立刻失败当前测试。\n\n### @false\n\n执行真正的内置 `false` 命令。\n\n### @ignore\n\n忽略被模拟的函数。\n\n被忽略的函数，不会影响任何的测试和验证，且永远执行成功。\n\n与 `@mocktrue` 不同的是，`@ignore` 忽略掉命令的任何参数。\n\n例子：\n\n    @ignore echo cd # 忽略掉 `echo` 和 `cd` 命令\n\n### @load_function\n\n加载指定脚本中的函数，用于测试一个脚本中的指定函数\n\n例子：\n\n    @load_function path/to/script.sh func-name # 加载脚本 path/to/script.sh 里面的 func-name 定义\n    func-name foo bar # 加载成功后，就可以执行执行函数 `func-name` 了\n\n### @mock\n\n模拟命令或脚本的执行，如果命令执行的时候需要指定不同的参数，那就需要多次模拟 API `@@mock`。\n\n使用方法，在 `@mock` 后面是要模拟的完整命令，然后 `===` 后面是模拟的行为或输出。 例如： `@mock your-command param1 param2 === @stdout out1 out2`\n\n如果省略了 `===`，默认的行为是调用 `@dryrun` API 输出命令，这种方式常见于模拟管道命令来防止随机顺序的验证。如果需要将管道中的输入传递到输出，请使用 API `@mockpipe`。\n\n注意：\n- 如果要模拟一个脚本的执行，该脚本的路径必须是相对路径，不能模拟绝对路径的脚本。\n- 多次模拟一个命令，只有最后一次的模拟生效\n\n例子：\n\n#### 模拟命令\n\n    @mock ls file1 === @stdout file2\n\n    ls file1 # 会在控制台输出 file2，列出文件 `file1`，但显示的是 `file2`，很怪，对不对？\n\n    ls foo bar # 因为没有模拟 `ls` 命令和特定的参数，所以 `ls foo bar` 需要被验证\n\n#### 不指定行为或者输出的模拟\n\n    # 模拟了管道中的所有命令可以确保验证的时候命令的执行顺序如同管道中的顺序一致\n\n    @mock foo param1 param2\n    @mock bar param1\n    @mock foobar\n\n    foo param1 param2 | bar parm1 | foobar\n\n#### 模拟命令，但希望使用复杂的实现\n\n例子：\n\n    @mock ls \u003c\u003c\u003c\\CMD\n      if [[ \"$var\" -eq 1 ]]; then\n        @stdout one\n      else\n        @stdout others\n      fi\n    CMD\n\n\n    var=1\n    ls # 会输出 one，因为变量 `var` 的值是 `1`\n\n    @unset var\n    ls # 会输出 others，因为还没有定义变量 `var`\n\n### @@mock\n\n模拟命令的多次执行返回不同的值。\n\n    test-ls() {\n      @@mock ls === @stdout file1\n      @@mock ls === @stdout file2\n      @@mock ls === @stdout file3\n      ls\n      ls\n      ls\n      ls # 如果没有更多的命令序列，则会返回最后一次的模拟的结果\n    }\n    test-ls-assert() {\n      @cat \u003c\u003cEOF\n      file1\n      file2\n      file3\n      file3\n    EOF\n    }\n\n### @mockall\n\n批量模拟多个命令，每个参数为一个命令。\n\n例子：\n\n    @mockall ls cd\n\n### @mockfalse\n\n模拟命令会执行失败\n\n例子：\n\n    @mockfalse ls\n\n### @mocktrue\n\n模拟命令会执行成功\n\n例子：\n\n    @mocktrue false\n\n### @mockpipe\n\n模拟管道，被模拟的命令在管道中被调用时，将会把输入原封不变的输出。用 `@mock` 默认模拟的命令会丢弃管道中的数据。\n\n例子：\n\n    @mock say hello === @echo hello\n\n    @mockpipe a_command_in_a_pipeline some parameters\n    say hello | a_command_in_a_pipeline some parameters # 这个管道命令将会输出 hello\n\n    @mock foobar foo bar\n    say hello | foobar foo bar # 这个管道命令不会有任何输出，因为默认模拟的命令 `foobar foo bar` 会丢弃管道中的数据\n\n### @mock-regex\n\n模拟命令，类似于 `@mock`，但它支持使用正则表达式来匹配命令的参数。使用 `^` 来表示匹配第一个参数的开头，多个参数会通过空格连接，并整体与正则表达式进行匹配。\n\n例子：\n\n    # 模拟判断 [ -f anyfile ] 都会返回成功\n    @mock-regex [ -f [^ ]* ] === @true\n    if [ -f config ]; then\n      echo Found config file\n    fi\n\n### @out\n\n在标准输出终端上输出内容。\n\n### @popd\n\n执行真正的内置 `popd` 命令。\n\n### @pushd\n\n执行真正的内置 `popd` 命令。\n\n### @pwd\n\n执行真正的内置 `pwd` 命令。\n\n### @real\n\n执行真正的命令，将会执行在调用 Bach 时的 PATH 环境变量里面的命令。\n\n### @run\n\n用于在 Bach 的测试中执行被测试的脚本\n\n### @setup\n\n在测试的脚本中的每一个测试和测试断言函数之前都会被执行。\n\n注意，由于在测试断言里面不允许模拟任何命令，所以在 `@setup` 里模拟的命令将会引起测试失败。\n\n例子：\n\n    @setup {\n        @echo 在测试函数和测试断言函数中都会执行\n    }\n\n### @setup-assert\n\n在测试的脚本中的每一个测试断言函数之前都会被执行。\n\n注意，由于在测试断言里面不允许模拟任何命令，所以在 `@setup-assert` 里模拟的命令将会引起测试失败。\n\n例子：\n\n    @setup-assert {\n        @echo 在测试断言函数中执行\n    }\n\n### @setup-test\n\n在测试的脚本中的每一个测试函数之前都会被执行。\n\n例子：\n\n    @setup-tests {\n        @echo 在测试函数中执行\n    }\n\n### @stderr\n\n用于在错误控制台输出内容，每个参数输出一行。\n\n### @stdout\n\n用于在标准控制台输出内容，每个参数输出一行。\n\n### @trap\n\n执行真正的内置 `trap` 命令。\n\n### @true\n\n执行真正的内置 `true` 命令。\n\n### @type\n\n执行真正的内置 `type` 命令。\n\n## 用 Bach 来学习 Bash 编程\n\n我们可以用 Bach 来学习 Bash 编程，而不用担心有任何问题。\n\n    test-learn-bash-no-double-quote-star() {\n        @touch bar1 bar2 bar3 \"bar*\"\n\n        function cleanup() {\n            rm -rf $1\n        }\n\n        # 要删除这个错误的文件名 bar*，而不删除其他文件\n        cleanup \"bar*\"\n    }\n    test-learn-bash-no-double-quote-star-assert() {\n        # 但是在 cleanup 里面，遗漏了双引号，会导致变量被二次展开，将会删除所有文件\n        rm -rf \"bar*\" bar1 bar2 bar3\n    }\n\n    test-learn-bash-double-quote-star() {\n        @touch bar1 bar2 bar3 \"bar*\"\n\n        function cleanup() {\n            rm -rf \"$1\"\n        }\n\n        # 要删除这个错误的文件名 bar*，而不删除其他文件\n        cleanup \"bar*\"\n    }\n    test-learn-bash-double-quote-star-assert() {\n        # 在 cleanup 的实现里面有了双引号，将会正确的删除 `bar*` 这个文件。\n        rm -rf \"bar*\"\n    }\n\n## Bach 的规划\n\n* 开发一个 Bach 的命令行工具\n* 在 Docker 容器内执行测试\n\n## 正在使用 Bach 的客户\n\n* 宝马集团(BMW Group)\n* 华为(Huawei)\n\n*按英文名称的字母顺序排序*\n\n## 版本\n\nBach 当前最新的版本是 0.6.2，查看[Bach 的发布列表](https://github.com/bach-sh/bach/releases)\n\n## 作者\n\n* **Chai Feng** [github.com/chaifeng](https://github.com/chaifeng), [chaifeng.com](https://chaifeng.com)\n\n## 版权\n\nBach 测试框架采用了双版权协议：\n\n- [GNU General Public License v3.0](LICENSE.GPL-3.0)\n- [Mzilla Public License 2.0](LICENSE.MPL-2.0)\n\n请查看 [LICENSE](LICENSE)。\n","funding_links":[],"categories":["Shell","\u003ca name=\"scripting\"\u003e\u003c/a\u003escripting","bash"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbach-sh%2Fbach","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbach-sh%2Fbach","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbach-sh%2Fbach/lists"}