加快你的测试

By Gene Wu / @gene_wu

测试代码

  • 迅速
  • 简单
  • 辅助

你们测试跑完要多久?

  • 10分钟?
  • 30分钟?
  • 1小时?
  • >1小时

如果测试很慢你会?

  • 😪
  • 😄
  • 😭

工具

  • Unix
  • Ruby

Unix工具

  • time
  • strace

time

显示总体运行时间,以秒计时。
User Time   : 程序自身消耗时间
System Time : 系统调用时间
Real Time   : 总时间
CPU Time    : 大于(fork)或者小于(wait) Real Time
							
								
TIMEFORMAT=$'real\t%E\tuser\t%U\tsys\t%S\tcpu\t%P'
# For Bash, Doesn't work with ZSH
								
							

strace

跟踪系统调用和信号
							
						  	
strace -e trace=read,write -o /tmp/strace_io.log $CMD
cat /tmp/strace_io.log | grep read | \
 awk 'BEGIN {FS="="}{ sum += $2} END {print sum}'
  							
  						

Ruby工具

  • Bundler
  • perftools.rb or ruby-prof
  • Ruby Debugger

bundler viz [--requirements]

分析Bundler的依赖关系
							

perftools.rb

gperftools for ruby code
* gproftools : Fast, multi-threaded malloc() and nifty performance analysis tools
							
						  	
strace -e trace=read,write -o /tmp/strace_io.log $CMD
cat /tmp/strace_io.log | grep read | \
 awk 'BEGIN {FS="="}{ sum += $2} END {print sum}'
  							
  						

perftools 1,2,3

1. Gem

gem 'perftools.rb', require: 'perftools'
							

2. 跑Ruby

CPUPROFILE=/tmp/prof CPUPROFILE_REALTIME=1 time rspec
							

3. 生成报表

pprof.rb --text /tmp/prof
							

Ruby Debugger

分析Prof日志

							
DEMO
							
						

调整GC策略

题外话 GC
						

关于GC

GC参数

							
{
  RUBY_HEAP_MIN_SLOTS             =>
   '初始堆大小,默认10000,越大需要占用的内存越多'
  RUBY_HEAP_FREE_MIN              => 
  'GC后可用的heap slot的最小值,默认4096,如果太小,就会按照下面2个参数分配新栈'
  RUBY_HEAP_SLOTS_INCREMENT       => 
  '当Ruby需要开辟一片新的堆栈所需的数,默认是10000'
  RUBY_HEAP_SLOTS_GROWTH_FACTOR   => 
  '当ruby需要新的堆栈的时候, 此参数做为一个乘数被用来计算这片新的堆栈的大小'
  RUBY_GC_MALLOC_LIMIT            => 
  '允许不触发GC而分配的C数据结构的最大值,默认8000000 byte,设置的太低就会触发垃圾回收'
}
							
						

37signals’s GC参数

							
{
export RUBY_HEAP_MIN_SLOTS=600000
export RUBY_HEAP_SLOTS_INCREMENT=10000
export RUBY_HEAP_SLOTS_GROWTH_FACTOR=1.8
export RUBY_GC_MALLOC_LIMIT=59000000
export RUBY_HEAP_FREE_MIN=100000
}
							
						

twitter's GC参数

							
{
export RUBY_HEAP_MIN_SLOTS=500000
export RUBY_HEAP_SLOTS_INCREMENT=250000
export RUBY_HEAP_SLOTS_GROWTH_FACTOR=1
export RUBY_GC_MALLOC_LIMIT=50000000
export RUBY_HEAP_FREE_MIN=4096
}
							
						

Default GC参数

							
{
export RUBY_HEAP_MIN_SLOTS=10000
export RUBY_HEAP_SLOTS_INCREMENT=10000
export RUBY_HEAP_SLOTS_GROWTH_FACTOR=1.8
export RUBY_GC_MALLOC_LIMIT=8000000
export RUBY_HEAP_FREE_MIN=4096
}
							
						

Test设置一些技巧

找到最慢的


rspec -p
	Top 10 slowest examples (3.24 seconds, 42.6% of total time):
  User pages index pagination should list each user
    1.6 seconds ./spec/requests/user_pages_spec.rb:24
  User pages signup with invalid information should not create a user
    0.40022 seconds ./spec/requests/user_pages_spec.rb:134
....

						

基于Rspec的GC调优

							
RSpec.configure do |config|
  # Quick hack to see what our peak number of objects on the heap is
  heap_live_num = 0
  config.after(:each) do
    heap_live_num = [heap_live_num, GC.stat[:heap_live_num]].max
  end
  config.after(:suite) do
    puts "\nMAX HEAP OBJECT COUNT: #{heap_live_num}"
  end
end
							
						

并行测试 1

							
TEST_SHARDS={
  :functionals => [
    'test/functional/**/[a-e]*_test.rb',
    'test/functional/**/[f-n]*_test.rb',
    'test/functional/**/[^a-n]*_test.rb'
  ],
  :integration => [
    'test/integration/**/[a-f]*_test.rb',
    'test/integration/**/[^a-f]*_test.rb'
  ],
  :units => [
    'test/unit/**/[a-i]*_test.rb',
    'test/unit/**/[^a-i]*_test.rb'
  ]
}
							
						

并行测试 2

							
TEST_SHARDS.keys.each do |suite|
    namespace suite do
      TEST_SHARDS[suite].each_with_index do |shard_pattern,i|
        shardname = "shard#{i + 1}".to_sym
        Rake::TestTask.new(shardname) do |t|
          t.libs << "test"
          t.pattern = shard_pattern
          t.verbose = true
        end
        Rake::Task["test:#{suite.to_s}:#{shardname.to_s}"].tap do |t|
          t.comment = "Run the #{suite.to_s} tests in #{shard_pattern}"
          prereqs = ['db:test:prepare']
          prereqs += ['ci:setup:testunit'] if ENV.include?('ENABLE_REPORTING')
          t.enhance(prereqs)
        end
      end
    end
  end
							
						

Thank You

BY Gene Wu (Twitter:gene_wu)