단위 테스트에서 널 매개 변수로 오브젝트를 구성 할 수 있습니까?


37

현재 프로젝트에 대한 단위 테스트를 작성하기 시작했습니다. 그래도 경험이 없습니다. 먼저 "완료"하고 싶기 때문에 현재 IoC 프레임 워크 나 조롱 라이브러리를 사용하지 않습니다.

단위 테스트에서 객체의 생성자에 null 인수를 제공하는 데 문제가 있는지 궁금합니다. 몇 가지 예제 코드를 제공하겠습니다.

public class CarRadio
{...}


public class Motor
{
    public void SetSpeed(float speed){...}
}


public class Car
{
    public Car(CarRadio carRadio, Motor motor){...}
}


public class SpeedLimit
{
    public bool IsViolatedBy(Car car){...}
}

또 다른 Car Code Example (TM)은 질문에 중요한 부분으로 축소되었습니다. 나는 이제 다음과 같은 테스트를 작성했다.

public class SpeedLimitTest
{
    public void TestSpeedLimit()
    {
        Motor motor = new Motor();
        motor.SetSpeed(10f);
        Car car = new Car(null, motor);

        SpeedLimit speedLimit = new SpeedLimit();
        Assert.IsTrue(speedLimit.IsViolatedBy(car));
    }
}

시험은 잘 진행됩니다. SpeedLimit필요 CarA를을 Motor그 일을하기 위해. 전혀 관심이 없기 CarRadio때문에 null을 제공했습니다.

완전히 구성되지 않고 올바른 기능 제공하는 객체 가 SRP 또는 코드 냄새를 위반 하는지 궁금합니다 . 나는이 잔소리가 있지만 speedLimit.IsViolatedBy(motor)기분이 좋지 않습니다. 속도 제한은 모터가 아닌 자동차에 의해 위반됩니다. 전체 의도전체 의 일부만 테스트 하기 때문에 단위 테스트와 작업 코드에 대해 다른 관점이 필요할 수도 있습니다.

단위 테스트에서 null로 객체를 생성하는 것이 코드 냄새입니까?


24
약간의 주제는 아니지만 Motor아마도 전혀 없어야 speed합니다. 전류 와를 기반으로 throttle하고를 계산 해야합니다 . 그것을 사용하여 현재 속도로 통합하고 그것을 다시 신호 로 바꾸는 것이 자동차의 일입니다 ... 그러나 어쨌든 당신은 현실주의를 위해 있지 않았습니까? torquerpmthrottleTransmissionrpmMotor
cmaster

17
코드 냄새는 생성자가 처음에 불평하지 않고 null을 취한다는 것입니다. 이것이 수용 가능하다고 생각되면 (이유가있을 수 있습니다) 테스트 해보십시오!
Tommy

4
널 오브젝트 패턴을 고려하십시오 . 종종 코드를 단순화하고 NPE 안전을 제공합니다.
Bakuriu

17
축하합니다 : null라디오로 속도 제한이 올바르게 계산되었는지 테스트했습니다 . 지금 당신은 제한 속도를 확인하기위한 테스트 생성 할 수 있습니다 라디오를; 행동이 다른 경우에 대비하여 ...
Matthieu M.

3
이 예에 대해서는 잘 모르지만 때로는 반만 시험하고 싶다는 사실은 무언가가 깨져야한다는 단서입니다.
Owen

답변:


70

위 예의 경우 a Car가없이 존재할 수 있는 것이 합리적입니다 CarRadio. 어떤 경우에는 CarRadio때로는 null을 전달하는 것이 허용 될뿐만 아니라 의무적이라고 말합니다. 테스트는 Car클래스 의 구현 이 견고하고없는 경우 널 포인터 예외를 발생시키지 않는지 확인해야합니다 CarRadio.

