새 프로젝트를 시작했으며 코드가 타사 서비스에 의존해야 할 때입니다. ElasticSearch, Resque, 청구 공급자 또는 임의의 HTTP API와 같은 것일 수 있습니다. 당신은 훌륭한 개발자이므로 이 코드가 잘 테스트되기를 원합니다. 하지만 완전히 제어할 수 없는 서비스에 대한 요청을 발생시키는 코드를 어떻게 테스트합니까?
테스트를 건너뛸 수 있지만 곧 불안정한 기반에 더 많은 코드를 쌓게 될 것입니다. 테스트되지 않은 코드는 더 복잡한 코드를 끌어들이는 경향이 있으며, 안전하다고 느끼는 데 필요한 테스트 범위가 없기 때문에 결국 코드가 너무 위험한 리팩터링이라고 느낄 것입니다. 미래의 작업을 위한 안정적인 기반을 구축하고 싶지만 관리할 수 없는 엉망이 되었습니다.
이 상황을 피하는 것은 보기보다 훨씬 쉽습니다! 몇 가지 도구와 약간의 사전 노력으로 코드가 의존하는 서비스에서 테스트를 분리하고, 더 간단한 코드를 작성하고, 버그를 도입하지 않고도 작성한 코드를 개선할 자신감을 가질 수 있습니다. 다음 테스트를 작성하는 방법을 모르기 때문에 미루는 대신 코드와 외부 세계 간의 상호 작용을 살펴보고 그 중간에 바로 뛰어드는 방법을 정확히 알 수 있습니다.
모카:빠른 접근 방식
Mocha는 코드와 외부 세계 사이를 오가는 가장 쉬운 방법입니다.
예를 들어 Cart
가 있다고 가정해 보겠습니다. 체크아웃될 때 신용카드 청구를 유발하는 객체. 청구에 실패할 경우 장바구니에 오류 메시지가 첨부되어 있는지 확인하려고 합니다.
테스트가 실제로 적중되는 것을 원하지 않을 것입니다. 테스트가 실행될 때마다 청구 시스템. 그렇게 하는 경우에도 해당 서비스가 실패를 반환하도록 하는 것은 어려울 수 있습니다. Mocha를 사용하면 다음과 같습니다.
def test_error_message_set_on_charge_failure
cart = Cart.new(items)
cart.stubs(:charge!).returns(false) # mocha in action
cart.checkout!
assert_equal "The credit card could not be charged", cart.credit_card_error
end
Mocha는 메서드가 예상대로 호출되지 않으면 테스트에 실패할 수도 있습니다.
def test_only_bill_once_per_cart
cart = Cart.new(items)
cart.expects(:charge!).once # Don't double-bill, no matter how many times we check out
cart.checkout!
cart.checkout!
end
모카는 사용하기 쉽지만 매우 편리할 수 있습니다. 단지 조롱하고 있다는 점을 주의해야 합니다. 원하지 않는 행동 - 너무 많이 조롱하고 실제 버그를 숨기기 쉽습니다. expects
로 가득 찬 테스트:이 접근 방식을 지나치게 사용하고 싶지는 않습니다. 및 stubs
읽고 생각하기 어렵습니다.
가짜 테스트:내가 선호하는 접근 방식
동일한 객체에 대해 항상 같은 메소드를 조롱하거나 스텁하면 모의 객체를 본격적인 객체로 승격시킬 수 있습니다(때때로 가짜 테스트라고 함). ), 다음과 같이:
def test_billed_full_amount_minus_discount
test_payment_provider = TestPaymentProvider.new # A fake payment provider
cart = Cart.new(items, discount: 30, provider: test_payment_provider)
cart.checkout!
assert_equal items.sum(:&price) * 0.7, test_payment_provider.total_charges
end
가짜가 좋습니다:
-
가짜가 내부 상태를 추적할 수 있음
가짜에는
total_charges
와 같이 테스트를 더 쉽게 작성할 수 있도록 해주는 사용자 지정 어설션 메시지와 도우미 기능이 있을 수 있습니다. 위의 예에서 방법 -
완전한 개체로서 추가 편집기 및 언어 지원을 받습니다.
이를 지원하는 편집기를 사용하는 경우 자동 완성, 인라인 문서 및 Mocha로 개별 메서드를 스텁아웃하여 얻을 수 없는 기타 항목을 얻을 수 있습니다. 또한 더 나은 유효성 검사, 예외 처리 및 가짜에 구축하려는 모든 것을 얻을 수 있습니다.
-
개발 모드에서 가짜를 사용하면 실제 서비스에 연결할 필요가 없습니다.
버스에서 앱을 작성할 수 있고, 랩톱 배터리를 소모하는 서비스의 숲을 가질 필요가 없으며, 많은 시간을 필요로 하지 않고 엣지 케이스를 통해 작업하는 데 필요한 데이터를 반환하도록 이러한 가짜 서비스를 설정할 수 있습니다. 설정.
-
이러한 개체는 테스트 외부에서 사용할 수 있습니다.
이것은 아마도 내가 가장 좋아하는 가짜 부분일 것입니다. 메모리 내 배열로 뒷받침되는 loggingclient가 타사 서비스와 가짜 서비스에 모두 기록되도록 할 수 있습니다. 이 배열의 콘텐츠를 사이트의 관리자 보기에 덤프할 수 있습니다. , 로깅하고 있다고 생각하는 것을 로깅하고 있는지 훨씬 쉽게 확인할 수 있습니다.
다음과 같이 할 수 있습니다.
fake_backend = FakeBackend.new
LoggingService.backends = [RealBackend.new, fake_backend]
LoggingService.debug("TEST MESSAGE PLEASE IGNORE")
fake_backend.messages.first # => [:debug, "TEST MESSAGE PLEASE IGNORE"]
가짜를 작성하는 것은 개별 방법을 스터빙하는 것보다 더 많은 노력이 필요하지만 연습을 통해 도움이 되는 가짜를 만드는 데 한두 시간 이상 걸리지 않아야 합니다. 다른 사람들에게 유용할 수 있는 것을 구축했다면 공유하세요! 오래전에 resque-unit을 만들었고 지금도 많은 사람들이 사용하고 있습니다.
이러한 개체를 어떻게 주입합니까?
어떻게든 이러한 가짜와 대화하려면 테스트 중인 개체를 가져와야 합니다. 다행히 Ruby는 남용하기 쉽기 때문에 가짜를 주입하는 것은 어렵지 않습니다.
테스트 중인 개체의 API를 제어하는 경우 가짜를 설정할 수 있는 기본 매개변수, 속성 또는 생성자 옵션을 추가하는 것이 가장 좋습니다.
class Card
attr_reader :provider
def initialize(items, options={})
@provider = options.fetch(:provider) { RealProvider.new }
end
end
이것은 실제 서비스와 대화할 때 깨끗하고 나중에 유연성을 추가할 수 있는 연결을 제공합니다.
개체를 제어하지 않거나 추가 매개변수를 추가하지 않으려면 항상 원숭이 패치를 수행할 수 있습니다.
# if in test mode
Card.class_eval do
def provider
@provider ||= TestProvider.new
end
end
테스트에서는 더 못생겼지만 가짜를 사용하지 않는 환경에서는 더 깨끗합니다.
지금 바로 자신만의 가짜 만들기 시작
가짜 만들기는 연습을 통해 더 쉬워지므로 지금 시도해 보세요.
- 외부 서비스와 통신하는 테스트를 찾습니다. 인터넷 연결이 끊어지면 실패할 테스트입니다.
- 어떤 개체가 실제로 통신을 수행하고 코드가 해당 개체에 대해 호출하는 내용을 파악하세요.
- 객체 클래스의 대부분이 비어 있는 복제본을 만들고 배열에 대한 호출을 기록하도록 합니다.
- 통화 목록을 반환하는 방법을 가짜에 추가합니다.
- 실제 개체를 새 가짜 개체로 바꾸고 코드 호출에 대해 몇 가지 주장을 작성합니다.
시도해보시면 어떻게 되는지 알려주세요!
이러한 기술을 사용하면 애플리케이션과 외부 세계 간의 가장 미친 상호 작용을 길들일 수 있을 때까지 오래 걸리지 않습니다. 올바른 위치에 있는 간단한 스텁을 사용하면 잘 테스트된 코드를 자신 있게 제공할 수 있습니다.