在测试永无止境的流程时,如何从测试中停止流程?
How to stop a process from within the tests, when testing a never-ending process?
我正在 Ruby 中开发一个长期 运行 的程序。我正在为此编写一些集成测试。这些测试需要在启动后杀死或停止程序;否则测试挂起。
例如,使用文件 bin/runner
#!/usr/bin/env ruby
while true do
puts "Hello World"
sleep 10
end
(集成)测试将是:
class RunReflectorTest < TestCase
test "it prints a welcome message over and over" do
out, err = capture_subprocess_io do
system "bin/runner"
end
assert_empty err
assert_includes out, "Hello World"
end
end
只是,显然,这行不通;测试开始并且永远不会停止,因为 system
调用永远不会结束。
我该如何解决这个问题?是system
本身的问题,Kernel#spawn
会提供解决方案吗?如果是这样,如何?以下不知何故使 out
为空:
class RunReflectorTest < TestCase
test "it prints a welcome message over and over" do
out, err = capture_subprocess_io do
pid = spawn "bin/runner"
sleep 2
Process.kill pid
end
assert_empty err
assert_includes out, "Hello World"
end
end
。这个方向似乎也会导致很多时间问题(和缓慢的测试)。理想情况下,reader 将跟随 STDOUT 流,并在遇到字符串时让测试通过,然后立即终止子进程。我找不到如何使用 Process
.
执行此操作
测试行为,而不是语言功能
首先,您正在做的是 TDD 反模式。测试应该关注方法或对象的行为,而不是像循环这样的语言特性。如果您必须测试循环,请构建一个测试来检查有用的 行为 ,例如 "entering an invalid response results in a re-prompt." 检查循环是否永远循环几乎没有用处。
但是,您可能决定通过查看以下内容来测试一个长 运行ning 进程:
- 如果 t 时间后仍然 运行ning。
- 如果至少执行 i 次迭代。
- 如果循环在给定特定输入或达到边界条件时正确退出。
使用超时或信号结束测试
其次,如果你决定这样做,你可以用 Timeout::timeout 来逃避封锁。例如:
require 'timeout'
# Terminates block
Timeout::timeout(3) { `sleep 300` }
这既快速又简单。但是,请注意,使用超时实际上并不表示进程。如果你 运行 这几次,你会注意到 sleep 仍然作为系统进程 运行ning 多次。
最好是在您想要退出时用 Process::kill 向进程发出信号,确保您自己进行清理。例如:
pid = spawn 'sleep 300'
Process::kill 'TERM', pid
sleep 3
Process::wait pid
除了资源问题之外,当您生成有状态的东西并且不想破坏测试的独立性时,这是一种更好的方法。只要有可能,你应该几乎总是在你的测试拆解中杀死长运行宁(或无限)进程。
Ideally, a reader would follow the stream of STDOUT and let the test pass as soon as the string is encountered and then immediately kill the subprocess. I cannot find how to do this with Process.
您可以通过指定 out
选项
将派生进程的标准输出重定向到任何文件描述符
pid = spawn(command, :out=>"/dev/null") # write mode
Example of redirection
根据关于如何重定向 Process::spawn
IO 的 on how to use Timeout::timeout
and ,我想出了两个 Minitest 助手,可以按如下方式使用:
it "runs a deamon" do
wait_for(timeout: 2) do
wait_for_spawned_io(regexp: /Hello World/, command: ["bin/runner"])
end
end
帮手是:
def wait_for(timeout: 1, &block)
Timeout::timeout(timeout) do
yield block
end
rescue Timeout::Error
flunk "Test did not pass within #{timeout} seconds"
end
def wait_for_spawned_io(regexp: //, command: [])
buffer = ""
begin
read_pipe, write_pipe = IO.pipe
pid = Process.spawn(command.shelljoin, out: write_pipe, err: write_pipe)
loop do
buffer << read_pipe.readpartial(1000)
break if regexp =~ buffer
end
ensure
read_pipe.close
write_pipe.close
Process.kill("INT", pid)
end
buffer
end
这些可以用于允许我启动子进程的测试,捕获 STDOUT,一旦它与测试正则表达式匹配,它就会通过,否则它将等待直到超时并失败(测试失败).
loop
将捕获输出并在看到匹配的输出后通过测试。它使用 IO.pipe
因为这是子进程(及其子进程)要写入的最多 t运行spa运行t。
我怀疑这是否适用于 Windows。它需要对 wait_for_spawned_io
进行一些清理,这在 IMO 中做的有点太多了。另一个问题是 Process.kill('INT')
可能无法到达孤儿,但在这个测试有 运行 之后仍然 运行。我需要找到一种方法来确保进程的整个子树都被杀死。
我正在 Ruby 中开发一个长期 运行 的程序。我正在为此编写一些集成测试。这些测试需要在启动后杀死或停止程序;否则测试挂起。
例如,使用文件 bin/runner
#!/usr/bin/env ruby
while true do
puts "Hello World"
sleep 10
end
(集成)测试将是:
class RunReflectorTest < TestCase
test "it prints a welcome message over and over" do
out, err = capture_subprocess_io do
system "bin/runner"
end
assert_empty err
assert_includes out, "Hello World"
end
end
只是,显然,这行不通;测试开始并且永远不会停止,因为 system
调用永远不会结束。
我该如何解决这个问题?是system
本身的问题,Kernel#spawn
会提供解决方案吗?如果是这样,如何?以下不知何故使 out
为空:
class RunReflectorTest < TestCase
test "it prints a welcome message over and over" do
out, err = capture_subprocess_io do
pid = spawn "bin/runner"
sleep 2
Process.kill pid
end
assert_empty err
assert_includes out, "Hello World"
end
end
。这个方向似乎也会导致很多时间问题(和缓慢的测试)。理想情况下,reader 将跟随 STDOUT 流,并在遇到字符串时让测试通过,然后立即终止子进程。我找不到如何使用 Process
.
测试行为,而不是语言功能
首先,您正在做的是 TDD 反模式。测试应该关注方法或对象的行为,而不是像循环这样的语言特性。如果您必须测试循环,请构建一个测试来检查有用的 行为 ,例如 "entering an invalid response results in a re-prompt." 检查循环是否永远循环几乎没有用处。
但是,您可能决定通过查看以下内容来测试一个长 运行ning 进程:
- 如果 t 时间后仍然 运行ning。
- 如果至少执行 i 次迭代。
- 如果循环在给定特定输入或达到边界条件时正确退出。
使用超时或信号结束测试
其次,如果你决定这样做,你可以用 Timeout::timeout 来逃避封锁。例如:
require 'timeout'
# Terminates block
Timeout::timeout(3) { `sleep 300` }
这既快速又简单。但是,请注意,使用超时实际上并不表示进程。如果你 运行 这几次,你会注意到 sleep 仍然作为系统进程 运行ning 多次。
最好是在您想要退出时用 Process::kill 向进程发出信号,确保您自己进行清理。例如:
pid = spawn 'sleep 300'
Process::kill 'TERM', pid
sleep 3
Process::wait pid
除了资源问题之外,当您生成有状态的东西并且不想破坏测试的独立性时,这是一种更好的方法。只要有可能,你应该几乎总是在你的测试拆解中杀死长运行宁(或无限)进程。
Ideally, a reader would follow the stream of STDOUT and let the test pass as soon as the string is encountered and then immediately kill the subprocess. I cannot find how to do this with Process.
您可以通过指定 out
选项
pid = spawn(command, :out=>"/dev/null") # write mode
Example of redirection
根据关于如何重定向 Process::spawn
IO 的 Timeout::timeout
and
it "runs a deamon" do
wait_for(timeout: 2) do
wait_for_spawned_io(regexp: /Hello World/, command: ["bin/runner"])
end
end
帮手是:
def wait_for(timeout: 1, &block)
Timeout::timeout(timeout) do
yield block
end
rescue Timeout::Error
flunk "Test did not pass within #{timeout} seconds"
end
def wait_for_spawned_io(regexp: //, command: [])
buffer = ""
begin
read_pipe, write_pipe = IO.pipe
pid = Process.spawn(command.shelljoin, out: write_pipe, err: write_pipe)
loop do
buffer << read_pipe.readpartial(1000)
break if regexp =~ buffer
end
ensure
read_pipe.close
write_pipe.close
Process.kill("INT", pid)
end
buffer
end
这些可以用于允许我启动子进程的测试,捕获 STDOUT,一旦它与测试正则表达式匹配,它就会通过,否则它将等待直到超时并失败(测试失败).
loop
将捕获输出并在看到匹配的输出后通过测试。它使用 IO.pipe
因为这是子进程(及其子进程)要写入的最多 t运行spa运行t。
我怀疑这是否适用于 Windows。它需要对 wait_for_spawned_io
进行一些清理,这在 IMO 中做的有点太多了。另一个问题是 Process.kill('INT')
可能无法到达孤儿,但在这个测试有 运行 之后仍然 运行。我需要找到一种方法来确保进程的整个子树都被杀死。