새로운 언어가 처음부터 TDD하기 쉽도록 설계된 경우 어떻게됩니까?


9

가장 일반적인 일부 언어 (Java, C #, Java 등)를 사용하면 코드를 완전히 TDD하려는 경우 언어와 충돌하는 경우가 있습니다.

예를 들어 Java 및 C #에서는 클래스의 모든 종속성을 조롱하고 대부분의 조롱 프레임 워크는 클래스가 아닌 인터페이스를 조롱하는 것이 좋습니다. 이것은 종종 단일 구현으로 많은 인터페이스를 가지고 있음을 의미합니다 (이 효과는 TDD가 더 많은 수의 더 작은 클래스를 작성하도록 강제하기 때문에 더욱 두드러집니다). 구체적인 클래스를 조롱 할 수있는 솔루션은 컴파일러를 변경하거나 클래스 로더를 재정의하는 등의 작업을 수행하는 데 매우 불쾌합니다.

언어가 처음부터 TDD에 적합하도록 설계 되었다면 어떤 모습일까요? 인터페이스를 생성자에게 전달하지 않고 종속성을 설명하고 명시 적으로 클래스 인터페이스를 분리하지 않는 언어 수준의 방법 일 수 있습니까?


TDD가 필요없는 언어는 어떻습니까? blog.8thlight.com/uncle-bob/2011/10/20/Simple-Hickey.html
Job

2
언어 TDD 가 필요 하지 않습니다 . TDD는 유용한 관행 이며, Hickey의 요점 중 하나는 테스트를한다고해서 생각을 멈출 수 있다는 의미는 아닙니다 .
Frank Shearar

테스트 주도 개발은 내부 및 외부 API를 올바르게 사용하는 것입니다. 따라서 Java 에서는 인터페이스에 관한 모든 것 입니다 . 실제 클래스는 부산물입니다.

답변:


6

몇 년 전에 저는 비슷한 질문을하는 프로토 타입을 함께 던졌습니다. 다음은 스크린 샷입니다.

제로 버튼 테스트

아이디어는 어설 션이 코드 자체와 일치하고 모든 테스트는 기본적으로 각 키 스트로크에서 실행된다는 것입니다. 테스트를 통과하자마자 메서드가 녹색으로 바뀝니다.


2
하하, 놀라워요! 나는 실제로 코드와 함께 테스트를하는 아이디어를 좋아한다. 단위 테스트를 위해 병렬 네임 스페이스가있는 별도의 어셈블리가있는 .NET에서는 상당히 지루합니다 (아주 좋은 이유가 있지만). 또한 코드를 이동하고 테스트를 자동으로 이동하기 때문에 리팩토링이 더 쉬워집니다.
Geoff

그러나 거기에 시험을 남기고 싶습니까? 프로덕션 코드를 사용하도록 설정 하시겠습니까? 아마도 C의 경우 # ifdef'd 일 수 있습니다. 그렇지 않으면 코드 크기 / 런타임 적중을보고 있습니다.
Mawg는 Monica를

순전히 프로토 타입입니다. 그것이 현실이 되려면 성능과 크기와 같은 것들을 고려해야 할 것입니다. 그러나 그것에 대해 걱정하는 것은 너무나 초기 일이며, 우리가 그 시점에 도달하면 빠져 나가는 것을 선택하는 것이 어렵지 않을 것입니다 또는 원하는 경우 어설 션을 컴파일 된 코드에서 제외합니다. 관심을 가져 주셔서 감사합니다.
Carl Manaster

5

그것은 것 동적 보다는 정적으로 입력 된보다. 그러면 덕 타이핑 은 인터페이스가 정적으로 유형이 지정된 언어와 동일한 작업을 수행합니다. 또한 테스트 클래스가 기존 클래스에서 메소드를 쉽게 스텁하거나 모방 할 수 있도록 런타임에 클래스를 수정할 수 있습니다. 루비는 그러한 언어 중 하나입니다. rspec 은 TDD를위한 최고의 테스트 프레임 워크입니다.

동적 타이핑이 테스트를 돕는 방법

동적 입력을 사용하면 조롱해야하는 공동 작업자 객체와 동일한 인터페이스 (메소드 서명)가있는 클래스를 간단히 만들어 모의 객체를 만들 수 있습니다. 예를 들어, 메시지를 보낸 클래스가 있다고 가정하십시오.

class MessageSender
  def send
    # Do something with a side effect
  end
end

MessageSender 인스턴스를 사용하는 MessageSenderUser가 있다고 가정 해 보겠습니다.

class MessageSenderUser

  def initialize(message_sender)
    @message_sender = message_sender
  end

  def do_stuff
    ...
    @message_sender.send
    ...
    @message_sender.send
    ...
  end

end

단위 테스트의 필수 요소 인 종속성 주입 의 사용에 주목하십시오 . 우리는 다시 돌아올 것입니다.

MessageSenderUser#do_stuff통화가 두 번 전송되는지 테스트하려고 합니다. 정적으로 유형이 지정된 언어와 마찬가지로 send호출 횟수를 계산하는 모의 MessageSender를 만들 수 있습니다 . 그러나 정적으로 유형이 지정된 언어와 달리 인터페이스 클래스는 필요하지 않습니다. 계속 진행하여 작성하십시오.

class MockMessageSender

  attr_accessor :send_count

  def initialize
    @send_count = 0
  end

  def send
    @send_count += 1
  end

end

그리고 테스트에서 사용하십시오 :

mock_sender = MockMessageSender.new
MessageSenderUser.new(mock_sender).do_stuff
assert_equal(mock_sender.send_count, 2)

동적으로 유형이 지정된 언어의 "오리진 입력"자체는 정적 유형의 언어와 비교할 때 테스트에 큰 도움이되지 않습니다. 그러나 클래스가 닫히지 않았지만 런타임에 수정할 수 있다면 어떨까요? 이것이 게임 체인저입니다. 방법을 보자.

클래스를 테스트 할 수 있도록 종속성 주입을 사용할 필요가 없으면 어떻게합니까?

MessageSenderUser가 MessageSender를 사용하여 메시지를 보내는 경우에만 MessageSender를 다른 클래스로 대체 할 필요가 없다고 가정하십시오. 단일 프로그램 내에서 종종 그렇습니다. 의존성 주입없이 MessageSender를 작성하고 사용하도록 MessageSenderUser를 다시 작성해 봅시다.

class MessageSenderUser

  def initialize
    @message_sender = MessageSender.new
  end

  def do_stuff
    ...
    @message_sender.send
    ...
    @message_sender.send
    ...
  end

end

MessageSenderUser는 이제 사용하기가 더 간단합니다. 아무도 작성하기 위해 MessageSender를 작성할 필요가 없습니다. 이 간단한 예제에서는 크게 개선 된 것처럼 보이지 않지만 이제 MessageSenderUser가 두 번 이상 생성되었거나 세 개의 종속성이 있다고 상상해보십시오. 이제 시스템은 디자인을 전혀 개선하지 않기 때문에 단위 테스트를 만족시키기 위해 인스턴스를 전달합니다.

의존성 주입없이 테스트 할 수있는 개방형 클래스

다이나믹 한 타이핑과 오픈 클래스를 가진 언어의 테스트 프레임 워크는 TDD를 상당히 좋게 만들 수 있습니다. MessageSenderUser에 대한 rspec 테스트의 코드 스 니펫은 다음과 같습니다.

mock_message_sender = mock MessageSender
MessageSender.should_receive(:new).and_return(mock_message_sender)
mock_message_sender.should_receive(:send).twice.with(no_arguments)
MessageSenderUser.new.do_stuff

그것은 전체 테스트입니다. 만약MessageSenderUser#do_stuff 호출하지 않습니다 MessageSender#send정확히 두 번이 테스트가 실패합니다. 실제 MessageSender 클래스는 결코 호출되지 않습니다. 우리는 누군가 누군가 MessageSender를 만들 때마다 모의 MessageSender를 가져와야한다고 테스트에 말했습니다. 의존성 주입이 필요하지 않습니다.

더 간단한 테스트에서 그렇게하는 것이 좋습니다. 디자인에 실제로 의미가 없다면 의존성 주입을 사용하지 않는 것이 훨씬 좋습니다.

그러나 이것은 공개 수업과 어떤 관련이 있습니까? 에 대한 호출에 유의하십시오 MessageSender.should_receive. MessageSender를 작성할 때 #should_receive를 정의하지 않았습니다. 대답은 시스템 클래스를 신중하게 수정 한 테스트 프레임 워크가 모든 객체에 #should_receive가 정의 된 것처럼 보일 수 있다는 것입니다. 이와 같은 시스템 클래스를 수정하는 데 약간의주의가 필요하다고 생각하면 옳습니다. 그러나 테스트 라이브러리에서 수행하는 작업에 가장 적합하며 열린 클래스를 통해 가능합니다.


좋은 답변입니다! 너희들은 역동적 인 언어로 나를 다시 말하기 시작했다 :) 오리 타이핑이 여기에 핵심이라고 생각한다.
Geoff

