rspec 测试页面元素_如何使用RSpec对Go应用进行黑盒测试

rspec 测试页面元素

by Dmitriy Lutsko

德米特里·卢茨科(Dmitriy Lutsko)

如何使用RSpec对Go应用进行黑盒测试 (How to black box test a Go app with RSpec)

Automated testing is all the rage in web development these days and goes on across the whole industry. A well-written test dramatically reduces the risk of accidentally breaking an application when you add new features or fix bugs. When you have a complex system that’s built from several components that interact with each other, it’s incredibly hard to test how each component interacts with other components.

如今,自动化测试在Web开发中风靡一时,并在整个行业中不断发展。 精心编写的测试可以大大降低添加新功能或修复错误时意外破坏应用程序的风险。 当您有一个由多个相互交互的组件构建的复杂系统时,很难测试每个组件如何与其他组件交互。

Let’s take a look at how to write good automatic tests for developing components in Go and how to do so using the RSpec library in Ruby on Rails.

让我们看一下如何编写好的自动测试以在Go中开发组件,以及如何使用Ruby on Rails中的RSpec库来实现。

将Go添加到我们项目的技术堆栈中 (Adding Go to our project’s tech stack)

One of the projects that I’m working on at my company, eTeam, can be divided into an admin panel, user dashboard, report generator and request processor that handles requests from different services integrated into the application.

我在公司从事的项目之一eTeam可以分为管理面板,用户仪表板,报告生成器和请求处理器,它们可以处理来自集成到应用程序中的不同服务的请求。

The part of the project that processes requests is the most important, thus we needed to maximize its reliability and availability.

项目中处理请求的部分最为重要,因此我们需要最大限度地提高其可靠性和可用性。

As part of a monolithic application, there’s a high risk of a bug affecting the request processor, even when there are changes in code in parts of the app not related to it. Likewise, there’s a risk of crashing the request processor when other components are under a heavy load. The number of Ngnix workers for the app is limited, which can cause problems as the load increases. For instance, when a number of resource-intensive pages are opened at once in the admin panel, the processor slows down or even crashes the entire app.

作为整体应用程序的一部分,即使应用程序中与它不相关的部分中的代码发生更改,也存在错误影响请求处理器的高风险。 同样,当其他组件承受重负载时,也有可能使请求处理器崩溃。 该应用程序的Ngnix worker数量是有限的,随着负载的增加,这可能会引起问题。 例如,当在管理面板中一次打开大量资源密集的页面时,处理器会减慢速度甚至崩溃整个应用程序。

These risks, as well as the maturity of the system in question — we didn’t have to make major changes for months — made this app an ideal candidate for creating a separate service to handle request processing.

这些风险以及相关系统的成熟度(我们几个月都不必进行重大更改)使该应用程序成为创建单独服务来处理请求处理的理想选择。

We decided to write the separate service in Go, that shared the database access with the Rails application that remained responsible for changes in the table structure. With only two applications, such a scheme with a shared database works fine. Here’s what it looked like:

我们决定用Go编写单独的服务,该服务与仍负责表结构更改的Rails应用程序共享数据库访问权限。 仅使用两个应用程序,这种具有共享数据库的方案就可以正常工作。 看起来是这样的:

We wrote and deployed the service in a separate Rails instance. This way, there was no need to worry that the request processor would be affected whenever the Rails app was deployed. The service directly accepts HTTP requests without Ngnix and doesn’t use a lot of memory. You could call it a minimalist app!

我们在单独的Rails实例中编写和部署了该服务。 这样,无需担心部署Rails应用程序时请求处理器会受到影响。 该服务无需Ngnix即可直接接受HTTP请求,并且不占用大量内存。 您可以将其称为简约应用!

Go中的单元测试问题 (The problem with unit testing in Go)

We created unit tests for the Go application where all database requests were mocked. In addition to other arguments for this solution, the main Rails application was responsible for the database structure, thus the Go application didn’t actually have the information for creating a test database. Half of processing was business logic, while the other half was database queries, all of which were mocked.