그러나 다른 예를 들어 봅시다 SteeringWheel. a Car가 있어야 한다고 가정 SteeringWheel하지만 속도 테스트는 실제로 신경 쓰지 않습니다. 이 경우 null SteeringWheel을 전달하지 않으므로 Car클래스를 의도하지 않은 곳으로 클래스를 푸시합니다 . 이 경우, DefaultSteeringWheel은유를 계속하기 위해 일직선으로 잠긴 종류를 만드는 것이 좋습니다 .


44
그리고 ... Car없이 SteeringWheel
c를

13
뭔가 빠졌을 수도 있지만 을 사용 하지 않고 생성하는 것이 가능하고 일반적인 경우 전달할 필요가 없으며 명시적인 null에 대해 전혀 걱정할 필요가없는 생성자를 만드는 것이 이치에 맞지 않습니다. ? CarCarRadio
집게 벌레

16
자가 운전 차량에는 핸들이 없을 수 있습니다. 그냥 말하면 ☺
BlackJack

1
@James_Parsons 나는 내부에서만 사용되었고 항상 null이 아닌 매개 변수를 받았기 때문에 null 검사가없는 클래스에 관한 것을 잊었습니다. 다른 방법은 Carnull과 함께 NRE를 throw CarRadio했지만 관련 코드 SpeedLimit는 그렇지 않습니다. 지금 게시 된 질문의 경우 정확하고 실제로 그렇게 할 것입니다.
R. Schmitz

2
@mtraceur 모두가 널 인수로 구성을 허용하는 클래스가 널을 의미한다고 가정하는 방식은 null 옵션이 유효하다는 것입니다. 내가 실제로 가질 수있는 문제는 내가 망치고 싶지 않고 나를 도왔던 좋은, 흥미롭고 잘 작성된 많은 답변으로 덮여 있습니다. 또한 미완성 상태로 두는 것을 좋아하지 않기 때문에이 답변을 수락하십시오.
R. Schmitz

23

단위 테스트에서 null로 객체를 생성하는 것이 코드 냄새입니까?

예. 코드 냄새 의 C2 정의에 유의하십시오 . "CodeSmell은 확실하지 않은 무언가가있을 수 있음을 암시합니다."

시험은 잘 진행됩니다. SpeedLimit는 그 일을하기 위해 모터가 장착 된 자동차가 필요합니다. CarRadio에 전혀 관심이 없기 때문에 null을 제공했습니다.

생성자에게 전달 speedLimit.IsViolatedBy(car)CarRadio인수에 의존하지 않는다는 것을 증명하려는 경우 , 호출이있을 경우 테스트에 실패한 테스트 double을 도입하여 해당 제한 조건을 명시 적으로 작성하는 것이 향후 유지 보수 담당자에게 더 명확 할 것입니다.

더 가능성이 높기 CarRadio때문에이 경우에 사용되지 않는 것을 만드는 작업을 스스로 저장하려고하는 경우

  • 이것은 캡슐화를 위반하는 것입니다 ( CarRadio테스트에 누출되지 않은 사실을 알려 줍니다)
  • Car지정하지 않고 생성하려는 경우를 하나 이상 발견했습니다 .CarRadio

코드가 다음과 같은 생성자를 원한다고 말하고 있다고 의심합니다.

public Car(IMotor motor) {...}

생성자 는 아무것도 없다는 사실로 무엇을 할 것인지 결정할 수 있습니다.CarRadio

public Car(IMotor motor) {
    this(null, motor);
}

또는

public Car(IMotor motor) {
    this( new ICarRadio() {...} , motor);
}

또는

public Car(IMotor motor) {
    this( new DefaultRadio(), motor);
}

기타

SpeedLimit을 테스트하는 동안 null을 다른 것으로 전달하는 경우입니다. 테스트가 "SpeedLimitTest"라고하며 Assert만이 SpeedLimit의 방법을 확인하고 있음을 알 수 있습니다.

SpeedLimit를 테스트하기 위해서는 SpeedLimit 검사가 신경 쓰는 유일한 것이 속도 일지라도 라디오 주위에 차 전체를 만들어야합니다 .

