레일스는 테스트를 모른다. 레일스는 테스트를 할때 Rails Dependency와 DB Dependency를 끊지 않고 테스트 환경을 구성한다. 이로 인해 그들간의 의존성이 복잡해 질 경우에는 테스트 1개를 실행하는데도 1~2분의 시간이 소요된다. 이렇게 느린 테스트 환경에서는 테스트의 모든 장점이 붕괴된다.
그렇다면 레일스는 테스트환경을 어떻게 만들어줄까? 다음 그림을 살펴보자.
- 레일스 application.rb를 읽는다. 여기서는 Gem Dependency에 따라 시간이 좌우된다. 보통 젬 100개 당 10초 정도의 시간이 소요된다.
- 레일스 environement.rb를 읽는다. 각종 환경설정을 로드한다.
- DB Schema를 test database에 로드한다.
- test_helper.rb를 읽는다. 여기서 environment.rb와 application.rb가 중복되어 한번 더 로드된다.
이로 인해 Gem Dependency나 DB Schema가 복잡해 질 경우 한번 테스트하는데 오랜 시간이 걸린다. (위와 같은 경우 1분 15초가 걸린다.)
이러한 상황을 막는 방법은 테스팅 환경에서 레일스와 DB 의존을 제거하는 것이다. 필자는 다음과 같은 순서로 레일스와 DB 의존을 제거했다.
-
ActiveSupport::TestCase를 상속하지 말고 Test::Unit::TestCase를 직접 상속하자. 예)
를 아래와 같이 변경한다.
- test_helper.rb에 정의되어 있는 ActiveSupport::TestCase는 이제 필요없다. 지우자. ``` class ActiveSupport::TestCase # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. # # Note: You'll currently still have to declare fixtures explicitly in integration tests # -- they do not yet inherit this setting fixtures :all
# Add more helper methods to be used by all tests here... end
3. test_helper.rb에 남아있는 다른 레일스 의존 코드를 모두 지우자.
ENV["RAILS_ENV"] = "test" require File.expand_path('../../config/environment', FILE) require 'rails/test_help'
4. 이젠 test_helper.rb가 텅 비었을 것이다. 이제 직접 app/models를 ruby LOAD_PATH에 추가하자.
여기에 대한 부분은 [http://slipp.net/questions/235를](http://slipp.net/questions/235를) 참고하자.
$LOAD_PATH.unshift File.expand_path('app/models')
5. 유닛테스트를 위한 젬들을 require하자.
require 'test-unit' require 'mocha/setup'
6. 액티브레코드를 직접 require하자.
require 'active_record'
7. DB연결을 아예 끊어버리면 더 좋지만, ActiveRecord 패턴에서 그러기엔 너무 복잡하니 테스트 할때만 메모리에 디비를 올리는 훼이크를 쓰자.
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:" load 'db/schema.rb'
8. 다음과 같은 test_helper.rb가 완성될 것이다.
$LOAD_PATH.unshift File.expand_path('app/models') require 'test-unit' require 'mocha/setup' require 'active_record'
ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:" load 'db/schema.rb'
9. 이제 테스트 파일에서는 test_helper.rb와 모델 파일 1개를 require해주면 된다.
require 'test_helper' require 'my_model'
class MyTest < Test::Unit::TestCase end ```
- 테스트 코드를 돌려보자. 기존에 1~2분 걸렸던 것이 2초 내외로 끝나는 상황을 볼 수 있다.
굉장히 두서없이 정리된 듯한 느낌이다.. 다른 레일스 개발자 분들은 어떤 방식으로 테스트 속도를 빠르게 유지하는지 궁금하다.
9개의 의견 from SLiPP
좋은 정보 공유 고맙다. 깔끔하게 정리잘 했네. 네가 활동하고 있는 루비 그룹으로 전송하면 더 많은 개발자들의 의견을 받을 수 있다.
ActiveRecord(또는 ORM)를 쓰는 곳에서 테스트할 때 테스트 성능을 높이기 sqlite, h2와 같은 메모리 DB를 사용하는 경우가 있다. 나름 현재 상태에서 최선의 선택을 했다고 본다.
의문에 대한 도전과 그에 대한 결과가 나왔군요. 잘 모르는 영역이라서 조언은 어렵겠지만 전진하는 모습이 멋집니다.~!!
spork나 spring, zeus, 이것 저것... 같은 걸 쓰면 이러지 않고도, test를 빨리 돌릴 수 있습니다.
db:schema:load 에 45초나 걸리는 건 다른 문제가 있어보이네요. 그리고 production 환경에서도 sqlite를 사용할 게 아니라면 test db도 서비스 환경과 동일한 db를 사용하는게 좋습니다.
참고 : http://blog.codeship.io/2013/08/21/faster-test-suite-boot-times-with-ruby-on-rails.html
실무에서는 TestUnit 거의 사용하지 않습니다. RSpec 위주로 생각해 보세요. Guard 를 이용해서 파일 변경을 추적하여 해당 테스트를 자동으로 실행시킬 수 있습니다. Spork 을 이용해서 RSpec 을 Server 모드로 실행 할 수 있습니다.
참고 : http://ruby.railstutorial.org/chapters/static-pages#sec-guard
3.6.2 부터 3.6.3 까지 읽어 보시면 어떤 방법인지 대충 감이 오실 듯 합니다.
프레임워크를 사용할 때, 해당 프레임워크의 라이프사이클을 건드리는 것은 위험합니다. 버전 변화에 따라 대응을 못하는 상황들이 벌어지기 때문입니다. 가능하면, 기존에 실무에서 어떻게 사용하고 있는지 리서치 하는 부분도 같이 진행하셨으면 좋겠습니다.
몇일 됐지만, Ruby On Rails 4.1.0 Beta 1 이 나왔습니다.
참고 : http://weblog.rubyonrails.org/2013/12/18/Rails-4-1-beta1/
4.1 부터는 Spring application preloader (Java의 그 Spring 아닙니다.) 가 추가되어, 매번 전부 다시 읽지 않고 진행 할 수 있게 되었습니다.
참고 : https://github.com/jonleighton/spring/blob/master/README.md
로그인 하는 사이에 보니 다른 분이 좋은 댓글 달아 주셨네요. 간단한 경우에는 fixture 사용법을 익히시는 것도 도움이 되실 겁니다.
@kim.jaewoo spring이나 zeus도 고려해봤는데요. 이게 재부팅 해야하는 경우도 있는지라 그때는 1~2분을 기다려야 하더라고요. 또 CI 서버에서 테스팅 돌리는 경우에는 spring이나 zeus는 무용지물인거 같더라고요 :)
잔소리 더 할까 했는데, 케니가 다 해줬네요.
근데, 실무에서 rspec말고 그냥 Test::Unit 쓰는 곳도 많이 있습니다.
@Kenny 참고자료 감사합니다. 어디선가 Rspec이 느리기로 악명높다고 들은거 같은데 아닌가요? 또 레일스의 관례가 유닛테스트시에 DB를 이용하는건가요?
엇, 테스트 db를 메모리 DB로 바꿔서 올린게 패턴의 일종이었군요! 이런 즐거움이?!
양파 패턴! http://jeffreypalermo.com/blog/the-onion-architecture-part-1/
육면체 패턴! http://alistair.cockburn.us/Hexagonal+architecture
@dongkuk 지난 번에 네가 메모리 db로 바꿔서 테스트했다고 했을 때 내가 이야기했다. 패턴으로까지 있는 줄은 몰랐는데 테스트 시간을 줄이기 위해 메모리 db를 활용하는 경우가 있다고.. 자바 진영의 경우 ORM을 쓰는 곳에서 시도한 곳이 있는 것으로 아는데 rails는 이런 방식으로 테스트하는 줄 모르겠네.
의견을 남기기 위해서는 SLiPP 계정이 필요합니다.
안심하세요! 회원가입/로그인 후에도 작성하시던 내용은 안전하게 보존됩니다.
SLiPP 계정으로 로그인하세요.
또는, SNS 계정으로 로그인하세요.