3

언어가 처음부터 TDD에 적합하도록 설계 되었다면 어떤 모습일까요?

'TDD와 잘 작동합니다'는 언어를 설명하기에 충분하지 않으므로 다른 것처럼 보일 수 있습니다. Lisp, Prolog, C ++, Ruby, Python ... 선택하십시오.

또한 TDD 지원이 언어 자체에서 가장 잘 처리되는 것이 확실하지 않습니다. 물론 모든 함수 나 메소드에 관련 테스트가있는 언어를 만들 수 있으며 해당 테스트를 검색하고 실행하기위한 지원을 구축 할 수 있습니다. 그러나 단위 테스트 프레임 워크는 이미 검색 및 실행 부분을 잘 처리하므로 모든 기능에 대한 테스트 요구 사항을 깔끔하게 추가하는 방법을 찾기가 어렵습니다. 테스트에도 테스트가 필요합니까? 아니면 테스트가 필요한 일반 함수와 필요하지 않은 테스트 함수의 두 가지 클래스가 있습니까? 그다지 우아하지 않은 것 같습니다.

도구와 프레임 워크로 TDD를 지원하는 것이 좋습니다. IDE에 빌드하십시오. 그것을 장려하는 개발 프로세스를 만듭니다.

또한 언어를 디자인하는 경우 장기적으로 생각하는 것이 좋습니다. TDD는 하나의 방법론 일 뿐이며 모든 사람이 선호하는 작업 방식은 아닙니다. 상상하기 어려울 수 있지만 더 나은 방법이 올 수 있습니다. 언어 디자이너로서 사람들이 당신의 언어를 포기해야합니까?