我们为模拟所有数据库请求的Go应用程序创建了单元测试。 除了该解决方案的其他参数之外,主要的Rails应用程序还负责数据库结构,因此Go应用程序实际上没有创建测试数据库的信息。 处理的一半是业务逻辑,而另一半是数据库查询,所有这些都是模拟的。

Mocked objects are much less readable in Go than in Ruby. Whenever new functions were added for reading data from the database, we had to add mocked objects during many failed tests that had previously worked. In the end, such unit tests didn’t prove very effective and were extremely fragile.

在Go中,模拟对象比在Ruby中可读性低得多。 每当添加新功能以从数据库读取数据时,我们就不得不在许多以前无法通过的失败测试中添加模拟对象。 最后,这种单元测试并没有证明非常有效,而且非常脆弱。

我们的解决方案 (Our solution)

In order to make up for these drawbacks, we decided to cover the service with functional tests in the Rails application and test the service in Go like a black box. White-box testing wouldn’t work in any case, since it was impossible to use Ruby to get inside the service and see whether a method was being called.

为了弥补这些缺陷,我们决定在Rails应用程序中对服务进行功能测试,并像黑盒子一样在Go中测试服务。 白盒测试在任何情况下都不起作用,因为不可能使用Ruby进入服务内部并查看是否正在调用方法。

That also means that requests sent through the test service were also impossible to mock, thus we needed another application for managing and writing these tests. Something like RequestBin would work, but it had to work locally. We’d already written a utility that’d do the trick, so we decided to try using it.

这也意味着通过测试服务发送的请求也无法被模拟,因此我们需要另一个应用程序来管理和编写这些测试。 像RequestBin这样的东西可以工作,但必须在本地工作。 我们已经编写了可以解决问题的实用程序,因此我们决定尝试使用它。

This was the resulting setup:

这是结果设置:

  1. RSpec compiles and runs the Go binary with the configuration in which access to the test database is specified along with a particular port for receiving HTTP requests, i.e 8082.

    RSpec使用以下配置编译并运行Go二进制文件:在该配置中,指定了对测试数据库的访问以及用于接收HTTP请求的特定端口,即8082。
  2. It also runs the utility, which records HTTP requests coming to port 8083.

    它还运行该实用程序,该实用程序记录到端口8083的HTTP请求。
  3. We write regular tests in RSpec. This creates the necessary data in the database and sends a request to localhost:8082 as if it were an external service such as HTTParty.

    我们在RSpec中编写常规测试。 这将在数​​据库中创建必要的数据,并将请求发送到localhost:8082,就好像它是外部服务(例如HTTParty)一样。
  4. We parse the response, check changes in the database, receive a list of requests that were recorded by the RequestBin substitute and check them.

    我们解析响应,检查数据库中的更改,接收由RequestBin替代项记录的请求列表并进行检查。

实施细节 (Details of the implementation)

Here’s how we implemented this. As a demonstration, let’s call the test service TheService and create a wrapper:

这是我们实现此方法的方式。 作为演示,我们将测试服务称为TheService并创建一个包装器:

It’s worth mentioning that autoloading files have to be configured in the support folder when using RSpec:

值得一提的是,使用RSpec时,必须在support文件夹中配置自动加载文件:

Dir[Rails.root.join('spec/support/**/*.rb')].each {|f| require f}

The start method:

启动方法:

  • Reads the configuration information necessary to start TheService. This information can differ among different developers and therefore is excluded from Git. The configuration contains the necessary settings for starting the program. All of these different configurations are in a single place so you don’t have to create unnecessary files.

    读取启动TheService所需的配置信息。 这些信息在不同的开发人员中可能有所不同,因此从Git中排除。 该配置包含用于启动程序的必要设置。 所有这些不同的配置都放在一个地方,因此您不必创建不必要的文件。
  • Compiles and runs through go run <path to main.go> <path to config>

    编译并通过go run <path to main.go> <path t config的go run <path to main.go> <path t >运行

  • Polls every second and waits until TheService is ready to accept requests.

    每秒轮询一次,直到TheService准备好接受请求为止。
  • Records the identifier of each process in order to not repeat anything and to have the ability to stop a process.

    记录每个进程的标识符,以便不重复任何内容并具有停止进程的能力。