이것은 자동차 전체를 요구하지 않고 자동차가 구현SpeedLimit.isViolatedBy 하는 역할 인터페이스 를 수용해야하는 힌트 일 수 있습니다 .

interface IHaveSpeed {
    float speed();
}

class Car implements IHaveSpeed {...}

IHaveSpeed정말 대단한 이름입니다. 도메인 언어가 향상되기를 바랍니다.

해당 인터페이스를 사용하면 테스트 SpeedLimit가 훨씬 간단 해집니다.

public void TestSpeedLimit()
{
    IHaveSpeed somethingTravelingQuickly = new IHaveSpeed {
        float speed() { return 10f; }
    }

    SpeedLimit speedLimit = new SpeedLimit();
    Assert.IsTrue(speedLimit.IsViolatedBy(somethingTravelingQuickly));
}

마지막 예제에서 IHaveSpeed인터페이스 를 구현하는 모의 클래스를 작성해야한다고 가정합니다 (적어도 c #에서는 인터페이스 자체를 인스턴스화 할 수 없음).
Ryan Searle

1
맞습니다. 효과적인 철자는 테스트에 사용하는 Blub의 맛에 따라 다릅니다.
VoiceOfUnreason

18

단위 테스트에서 null로 객체를 생성하는 것이 코드 냄새입니까?

아니

전혀. 반대로, NULL인수로 사용할 수있는 값이있는 경우 (일부 언어에는 널 포인터 전달을 허용하지 않는 구문이있을 수 있음) 이를 테스트 해야 합니다.

또 다른 질문은 당신이 통과하면 어떻게되는지 NULL입니다. 그러나 이것이 바로 단위 테스트에 관한 것입니다. 가능한 모든 입력에 대해 정의 된 결과가 발생 하는지 확인하십시오 . 그리고 NULL 것입니다 당신이 정의 된 결과가 있어야하고 해당에 대한 테스트를해야하므로, 잠재적 입력.

따라서 라디오없이 자동차를 조립할있어야 한다면 테스트를 작성 해야 합니다. 예외 가 아니며 예외를 throw 해야하는 경우 테스트를 작성 해야 합니다.

어느 쪽이든, 당신 은에 대한 테스트를 작성 해야 합니다 NULL. 당신이 그것을 떠나면 코드 냄새가 될 것입니다. 즉, 방금 테스트 한 테스트되지 않은 코드 분기가 마법처럼 버그가 없다고 가정합니다.


테스트중인 코드를 개선 할 수있는 다른 답변에 동의합니다. 그럼에도 불구하고 개선 된 코드에 포인터 인 매개 변수가있는 NULL경우 개선 된 코드에 대해서도 전달 하는 것이 좋습니다. NULL모터로 통과 할 때 발생하는 상황을 보여주는 테스트를 원합니다 . 코드 냄새가 아닙니다. 코드 냄새와 반대입니다. 테스트 중입니다.


한판 승부 테스트를 작성하는 것 같습니다 Car. 잘못된 진술로 간주되는 내용은 보이지 않지만 테스트는 문제입니다 SpeedLimit.
R. Schmitz

@ R.Schmitz 무슨 뜻인지 잘 모르겠습니다. 질문을 인용했습니다 . NULL의 생성자 에 전달 하는 것입니다 Car.
nvoigt

1
테스트하는 동안SpeedLimit null을 다른 것으로 전달하는 경우 입니다. 테스트는 "SpeedLimitTest"라고하며 유일한 Assert방법은의 방법을 확인하는 것입니다 SpeedLimit. 테스트 Car-예를 들어 "무선없이 자동차를 구성 할 수 있어야하는 경우 테스트를 작성해야합니다"는 다른 테스트에서 발생합니다.
R. Schmitz

7

나는 개인적으로 null을 주입하는 것을 주저 할 것입니다. 사실, 의존성을 생성자에 주입 할 때마다 가장 먼저 주입 중 하나가 ArgumentNullException을 발생시키는 것입니다. 그렇게하면 수십 개의 null 검사가 확산되지 않고 클래스 내에서 안전하게 사용할 수 있습니다. 다음은 매우 일반적인 패턴입니다. 나중에 생성자에 설정된 종속성을 널 (또는 다른 것)로 설정할 수 없도록하기 위해 읽기 전용을 사용하십시오.

class Car
{
   private readonly ICarRadio _carRadio;
   private readonly IMotor _motor;

   public Car(ICarRadio carRadio, IMotor motor)
   {
      if (carRadio == null)
         throw new ArgumentNullException(nameof(carRadio));

      if (motor == null)
         throw new ArgumentNullException(nameof(motor));

      _carRadio = carRadio;
      _motor = motor;
   }
}

위와 같이, null 검사가 예상대로 작동하는지 확인하기 위해 null을 주입하는 단위 테스트 또는 2 개를 가질 수 있습니다.

말했듯이 두 가지 일을하면 이것이 문제가되지 않습니다.

  1. 클래스 대신 인터페이스로 프로그램하십시오.
  2. 모의 프레임 워크 (예 : Moq for C #)를 사용하여 널 대신 모의 종속성을 주입하십시오.

따라서 귀하의 예를 들면 다음과 같습니다.

interface ICarRadio
{
   bool Tune(float frequency);
}

interface Motor
{
   bool SetSpeed(float speed);
}

...
var car = new Car(new Moq<ICarRadio>().Object, new Moq<IMotor>().Object);

Moq 사용에 대한 자세한 내용은 여기를 참조하십시오 .

물론, 먼저 조롱하지 않고 이해하고 싶다면 인터페이스를 사용하는 한 여전히 이해할 수 있습니다. 다음과 같이 더미 CarRadio 및 Motor를 작성하고 대신 주입하십시오.

class DummyCarRadio : ICarRadio
{
   ...
}
class DummyMotor : IMotor
{
   ...
}

...
var car = new Car(new DummyCarRadio(), new DummyMotor())

3
이것은 내가 쓴 것이 답이다
Liath

1
나는 심지어 더미 객체도 계약을 이행해야한다고 말하고 싶습니다. 경우 IMotor했다 getSpeed()방법을의이 DummyMotor성공적으로 이후 수정 된 속도를 반환해야합니다 setSpeed뿐만 아니라.
ComFreek

1

레거시 코드를 테스트 하네스로 가져 오는 잘 알려진 종속성 깨기 기술입니다. (Michael Feathers의“레거시 코드로 효과적으로 작업하기”를 참조하십시오.)

냄새가나요? 잘...

시험은 냄새가 나지 않습니다. 시험은 일을하고있다. "이 개체는 null 일 수 있으며 테스트중인 코드는 여전히 올바르게 작동합니다"라고 선언합니다.

냄새가 구현 중입니다. 생성자 인수를 선언하면 "이 클래스 제대로 작동하려면 이 클래스가 필요 합니다 "라고 선언 합니다. 분명히, 그것은 사실이 아닙니다. 사실이 아닌 것은 약간 냄새가납니다.


1

첫 번째 원칙으로 돌아가 봅시다.

단위 테스트는 단일 클래스의 일부 측면을 테스트합니다.

그러나 다른 클래스를 매개 변수로 사용하는 생성자 또는 메서드가 있습니다. 이를 처리하는 방법은 상황에 따라 다르지만 목표와 접하기 때문에 설정이 최소화되어야합니다. 라디오가없는 차량과 같이 NULL 매개 변수를 전달하는 것이 완벽하게 유효한 시나리오가있을 수 있습니다.

나는 우리가 (차 테마를 계속하는)로 시작하는 레이싱 라인을 테스트하는 것으로, 여기 겠지,하지만 당신은 할 수있다 또한 어떤 방어 코드와 예외 나눠 메커니즘으로 작동하는지 확인하기 위해 다른 매개 변수에 NULL을 전달하려면 필요합니다.

여태까지는 그런대로 잘됐다. 그러나 NULL 유효한 값 이 아닌 경우 글쎄, 여기 모의, 스터브, 인형 및 가짜 의 어두운 세계에 있습니다.

이 모든 것이 조금 더 걸릴 것으로 보인다면 실제로 사용되지 않을 값이므로 Car 생성자에서 NULL을 전달하여 이미 더미를 사용하고 있습니다.

꽤 빨리 분명 해져야 할 것은 많은 NULL 핸들링으로 코드를 잠재적으로 생성하는 것입니다. 클래스 매개 변수를 인터페이스로 바꾸면 조롱 및 DI 등을 통해 훨씬 쉽게 처리 할 수 ​​있습니다.


1
"단위 테스트는 단일 클래스의 코드를 테스트하기위한 것입니다." 한숨 . 그것은 단위 테스트가 아니며 그 지침을 따르는 것이 당신을 부풀린 수업으로 만들거나 반 생산적이고 방해하는 테스트로 이끌 것입니다.
Ant P

3
"단위"를 "클래스"의 완곡 어로 취급하는 것은 불행히도 매우 일반적인 사고 방식이지만 실제로는이 모든 것이 (a) "쓰레기 수거, 쓰레기 수거"검사를 장려합니다. 스위트 및 (b) 자유롭게 변경해야하는 경계를 강화하여 생산성을 저해 할 수 있습니다. 더 많은 사람들이 그들의 고통을 듣고이 선입견에 도전하기를 바랍니다.
Ant P

3
그러한 혼란은 없습니다. 코드 단위가 아닌 동작 단위를 테스트하십시오. 소프트웨어 테스트의 광범위한화물 컬트 하이브를 제외하고는 단위 테스트의 개념에 대해서는 아무 것도 없습니다. 이는 테스트 단위가 클래스의 OOP 개념과 분명히 관련되어 있음을 의미합니다. 그리고 매우 해로운 오해이기도합니다.
Ant P

1
정확히 무슨 뜻인지 잘 모르겠습니다. 당신이 의미하는 것과 정반대입니다. 단단한 경계를 스터브 / 모의하고 (필요한 경우) 행동 단위를 테스트하십시오 . 공개 API를 통해. API가 무엇인지는 빌드하는 내용에 따라 다르지만 풋 프린트는 최소화되어야합니다. 추상화, 다형성 또는 구조 변경 코드를 추가하면 테스트가 실패하지 않아야합니다. "클래스 당 단위"는이 규칙과 모순되며 테스트의 가치를 완전히 손상시킵니다.
Ant P

1
RobbieDee-나는 정반대에 대해 이야기하고 있습니다. 당신이 일반적인 오해의 대상이 될 수 있다고 생각하고, "단위"로 간주하는 것을 다시 방문하고 Kent Beck이 TDD에 관해 이야기했을 때 실제로 실제로 무엇에 대해 이야기했는지 조금 더 깊이 파고들 것입니다. 대부분의 사람들이 현재 TDD라고 부르는 것은화물 컬트 괴물입니다. youtube.com/watch?v=EZ05e7EMOLM
Ant P

1

귀하의 디자인을 살펴보면 a Car가 일련의로 구성되어 있다고 제안합니다 Features. 특징은 어쩌면 CarRadio, 또는 EntertainmentSystem이는 같은 것들로 구성 될 수있다 CarRadio, DvdPlayer

따라서 최소한 Car집합 을 사용하여을 구성해야합니다 Features. EntertainmentSystem그리고 CarRadio인터페이스 (Java, C # 등)의 구현자가 될 것이며 public [I|i]nterface Feature이것은 Composite 디자인 패턴의 인스턴스가 될 것입니다.

따라서 항상 Carwith Features를 구성하고 단위 테스트를 사용하면 a Car없이 인스턴스를 생성 하지 않습니다 Features. BasicTestCarFeatureSet가장 기본적인 테스트에 필요한 최소한의 기능 세트가있는 Mocking을 고려할 수도 있습니다 .

몇 가지 생각.

남자

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.