질문에 대답하기 위해 실제로 말할 수있는 것은 그러한 언어가 테스트에 도움이된다는 것입니다. 나는 그것이 그다지 도움이되지 않는다는 것을 알고 있지만 문제는 질문에 있다고 생각합니다.


동의한다는 것은 잘 표현하기가 매우 어려운 질문입니다. 내 말은 Java / C #과 같은 언어에 대한 현재 테스트 도구가 언어가 약간 길어지고 있다고 느끼고 일부 추가 / 대체 언어 기능이 전체 경험을보다 우아하게 만들 것이라고 생각합니다 (즉, 90에 대한 인터페이스가 없음) 내 수업의 %, 더 높은 수준의 디자인 관점에서 의미가있는 수업).
Geoff

0

동적 타입 언어에는 명시적인 인터페이스가 필요하지 않습니다. 루비 또는 PHP 등을 참조하십시오.

반면에 Java 및 C # 또는 C ++와 같이 정적으로 형식이 지정된 언어는 형식을 적용하고 해당 인터페이스를 작성하도록합니다.

내가 이해하지 못하는 것은 그들에 대한 문제입니다. 인터페이스는 설계의 핵심 요소이며 SOLID 원리를 고려하여 설계 패턴 전체에 사용됩니다. 예를 들어 PHP에서 인터페이스를 자주 사용하는 이유는 디자인을 명시 적으로 만들고 디자인을 강제하기 때문입니다. 반면 루비에서는 형식을 적용 할 방법이 없으며 오리 형식 언어입니다. 그러나 여전히 인터페이스를 상상해야하며 올바르게 구현하려면 디자인을 추상화해야합니다.

따라서 귀하의 질문이 흥미로울 지 모르지만 의존성 주입 기술을 이해하거나 적용하는 데 문제가 있음을 의미합니다.

루비와 PHP는 단위 테스트 프레임 워크에 내장되어 있으며 별도로 제공되는 훌륭한 모의 인프라를 가지고 있습니다 (PHP 용 Mockery 참조). 경우에 따라 이러한 프레임 워크를 사용하면 종속성을 명시 적으로 주입하지 않고 정적 호출을 조롱하거나 객체를 초기화하는 것과 같은 제안을 수행 할 수도 있습니다.


1
인터페이스가 훌륭하고 핵심 디자인 요소라는 데 동의합니다. 그러나 내 코드에서 90 %의 클래스에 인터페이스가 있으며 해당 인터페이스의 클래스와 모의 클래스의 두 가지 구현 만 있음을 알았습니다. 이것은 기술적으로 인터페이스의 요점이지만, 우아하지 않다는 느낌을 줄 수는 없습니다.
Geoff

Java 및 C #에서의 조롱에 익숙하지는 않지만 모의 객체는 실제 객체를 모방합니다. 나는 종종 객체 유형의 매개 변수를 사용하고 대신 모의 객체를 메소드 / 클래스에 보내 의존성 주입을 수행합니다. 함수 someName (AnotherClass $ object = null)과 같은 것 {$ this-> anotherObject = $ object? : 새로운 AnotherClass; } 인터페이스에서 파생하지 않고 종속성을 주입하는 데 자주 사용되는 트릭입니다.
Patkos Csaba

1
이것은 동적 언어가 내 질문과 관련하여 Java / C # 유형 언어보다 유리한 곳입니다. 구체적인 클래스의 전형적인 모의는 실제로 클래스의 서브 클래스를 생성 할 것입니다. 이는 구체적 클래스 생성자가 호출 될 것임을 의미합니다. 다이내믹 모의는 오리 타이핑을 활용하기 때문에 구체적인 클래스와 모의 사이에는 아무런 관계가 없습니다. 나는 파이썬으로 코드를 작성하는 데 많이 사용되었지만, TDD 시절이 오기 전에, 아마도 다시 한번 살펴볼 시간입니다!
Geoff
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.