# Testing best practices

> 原文：[https://docs.gitlab.com/ee/development/testing_guide/best_practices.html](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html)

*   [Test Design](#test-design)
*   [Test speed](#test-speed)
*   [RSpec](#rspec)
    *   [General guidelines](#general-guidelines)
    *   [Coverage](#coverage)
    *   [System / Feature tests](#system--feature-tests)
        *   [Debugging Capybara](#debugging-capybara)
        *   [Live debug](#live-debug)
        *   [Run `:js` spec in a visible browser](#run-js-spec-in-a-visible-browser)
        *   [Screenshots](#screenshots)
    *   [Fast unit tests](#fast-unit-tests)
    *   [`let` variables](#let-variables)
    *   [Common test setup](#common-test-setup)
    *   [Time-sensitive tests](#time-sensitive-tests)
    *   [Feature flags in tests](#feature-flags-in-tests)
        *   [`stub_feature_flags` vs `Feature.enable*`](#stub_feature_flags-vs-featureenable)
        *   [Stubbing gate](#stubbing-gate)
    *   [Pristine test environments](#pristine-test-environments)
        *   [SQL database](#sql-database)
        *   [Redis](#redis)
        *   [Background jobs / Sidekiq](#background-jobs--sidekiq)
        *   [DNS](#dns)
        *   [Filesystem](#filesystem)
        *   [Persistent in-memory application state](#persistent-in-memory-application-state)
    *   [Table-based / Parameterized tests](#table-based--parameterized-tests)
    *   [Prometheus tests](#prometheus-tests)
    *   [Matchers](#matchers)
        *   [`be_like_time`](#be_like_time)
        *   [`have_gitlab_http_status`](#have_gitlab_http_status)
    *   [Testing query performance](#testing-query-performance)
        *   [QueryRecorder](#queryrecorder)
        *   [GitalyClient](#gitalyclient)
    *   [Shared contexts](#shared-contexts)
    *   [Shared examples](#shared-examples)
    *   [Helpers](#helpers)
    *   [Factories](#factories)
    *   [Fixtures](#fixtures)
    *   [Repositories](#repositories)
    *   [Configuration](#configuration)
    *   [Test environment logging](#test-environment-logging)

# Testing best practices[](#testing-best-practices "Permalink")

## Test Design[](#test-design "Permalink")

在 manbetx 客户端打不开的测试是头等公民，而不是事后的想法. 在设计功能时，必须考虑测试的设计，这一点很重要.

在实现功能时，我们考虑以正确的方式开发正确的功能，这有助于我们将范围缩小到可管理的水平. 在对功能进行测试时，我们必须考虑开发正确的测试，然后涵盖测试可能失败的*所有*重要方式，这可能很快将我们的范围扩大到难以管理的水平.

测试启发法可以帮助解决此问题. 它们简明地解决了错误在我们的代码中表现出来的许多常见方式. 在设计测试时，请花一些时间来回顾已知的测试启发法，以告知我们的测试设计. 我们可以在" [测试工程"](https://about.gitlab.com/handbook/engineering/quality/test-engineering/#test-heuristics)部分的"手册"中找到一些有用的启发式方法.

## Test speed[](#test-speed "Permalink")

GitLab has a massive test suite that, without [parallelization](ci.html#test-suite-parallelization-on-the-ci), can take hours to run. It’s important that we make an effort to write tests that are accurate and effective *以及* fast.

关于测试性能，需要牢记以下几点：

*   `instance_double`和`spy`比`FactoryBot.build(...)`快
*   `FactoryBot.build(...)`和`.build_stubbed`比`.create`更快.
*   当`build` ， `build_stubbed` ， `attributes_for` ， `spy`或`instance_double`可以使用时，不要`create`对象. 数据库持久性很慢！
*   除非*实际*需要测试有效，否则不要将功能标记为需要 JavaScript（通过 RSpec 中的`:js` ）. 无头浏览器测试很慢！

## RSpec[](#rspec "Permalink")

要运行 RSpec 测试：

```
# run test for a file
bin/rspec spec/models/project_spec.rb

# run test for the example on line 10 on that file
bin/rspec spec/models/project_spec.rb:10

# run tests matching the example name has that string
bin/rspec spec/models/project_spec.rb -e associations

# run all tests, will take hours for GitLab codebase!
bin/rspec 
```

使用[Guard](https://github.com/guard/guard)连续监视更改，并仅运行匹配的测试：

```
bundle exec guard 
```

一起使用 spring 和 guard 时，请改用`SPRING=1 bundle exec guard`来使用 spring.

使用[Factory Doctor](https://test-prof.evilmartians.io/#/factory_doctor.md)查找不必要的数据库操作案例，这可能会导致测试缓慢.

```
# run test for path
FDOC=1 bin/rspec spec/[path]/[to]/[spec].rb 
```

### General guidelines[](#general-guidelines "Permalink")

*   使用单个顶级`RSpec.describe ClassName`块.
*   使用`.method`来描述类方法，并使用`#method`来描述实例方法.
*   使用`context`测试分支逻辑.
*   尝试使测试的顺序与类中的顺序匹配.
*   尝试遵循[四阶段测试](https://thoughtbot.com/blog/four-phase-test)模式，使用换行符分隔各个阶段.
*   使用`Gitlab.config.gitlab.host`而不是硬编码`'localhost'`
*   不要断言序列生成的属性的绝对值（请参见[Gotchas](../gotchas.html#do-not-assert-against-the-absolute-value-of-a-sequence-generated-attribute) ）.
*   避免使用`expect_any_instance_of`或`allow_any_instance_of` （见[陷阱](../gotchas.html#do-not-assert-against-the-absolute-value-of-a-sequence-generated-attribute) ）.
*   不要将`:each`参数提供给钩子，因为它是默认值.
*   在钩子`before`和`after` ，最好将它的作用域设置为`:context`不是`:all`
*   当使用作用在给定元素上的`execute_script` `evaluate_script("$('.js-foo').testSomething()")` （或`execute_script` ）时，请事先使用 Capybara 匹配器（例如`find('.js-foo')` ）以确保该元素实际存在.
*   使用`focus: true`可以隔离要运行的部分规范.
*   如果测试中有多个期望，请使用[`:aggregate_failures`](https://relishapp.com/rspec/rspec-core/docs/expectation-framework-integration/aggregating-failures) .
*   对于[空的测试描述块](https://github.com/rubocop-hq/rspec-style-guide#it-and-specify) ，如果测试是不言自明的，请使用`specify`而不是`it do` .
*   当您需要一个实际上不存在的 ID / IID /访问级别时，请使用`non_existing_record_id` / `non_existing_record_iid` / `non_existing_record_access_level` . 使用 123、1234 甚至 999 都很容易，因为在 CI 运行的情况下，这些 ID 实际上可能存在于数据库中.

### Coverage[](#coverage "Permalink")

[`simplecov`](https://github.com/colszowka/simplecov)用于生成代码测试覆盖率报告. 它们是在 CI 上自动生成的，但在本地运行测试时不会自动生成. 要在计算机上运行规格文件时生成部分报告，请设置`SIMPLECOV`环境变量：

```
SIMPLECOV=1 bundle exec rspec spec/models/repository_spec.rb 
```

覆盖率报告会生成到应用程序根目录下的`coverage`文件夹中，您可以在浏览器中打开这些报告，例如：

```
firefox coverage/index.html 
```

使用覆盖率报告来确保您的测试覆盖 100％的代码.

### System / Feature tests[](#system--feature-tests "Permalink")

**注意：**在编写新的系统测试之前， [请考虑**不要**编写一个](testing_levels.html#consider-not-writing-a-system-test) ！

*   Feature specs should be named `ROLE_ACTION_spec.rb`, such as `user_changes_password_spec.rb`.
*   使用描述成功和失败路径的方案标题.
*   避免方案标题未添加任何信息，例如"成功".
*   避免场景标题重复功能标题.
*   Create only the necessary records in the database
*   测试一条幸福的道路和一条不太幸福的道路，仅此而已
*   所有其他可能的路径均应通过单元测试或集成测试进行测试
*   测试页面上显示的内容，而不是 ActiveRecord 模型的内部. 例如，如果您要验证是否已创建记录，请添加期望其记录显示在页面上的期望，而不是`Model.count`增加一.
*   可以查找 DOM 元素，但不要滥用它，因为它会使测试更加脆弱

#### Debugging Capybara[](#debugging-capybara "Permalink")

有时您可能需要通过观察浏览器行为来调试 Capybara 测试.

#### Live debug[](#live-debug "Permalink")

您可以使用规范中的`live_debug`方法暂停 Capybara 并在浏览器中查看网站. 当前页面将在默认浏览器中自动打开. 您可能需要先登录（当前用户的凭据显示在终端中）.

要恢复测试运行，请按任意键.

例如：

```
$ bin/rspec spec/features/auto_deploy_spec.rb:34
Running via Spring preloader in process 8999
Run options: include {:locations=>{"./spec/features/auto_deploy_spec.rb"=>[34]}}

Current example is paused for live debugging
The current user credentials are: user2 / 12345678
Press any key to resume the execution of the example!
Back to the example!
.

Finished in 34.51 seconds (files took 0.76702 seconds to load)
1 example, 0 failures 
```

注意： `live_debug`仅在启用 JavaScript 的规范上起作用.

#### Run `:js` spec in a visible browser[](#run-js-spec-in-a-visible-browser "Permalink")

使用`CHROME_HEADLESS=0`运行规范，例如：

```
CHROME_HEADLESS=0 bin/rspec some_spec.rb 
```

测试将很快进行，但这将使您对正在发生的事情有所了解. 对`CHROME_HEADLESS=0`使用`live_debug`暂停打开的浏览器，并且不会再次打开该页面. 这可用于调试和检查元素.

您还可以添加`byebug`或`binding.pry`暂停执行，并[步通过](../pry_debugging.html#stepping)测试.

#### Screenshots[](#screenshots "Permalink")

我们使用`capybara-screenshot` gem 在失败时自动截屏. 在 CI 中，您可以下载这些文件作为作业工件.

另外，您可以通过添加以下方法在测试中的任何时候手动获取屏幕截图. 请确保在不再需要它们时将其删除！ 有关更多信息，请参见[https://github.com/mattheworiordan/capybara-screenshot#manual-screenshots](https://github.com/mattheworiordan/capybara-screenshot#manual-screenshots) .

在`:js`规范中添加`screenshot_and_save_page`以截图 Capybara 看到的内容，并保存页面源代码.

在`:js`规范中添加`screenshot_and_open_image`以截图 Capybara 看到的内容，并自动打开图像.

由此创建的 HTML 转储缺少 CSS. 这导致它们看起来与实际应用程序截然不同. 有一个添加 CSS 的[小技巧](https://gitlab.com/gitlab-org/gitlab-foss/snippets/1718469) ，可简化调试过程.

### Fast unit tests[](#fast-unit-tests "Permalink")

某些类与 Rails 完全隔离，您应该能够测试它们，而不会因 Rails 环境和 Bundler 的`:default`组的 gem 加载而增加开销. 在这些情况下，您可以在测试文件中`require 'fast_spec_helper'`而不是`require 'spec_helper'` ，并且由于以下原因，测试应该运行得非常快：

*   宝石加载被跳过
*   Rails 应用启动被跳过
*   跳过了 GitLab Shell 和 Gitaly 设置
*   测试存储库设置被跳过

`fast_spec_helper`还支持`lib/`目录中的自动加载类. 这意味着只要您的类/模块仅使用`lib/`目录中的代码，就无需显式加载任何依赖项. `fast_spec_helper`还将加载所有 ActiveSupport 扩展，包括 Rails 环境中常用的核心扩展.

请注意，在某些情况下，当代码使用 gems 或`lib/`没有依赖项时，您可能仍必须使用`require_dependency`加载某些依赖项.

例如，如果要测试调用`Gitlab::UntrustedRegexp`类的代码，该类在`Gitlab::UntrustedRegexp`使用`re2`库，则应将`require_dependency 're2'`添加到需要`re2` gem 的库文件中，以达到此要求明确，也可以将其添加到规范本身中，但最好使用前者.

加载使用`fast_spec_helper`测试大约需要一秒钟，而不是常规`spec_helper`的 30+秒.

### `let` variables[](#let-variables "Permalink")

GitLab 的 RSpec 套件广泛使用`let` （以及严格的非延迟版本`let!` ）变量来减少重复. 但是，有时这有时要以[清楚为代价](https://thoughtbot.com/blog/lets-not) ，因此我们需要为以后的使用设置一些准则：

*   `let!` 变量优于实例变量. `let`变量比`let!`可取`let!` 变量. 局部变量比`let`变量更可取.
*   使用`let`可以减少整个规格文件中的重复项.
*   不要使用`let`定义单个测试使用的变量； 在测试的`it`块中将它们定义为局部变量.
*   不要在顶层`describe`块中定义只用于更深层嵌套`context`或`describe`块的`let`变量. 使定义尽可能靠近使用位置.
*   尽量避免将一个`let`变量的定义覆盖另一个.
*   不要定义只供另一个定义使用的`let`变量. 请改用辅助方法.
*   `let!` 变量应该只在需要与定义为了严格评估的情况下使用，否则`let`就足够了. 请记住， `let`是惰性的，在被引用之前不会被评估.

### Common test setup[](#common-test-setup "Permalink")

在某些情况下，无需为每个示例再次创建相同的对象以进行测试. 例如，需要一个项目和该项目的访客来测试同一项目上的问题，一个项目和一个用户将对整个文件进行测试.

尽可能不要使用`before(:all)`或`before(:context)`实现此目的. 如果这样做，您将需要手动清理数据，因为这些挂钩在数据库事务外部运行.

相反，这可以通过使用来实现[`let_it_be`](https://test-prof.evilmartians.io/#/let_it_be)变量和[`before_all`](https://test-prof.evilmartians.io/#/before_all)钩从[`test-prof`的宝石](https://rubygems.org/gems/test-prof) .

```
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }

before_all do
  project.add_guest(user)
end 
```

这将仅为此上下文创建一个`Project` ， `User`和`ProjectMember` .

`let_it_be`和`before_all`在嵌套上下文中也可用. 使用事务回滚自动处理上下文后进行清理.

Note that if you modify an object defined inside a `let_it_be` block, then you will need to reload the object as needed, or specify the `reload` option to reload for every example.

```
let_it_be(:project, reload: true) { create(:project) } 
```

您还可以指定`refind`选项以完全加载新对象.

```
let_it_be(:project, refind: true) { create(:project) } 
```

### Time-sensitive tests[](#time-sensitive-tests "Permalink")

在基于 Ruby 的测试中可以使用[Timecop](https://github.com/travisjeffery/timecop)来验证对时间敏感的事物. 任何锻炼或验证时间敏感的测试都应使用 Timecop 来防止瞬态测试失败.

Example:

```
it 'is overdue' do
  issue = build(:issue, due_date: Date.tomorrow)

  Timecop.freeze(3.days.from_now) do
    expect(issue).to be_overdue
  end
end 
```

### Feature flags in tests[](#feature-flags-in-tests "Permalink")

在基于 Ruby 的测试中，默认情况下所有功能标志都已启用.

要在测试中禁用功能标记，请使用`stub_feature_flags`帮助器. 例如，要在测试中全局禁用`ci_live_trace`功能标志：

```
stub_feature_flags(ci_live_trace: false)

Feature.enabled?(:ci_live_trace) # => false 
```

如果您希望设置一个仅对某些角色而不对其他角色启用功能标志的测试，则可以在传递给助手的选项中指定此功能. 例如，要为特定项目启用`ci_live_trace`功能标记：

```
project1, project2 = build_list(:project, 2)

# Feature will only be enabled for project1
stub_feature_flags(ci_live_trace: project1)

Feature.enabled?(:ci_live_trace) # => false
Feature.enabled?(:ci_live_trace, project1) # => true
Feature.enabled?(:ci_live_trace, project2) # => false 
```

这代表了 FlipperGate 的实际行为：

1.  您可以启用要启用的指定角色的替代
2.  您可以禁用（删除）指定角色的替代，并还原为默认状态
3.  无法建模您明确禁用指定的参与者

```
Feature.enable(:my_feature)
Feature.disable(:my_feature, project1)
Feature.enabled?(:my_feature) # => true
Feature.enabled?(:my_feature, project1) # => true 
```

```
Feature.disable(:my_feature2)
Feature.enable(:my_feature2, project1)
Feature.enabled?(:my_feature2) # => false
Feature.enabled?(:my_feature2, project1) # => true 
```

#### `stub_feature_flags` vs `Feature.enable*`[](#stub_feature_flags-vs-featureenable "Permalink")

最好使用`stub_feature_flags`在测试环境中启用功能标志. 该方法为简单的用例提供了一个简单且描述良好的界面.

但是，在某些情况下，需要测试更复杂的行为，例如功能标志百分比展示. 这可以使用`.enable_percentage_of_time`和`.enable_percentage_of_actors`来实现

```
# Good: feature needs to be explicitly disabled, as it is enabled by default if not defined
stub_feature_flags(my_feature: false)
stub_feature_flags(my_feature: true)
stub_feature_flags(my_feature: project)
stub_feature_flags(my_feature: [project, project2])

# Bad
Feature.enable(:my_feature_2)

# Good: enable my_feature for 50% of time
Feature.enable_percentage_of_time(:my_feature_3, 50)

# Good: enable my_feature for 50% of actors/gates/things
Feature.enable_percentage_of_actors(:my_feature_4, 50) 
```

具有定义状态的每个功能标志将在测试执行时间内保留：

```
Feature.persisted_names.include?('my_feature') => true
Feature.persisted_names.include?('my_feature_2') => true
Feature.persisted_names.include?('my_feature_3') => true
Feature.persisted_names.include?('my_feature_4') => true 
```

#### Stubbing gate[](#stubbing-gate "Permalink")

要求将作为参数传递给`Feature.enabled?` 和`Feature.disabled?` 是包含`FeatureGate`的对象.

在规范中，您可以使用`stub_feature_flag_gate`方法，该方法可让您快速使用自定义门：

```
gate = stub_feature_flag_gate('CustomActor')

stub_feature_flags(ci_live_trace: gate)

Feature.enabled?(:ci_live_trace) # => false
Feature.enabled?(:ci_live_trace, gate) # => true 
```

### Pristine test environments[](#pristine-test-environments "Permalink")

单个 GitLab 测试执行的代码可以访问和修改许多数据项. 无需在测试运行之前进行仔细的准备，然后再进行清理，则测试可以更改数据，从而影响后续测试的行为. 应不惜一切代价避免这种情况！ 幸运的是，现有的测试框架已经可以处理大多数情况.

当测试环境确实受到污染时，常见的结果就是[不稳定的测试](flaky_tests.html) . 污染通常表现为顺序依赖性：运行规格 A，然后运行规格 B 会可靠地失败，但是运行规格 B，然后运行规格 A 将可靠地成功. 在这些情况下，可以使用`rspec --bisect` （或规格文件的手动成对二`rspec --bisect` ）来确定哪个规格有问题. 要解决此问题，需要对测试套件如何确保原始环境有一些了解. 继续阅读以发现有关每个数据存储的更多信息！

#### SQL database[](#sql-database "Permalink")

这是由`database_cleaner` gem 为我们管理的. 每个规范都包含在一个事务中，一旦测试完成，该事务将回滚. 某些规范将在完成后针对每个表发出`DELETE FROM`查询； 这样可以从多个数据库连接中查看创建的行，这对于在浏览器中运行的规范或迁移规范等非常重要.

使用这些策略而不是众所周知的`TRUNCATE TABLES`方法的结果是，主键和其他序列**不会**在整个规范中重置. 因此，如果您在规范 A 中创建一个项目，然后在规范 B 中创建一个项目，则第一个将具有`id=1` ，而第二个将具有`id=2` .

这意味着规范**永远**不应依赖 ID 或任何其他序列生成的列的值. 为避免意外冲突，规范还应避免手动指定此类列中的任何值. 而是将它们保留为未指定状态，并在创建行后查找值.

#### Redis[](#redis "Permalink")

GitLab 在 Redis 中存储了两个主要的数据类别：缓存项和 Sidekiq 作业.

在大多数规范中，Rails 缓存实际上是一个内存存储. 规格之间已替换了该代码，因此对`Rails.cache.read`和`Rails.cache.write`调用是安全的. 但是，如果某个规范直接进行 Redis 调用，则应适当地使用`:clean_gitlab_redis_cache` ， `:clean_gitlab_redis_shared_state`或`:clean_gitlab_redis_queues`特征对其进行标记.

#### Background jobs / Sidekiq[](#background-jobs--sidekiq "Permalink")

默认情况下，Sidekiq 作业会排队到作业数组中，并且不会进行处理. 如果测试将 Sidekiq 作业排入队列并需要对其进行处理，则可以使用`:sidekiq_inline`特性.

The `:sidekiq_might_not_need_inline` trait was added when [Sidekiq inline mode was changed to fake mode](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/15479) to all the tests that needed Sidekiq to actually process jobs. Tests with this trait should be either fixed to not rely on Sidekiq processing jobs, or their `:sidekiq_might_not_need_inline` trait should be updated to `:sidekiq_inline` if the processing of background jobs is needed/expected.

**注意：** `perform_enqueued_jobs`的用法仅对测试延迟的邮件传递有用，因为我们的 Sidekiq 工作者不是从`ApplicationJob` / `ActiveJob::Base`继承.

#### DNS[](#dns "Permalink")

DNS 请求在测试套件中普遍存在（截至[！22368](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22368) ），因为 DNS 可能会导致问题，具体取决于开发人员的本地网络. 在`spec/support/dns.rb`有可用的 RSpec 标签，如果您需要绕过 DNS 存根，则可以将其应用于测试，例如：

```
it "really connects to Prometheus", :permit_dns do 
```

而且，如果需要更具体的控制，则可以在`spec/support/helpers/dns_helpers.rb`实现 DNS 阻止，并且可以在其他地方调用这些方法.

#### Filesystem[](#filesystem "Permalink")

文件系统数据可以大致分为"存储库"和"其他所有内容". 存储库存储在`tmp/tests/repositories` . 在测试运行开始之前和测试运行结束之后，将清空此目录. 在规格之间不清空它，因此在过程的整个生命周期中，创建的存储库都在此目录中累积. 删除它们很昂贵，但是除非精心管理，否则可能会导致污染.

为避免这种情况，在测试套件中启用了[哈希存储](../../administration/repository_storage_types.html) . 这意味着将为存储库提供一个唯一的路径，该路径取决于其项目的 ID. 由于在规范之间未重置项目 ID，因此可以确保每个规范在磁盘上获得自己的存储库，并防止在规范之间可见更改.

如果规范手动指定了项目 ID，或直接检查`tmp/tests/repositories/`目录的状态，则它应该在运行之前和之后清理该目录. 通常，应完全避免这些模式.

链接到数据库对象的其他文件类别（例如上载）通常以相同的方式进行管理. 在规范中启用了散列存储后，它们将在 ID 确定的位置写入磁盘，因此不会发生冲突.

一些规范通过将`:legacy_storage`特征传递给`projects`工厂来禁用哈希存储. 执行此操作的规范绝**不能**覆盖项目或其任何组的`path` . 默认路径包含项目 ID，因此不会发生冲突. 但是，如果两个规范创建具有相同路径的`:legacy_storage`项目，则它们将使用磁盘上的相同存储库并导致测试环境污染.

其他文件必须由规范手动管理. 例如，如果运行创建`tmp/test-file.csv`文件的代码，则规范必须确保在清理过程中删除了该文件.

#### Persistent in-memory application state[](#persistent-in-memory-application-state "Permalink")

给定`rspec`运行中的所有规范共享相同的 Ruby 进程，这意味着它们可以通过修改规范之间可访问的 Ruby 对象相互影响. 实际上，这意味着全局变量和常量（包括 Ruby 类，模块等）.

全局变量通常不应修改. 如果绝对必要，则可以使用如下所示的块来确保更改被回滚：

```
around(:each) do |example|
  old_value = $0

  begin
    $0 = "new-value"
    example.run
  ensure
    $0 = old_value
  end
end 
```

如果规范需要修改常量，则应使用`stub_const`帮助器以确保更改被回滚.

如果需要修改`ENV`常量的内容，则可以改用`stub_env`帮助器方法.

虽然大多数 Ruby **实例**不在规范之间共享，但**类**和**模块**通常是共享的. 类和模块实例变量，访问器，类变量和其他有状态习语应与全局变量一样对待-除非必须修改，否则请勿对其进行修改！ 特别是，最好使用期望值或依赖项注入以及存根，以避免进行修改. 如果没有其他选择，可以使用类似于上面的全局变量示例的`around`块，但应尽可能避免这种情况.

### Table-based / Parameterized tests[](#table-based--parameterized-tests "Permalink")

这种测试风格用于执行一段具有广泛输入范围的代码. 通过一次指定测试用例，并在每个输入和预期输出表的旁边，可以使您的测试更易于阅读和紧凑.

我们使用[RSpec :: Parameterized](https://github.com/tomykaira/rspec-parameterized) gem. 一个简短的示例，使用表语法并检查 Ruby 相等性是否为一系列输入，可能看起来像这样：

```
describe "#==" do
  using RSpec::Parameterized::TableSyntax

  where(:a, :b, :result) do
    1         | 1        | true
    1         | 2        | false
    true      | true     | true
    true      | false    | false
  end

  with_them do
    it { expect(a == b).to eq(result) }

    it 'is isomorphic' do
      expect(b == a).to eq(result)
    end
  end
end 
```

**注意：**仅将简单值用作`where`块中的输入. 使用 proc，有状态对象，FactoryBot 创建的对象等可能导致[意外结果](https://github.com/tomykaira/rspec-parameterized/issues/8) .

### Prometheus tests[](#prometheus-tests "Permalink")

可以将 Prometheus 度量从一次测试运行保存到另一次测试. 为了确保在每个示例之前重置指标，请在 RSpec 测试中添加`:prometheus`标记.

### Matchers[](#matchers "Permalink")

应该创建自定义匹配器以阐明意图和/或隐藏 RSpec 期望的复杂性. 它们应放在`spec/support/matchers/` . 如果匹配器仅适用于某种类型的规范（例如功能，请求等），则可以放在子文件夹中，但如果它们适用于多种类型的规范，则不应该放在子文件夹中.

#### `be_like_time`[](#be_like_time "Permalink")

从数据库返回的时间的精度可能与 Ruby 中的时间对象不同，因此在规格比较时我们需要灵活的容差. 我们可以使用`be_like_time`来比较时间在彼此之间一秒之内.

Example:

```
expect(metrics.merged_at).to be_like_time(time) 
```

#### `have_gitlab_http_status`[](#have_gitlab_http_status "Permalink")

`have_gitlab_http_status`使用`have_http_status`而不是`have_http_status`和`have_http_status` `expect(response.status).to` `have_http_status` ，因为只要状态不匹配，前者还可以显示响应主体. 每当某些测试开始中断时，这将非常有用，我们很想知道为什么不编辑源代码并重新运行测试.

每当它显示 500 内部服务器错误时，此功能特别有用.

在数字表示形式`206` `:no_content`命名为 HTTP 的状态，如`:no_content` . 请参阅[支持的状态代码](https://github.com/rack/rack/blob/f2d2df4016a906beec755b63b4edfcc07b58ee05/lib/rack/utils.rb#L490)列表.

Example:

```
expect(response).to have_gitlab_http_status(:ok) 
```

### Testing query performance[](#testing-query-performance "Permalink")

测试查询性能使我们能够：

*   断言代码块中不存在 N + 1 个问题.
*   确保代码块中的查询数量不会被忽略.

#### QueryRecorder[](#queryrecorder "Permalink")

`QueryRecorder`允许分析和测试在给定代码块中执行的数据库查询的数量.

有关更多详细信息，请参见[`QueryRecorder`](../query_recorder.html)部分.

#### GitalyClient[](#gitalyclient "Permalink")

`Gitlab::GitalyClient.get_request_count` allows tests of the number of Gitaly queries made by a given block of code:

有关更多详细信息，请参见[`Gitaly Request Counts`](../gitaly.html#request-counts)部分.

### Shared contexts[](#shared-contexts "Permalink")

可以内联声明仅在一个规范文件中使用的共享上下文. 一个以上规范文件使用的任何共享上下文：

*   应该放在`spec/support/shared_contexts/` .
*   如果它们仅适用于某种类型的规范（例如功能，请求等），则可以放在子文件夹中，但如果它们适用于多种类型的规范，则不应放在子文件夹中.

每个文件应仅包含一个上下文并具有描述性名称，例如`spec/support/shared_contexts/controllers/githubish_import_controller_shared_context.rb` .

### Shared examples[](#shared-examples "Permalink")

可以内联声明仅在一个规范文件中使用的共享示例. 多个规范文件使用的任何共享示例：

*   应该放在`spec/support/shared_examples/` .
*   如果它们仅适用于某种类型的规范（例如功能，请求等），则可以放在子文件夹中，但如果它们适用于多种类型的规范，则不应放在子文件夹中.

每个文件应仅包含一个上下文并具有描述性名称，例如`spec/support/shared_examples/controllers/githubish_import_controller_shared_example.rb` .

### Helpers[](#helpers "Permalink")

帮助程序通常是提供一些隐藏特定 RSpec 示例复杂性的方法的模块. 如果不希望与其他规范共享，则可以在 RSpec 文件中定义帮助程序. 否则，应将它们放在`spec/support/helpers/` . 如果助手仅适用于某种类型的规范（例如功能，请求等），则可以放在子文件夹中，但如果它们适用于多种类型的规范，则不应放在子文件夹中.

助手应遵循 Rails 的命名/命名空间约定. 例如`spec/support/helpers/cycle_analytics_helpers.rb`应该定义：

```
module Spec
  module Support
    module Helpers
      module CycleAnalyticsHelpers
        def create_commit_referencing_issue(issue, branch_name: random_git_name)
          project.repository.add_branch(user, branch_name, 'master')
          create_commit("Commit for ##{issue.iid}", issue.project, user, branch_name)
        end
      end
    end
  end
end 
```

助手不应更改 RSpec 配置. 例如，上述帮助器模块不应包括：

```
RSpec.configure do |config|
  config.include Spec::Support::Helpers::CycleAnalyticsHelpers
end 
```

### Factories[](#factories "Permalink")

GitLab 使用[factory_bot](https://github.com/thoughtbot/factory_bot)替代测试夹具.

*   工厂定义存在于`spec/factories/` ，使用其对应模型的复数形式命名（ `User`工厂在`users.rb`中定义）.
*   每个文件应该只有一个顶级工厂定义.
*   FactoryBot 方法混入了所有 RSpec 组. 这意味着您可以（并且应该）调用`create(...)`而不是`FactoryBot.create(...)` .
*   利用[特征](https://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md#traits)来清理定义和用法.
*   定义工厂时，请勿定义结果记录通过验证不需要的属性.
*   从工厂实例化时，不要提供测试不需要的属性.
*   工厂不必仅限于`ActiveRecord`对象. [参见示例](https://gitlab.com/gitlab-org/gitlab-foss/commit/0b8cefd3b2385a21cfed779bd659978c0402766d) .

### Fixtures[](#fixtures "Permalink")

所有固定装置应放置在`spec/fixtures/` .

### Repositories[](#repositories "Permalink")

测试某些功能（例如，合并合并请求）需要在测试环境中具有某种状态的 Git 存储库. GitLab 在某些常见情况下会维护[`gitlab-test`](https://gitlab.com/gitlab-org/gitlab-test)存储库-您可以确保该存储库的副本与`:repository`特性一起用于项目工厂：

```
let(:project) { create(:project, :repository) } 
```

可以的话，请考虑使用`:custom_repo`特性而不是`:repository` . 这使您可以精确地指定哪些文件将出现在项目存储库的`master`分支中. 例如：

```
let(:project) do
  create(
    :project, :custom_repo,
    files: {
      'README.md'       => 'Content here',
      'foo/bar/baz.txt' => 'More content here'
    }
  )
end 
```

这将创建一个包含两个文件的存储库，这两个文件具有默认权限和指定的内容.

### Configuration[](#configuration "Permalink")

RSpec 配置文件是更改 RSpec 配置的文件（即`RSpec.configure do |config|`块）. 它们应放在`spec/support/` .

每个文件都应与特定域相关，例如`spec/support/capybara.rb` ， `spec/support/carrierwave.rb`等.

如果 helpers 模块仅适用于某种规格，则应在`config.include`调用中添加修饰符. 例如，如果`spec/support/helpers/cycle_analytics_helpers.rb`仅适用于`:lib`和`type: :model` specs，则应编写以下内容：

```
RSpec.configure do |config|
  config.include Spec::Support::Helpers::CycleAnalyticsHelpers, :lib
  config.include Spec::Support::Helpers::CycleAnalyticsHelpers, type: :model
end 
```

If a configuration file only consists of `config.include`, you can add these `config.include` directly in `spec/spec_helper.rb`.

对于非常通用的帮助程序，请考虑将它们包括在`spec/support/rspec.rb`文件使用的`spec/fast_spec_helper.rb`文件中. 有关`spec/fast_spec_helper.rb`文件的更多详细信息，请参见[快速单元测试](#fast-unit-tests) .

### Test environment logging[](#test-environment-logging "Permalink")

在运行测试时，会自动配置并启动用于测试环境的服务，包括 Gitaly，Workhorse，Elasticsearch 和 Capybara. 在 CI 中运行时，或者如果需要安装服务，则测试环境将记录有关设置时间的信息，并产生如下日志消息：

```
==> Setting up Gitaly...
    Gitaly set up in 31.459649 seconds...

==> Setting up GitLab Workhorse...
    GitLab Workhorse set up in 29.695619 seconds...
fatal: update refs/heads/diff-files-symlink-to-image: invalid <newvalue>: 8cfca84
From https://gitlab.com/gitlab-org/gitlab-test
 * [new branch]      diff-files-image-to-symlink -> origin/diff-files-image-to-symlink
 * [new branch]      diff-files-symlink-to-image -> origin/diff-files-symlink-to-image
 * [new branch]      diff-files-symlink-to-text -> origin/diff-files-symlink-to-text
 * [new branch]      diff-files-text-to-symlink -> origin/diff-files-text-to-symlink
   b80faa8..40232f7  snippet/multiple-files -> origin/snippet/multiple-files
 * [new branch]      testing/branch-with-#-hash -> origin/testing/branch-with-#-hash

==> Setting up GitLab Elasticsearch Indexer...
    GitLab Elasticsearch Indexer set up in 26.514623 seconds... 
```

当在本地运行并且不需要执行任何操作时，将忽略此信息. 如果您始终希望看到这些消息，请设置以下环境变量：

```
GITLAB_TESTING_LOG_LEVEL=debug 
```

* * *

[Return to Testing documentation](index.html)