The configuration itself:

配置本身:

The “stop” method simply stops the process. There’s a gotcha though! Ruby runs a “go run” command, which compiles TheService and launches a binary in a child process with an unknown ID. If we just stop the process that’s running in Ruby, the child process doesn’t stop automatically and the port will remain in use. Thus stopping TheService has to go through the Process Group ID:

“停止”方法只是停止该过程。 虽然有陷阱! Ruby运行“ go run”命令,该命令编译TheService并在子进程中以未知ID启动二进制文件。 如果我们只是停止在Ruby中运行的进程,则子进程不会自动停止,并且该端口将继续使用。 因此,停止TheService必须经过Process Group ID:

Next we prepare the “shared_context” where we define the default variables, start TheService if it hasn’t already been launched and temporarily turn off VCR since VCR would see what we’re doing as an external service request, but we don’t want VCR to mock requests at this point:

接下来,我们准备“ shared_context”,在其中定义默认变量,如果尚未启动,则启动TheService,并暂时关闭VCR,因为VCR会看到我们在做外部服务请求,但我们不想VCR此时模拟请求:

And now we can look at writing the specs themselves:

现在我们可以看看自己编写规范:

TheService can make HTTP requests to external services. We can configure it to redirect requests to the local utility that logs them. For this utility, there’s also a wrapper for starting and stopping it that’s similar to ‘TheServiceControl’, except that this utility can just be started as a binary without compilation.

TheService可以向外部服务发出HTTP请求。 我们可以配置它以将请求重定向到记录请求的本地实用程序。 对于此实用程序,还有一个用于启动和停止它的包装程序,类似于“ TheServiceControl”,不同之处在于,该实用程序可以作为二进制文件启动而无需编译。

其他亮点 (Additional highlights)

The Go application was written so that all the logs and debugging information would be sent to STDOUT. On production, this output is sent to a file. When launching from RSpec the log is displayed in the console, which really helps with debugging.

编写Go应用程序是为了将所有日志和调试信息发送到STDOUT。 在生产中,此输出将发送到文件。 从RSpec启动时,日志将显示在控制台中,这确实有助于调试。

If you specifically run the specs that don’t need TheService, then it won’t start.

如果您专门运行不需要TheService的规范,则它将不会启动。

In order not to waste time on launching TheService each time whenever a spec changes, during the development process you can launch TheService manually in the terminal and simply not turn it off. Whenever it’s necessary, you can even launch it in an IDE debugging mode. Then the specs prepare everything, send the request to the service, it stops and you can easily debug it. This makes the TDD approach really convenient.

为了避免每次更改规范时都浪费时间启动TheService,在开发过程中,您可以在终端中手动启动TheService,而不必关闭它。 必要时,您甚至可以在IDE调试模式下启动它。 然后,规范准备了一切,将请求发送到服务,它停止了,您可以轻松调试它。 这使得TDD方法非常方便。

结论 (Conclusion)

We’ve been using this setup for about a year now and haven’t experienced any failures with it. The specs come out far more readable than unit testing in Go, and they don’t rely on knowing the internal structure of the service. If we, for some reason, need to rewrite the service in another language, then we won’t need to change the specs. Only the wrappers, which are used for launching the test service with a different command would need to be rewritten.

我们已经使用此设置大约一年了,并且没有遇到任何失败。 规范的发布远比Go中的单元测试更具可读性,并且它们不依赖于了解服务的内部结构。 如果由于某种原因我们需要用另一种语言重写服务,那么我们就不需要更改规格。 只有用于使用其他命令启动测试服务的包装程序才需要重写。

翻译自: https://www.freecodecamp.org/news/how-to-black-box-test-a-go-app-with-rspec-421e786f4103/

rspec 测试页面元素

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值