내가 수업을 가지고 있다고 가정 해 봅시다 (생각 된 예와 나쁜 디자인을 용서하십시오).
class MyProfit
{
public decimal GetNewYorkRevenue();
public decimal GetNewYorkExpenses();
public decimal GetNewYorkProfit();
public decimal GetMiamiRevenue();
public decimal GetMiamiExpenses();
public decimal GetMiamiProfit();
public bool BothCitiesProfitable();
}
(GetxxxRevenue () 및 GetxxxExpenses () 메소드에는 종속 된 종속성이 있음을 참고하십시오)
이제 GetNewYorkProfit () 및 GetMiamiProfit ()에 따라 BothCitiesProfitable ()을 단위 테스트하고 있습니다. GetNewYorkProfit () 및 GetMiamiProfit ()을 스텁해도 괜찮습니까?
그렇지 않으면 BothCitiesProfitable ()과 함께 GetNewYorkProfit () 및 GetMiamiProfit ()을 동시에 테스트하는 것 같습니다. GetxxxProfit () 메서드가 올바른 값을 반환하도록 GetxxxRevenue () 및 GetxxxExpenses ()에 대한 스터 빙을 설정해야합니다.
지금까지는 내부 메소드가 아닌 외부 클래스에 대한 의존성을 스터 빙하는 예제 만 보았습니다.
그리고 그것이 괜찮다면, 이것을하기 위해 사용해야하는 특정 패턴이 있습니까?
최신 정보
나는 우리가 핵심 문제를 잃어 버렸을지도 모른다고 우려하고 있으며 이는 아마도 저의 나쁜 예의 잘못 일 것입니다. 근본적인 질문은 클래스의 메소드가 동일한 클래스에서 공개적으로 노출 된 다른 메소드에 종속되어 있다면 다른 메소드를 스텁 아웃하는 것이 괜찮거나 권장 되는가?입니다.
어쩌면 나는 뭔가를 놓치고 있지만 클래스를 나누는 것이 항상 의미가 있는지 확신 할 수 없습니다. 아마도 가장 좋은 또 다른 예는 다음과 같습니다.
class Person
{
public string FirstName()
public string LastName()
public string FullName()
}
여기서 전체 이름은 다음과 같이 정의됩니다.
public string FullName()
{
return FirstName() + " " + LastName();
}
FullName ()을 테스트 할 때 FirstName ()과 LastName ()을 스텁해도 괜찮습니까?
답변
문제의 수업을 해체해야합니다.
각 수업은 간단한 작업을 수행해야합니다. 작업이 테스트하기에 너무 복잡하면 클래스가 수행하는 작업이 너무 큽니다.
이 디자인의 구피를 무시합니다.
class NewYork
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class Miami
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class MyProfit
{
MyProfit(NewYork new_york, Miami miami);
boolean bothProfitable();
}
최신 정보
클래스에서 스텁 메소드의 문제점은 캡슐화를 위반한다는 것입니다. 테스트는 객체의 외부 동작이 사양과 일치하는지 확인해야합니다. 개체 내부에서 일어나는 일은 비즈니스의 대상이 아닙니다.
FullName이 FirstName과 LastName을 사용한다는 사실은 구현 세부 사항입니다. 수업 이외의 어떤 것도 그것이 사실임을 신경 쓰지 않아야합니다. 객체를 테스트하기 위해 퍼블릭 메소드를 조롱함으로써 해당 객체에 대한 가정을하고 있습니다.
미래의 어느 시점에서 그 가정은 정확하지 않을 수 있습니다. 아마도 모든 이름 논리는 Person이 단순히 호출하는 Name 객체로 재배치 될 것입니다. FullName은 FirstName 및 LastName을 호출하는 대신 멤버 변수 first_name 및 last_name에 직접 액세스합니다.
두 번째 질문은 왜 그렇게해야하는지 느끼는 것입니다. 모든 개인 수업은 다음과 같이 테스트 될 수 있습니다.
Person person = new Person("John", "Doe");
Test.AssertEquals(person.FullName(), "John Doe");
이 예제에 대한 스텁이 필요하다고 생각해서는 안됩니다. 당신이 그렇게하면 당신은 행복하고 잘 … 중지하세요! 어쨌든 메소드를 제어 할 수 있기 때문에 메소드를 조롱하면 이점이 없습니다.
FullName이 조롱하기 위해 사용하는 방법에 적합한 것으로 보이는 유일한 경우는 FirstName ()과 LastName ()이 사소한 작업이 아닌 경우입니다. 어쩌면 당신은 임의의 이름 생성기 중 하나를 작성하거나 FirstName 및 LastName이 데이터베이스에 답을 쿼리합니다. 그러나 그것이 일어나고 있다면 객체가 Person 클래스에 속하지 않는 것을하고 있다고 제안합니다.
다른 방법으로, 방법을 조롱하면 객체를 가져 와서 두 조각으로 나눕니다. 한 조각은 조롱되고 다른 조각은 테스트되고 있습니다. 당신이하고있는 일은 본질적으로 객체를 임시로 분해하는 것입니다. 이 경우 이미 개체를 분리하십시오.
수업이 단순하다면, 시험하는 동안 수업을 조롱 할 필요가 없다고 생각해야합니다. 수업이 복잡해서 조롱 할 필요가 있다고 생각되면 수업을 더 간단한 조각으로 나누어야합니다.
다시 업데이트
내가 보는 방식으로, 개체에는 외부 및 내부 동작이 있습니다. 외부 행동에는 다른 객체 등에 대한 값 호출이 포함됩니다. 분명히 해당 범주의 모든 항목을 테스트해야합니다. (그렇지 않으면 무엇을 테스트 하시겠습니까?) 그러나 내부 동작을 실제로 테스트해서는 안됩니다.
이제 내부 동작이 외부 동작의 결과이기 때문에 내부 동작이 테스트됩니다. 그러나 나는 내부 행동에 대한 테스트를 직접 작성하지 않고 외부 행동을 통해 간접적으로 만 작성합니다.
무언가를 테스트하고 싶다면 외부 행동이되도록 움직여야한다고 생각합니다. 그래서 당신이 무언가를 조롱하고 싶다면, 당신이 조롱하려는 것이 이제 문제의 객체의 외부 행동에 있도록 객체를 분리해야한다고 생각합니다.
그러나 어떤 차이가 있습니까? FirstName ()과 LastName ()이 다른 개체의 구성원이면 FullName ()의 문제가 실제로 변경됩니까? FirstName을 조롱하는 것이 필요하다고 결정하고 LastName은 실제로 다른 객체에있는 데 도움이됩니까?
당신이 당신의 조롱 접근법을 사용한다면, 당신은 객체에 이음새를 만듭니다. 외부 데이터 소스와 직접 통신하는 FirstName () 및 LastName ()과 같은 함수가 있습니다. 또한 FullName ()이 없습니다. 그러나 그들은 모두 같은 클래스에 있기 때문에 분명하지 않습니다. 일부 조각은 데이터 소스에 직접 액세스하지 않아야하고 다른 조각은 직접 액세스하지 않아야합니다. 두 그룹을 분리하면 코드가 명확 해집니다.
편집하다
한 걸음 물러서서 물어 보자. 왜 테스트 할 때 객체를 조롱 하는가?
- 테스트를 일관되게 실행합니다 (실행마다 변경되는 항목에 액세스하지 마십시오)
- 고가의 자원에 접근하지 마십시오
- 테스트중인 시스템 단순화
- 가능한 모든 시나리오 (예 : 실패 시뮬레이션 등)를 쉽게 테스트 할 수 있습니다.
- 다른 코드 조각의 세부 사항에 따라 다른 코드 조각을 변경해도이 테스트가 중단되지 않도록하십시오.
이제 1-4 이유가이 시나리오에 적용되지 않는다고 생각합니다. 전체 이름을 테스트 할 때 외부 소스를 조롱하면 조롱에 대한 모든 이유가 해결됩니다. 처리되지 않은 유일한 부분은 단순하지만 객체가 충분히 단순 해 걱정할 필요가없는 것 같습니다.
귀하의 우려는 사유 번호 5라고 생각합니다. 향후 어느 시점에서 FirstName 및 LastName의 구현을 변경하면 테스트가 중단 될 것입니다. 나중에 FirstName과 LastName은 다른 위치 나 소스에서 이름을 가져올 수 있습니다. 그러나 FullName은 아마도 항상입니다 FirstName() + " " + LastName()
. 그렇기 때문에 FirstName과 LastName을 조롱하여 FullName을 테스트하려고합니다.
당신이 가지고있는 것은 개인 객체의 일부 하위 집합이며 다른 하위 개체보다 변경 될 가능성이 더 큽니다. 나머지 오브젝트는이 서브 세트를 사용합니다. 해당 하위 집합은 현재 하나의 소스를 사용하여 데이터를 가져 오지만 나중에 완전히 다른 방식으로 해당 데이터를 가져올 수 있습니다. 그러나 나에게 그것은 그 하위 집합이 나 가려고하는 독특한 물건처럼 들립니다.
객체의 방법을 조롱하면 객체가 분할되는 것 같습니다. 그러나 당신은 임시 방식으로 그렇게하고 있습니다. 귀하의 코드는 Person 객체 내부에 두 개의 별개의 조각이 있음을 명확하게 나타내지 않습니다. 따라서 객체를 실제 코드로 분할하면 진행중인 코드를 읽을 수 없습니다. 의미있는 대상의 실제 분할을 선택하고 각 테스트마다 대상을 다르게 분할하지 마십시오.
나는 당신이 당신의 물건을 나누는 것에 반대 할 수 있다고 생각하지만 왜 그럴까요?
편집하다
내가 틀렸어.
개별 방법을 조롱하여 임시 분할을 도입하는 대신 객체를 분할해야합니다. 그러나 객체를 분리하는 한 가지 방법에 지나치게 집중했습니다. 그러나 OO는 객체를 분할하는 여러 가지 방법을 제공합니다.
내가 제안하는 것 :
class PersonBase
{
abstract sring FirstName();
abstract string LastName();
string FullName()
{
return FirstName() + " " + LastName();
}
}
class Person extends PersonBase
{
string FirstName();
string LastName();
}
class FakePerson extends PersonBase
{
void setFirstName(string);
void setLastName(string);
string getFirstName();
string getLastName();
}
어쩌면 그것은 당신이 함께하고있는 일입니다. 그러나 각 방법이 어느쪽에 있는지 명확하게 묘사했기 때문에이 방법에 조롱 방법으로 볼 때 문제가 있다고 생각하지 않습니다. 상속을 사용하면 추가 래퍼 객체를 사용했을 때 발생할 수있는 어색함을 피할 수 있습니다.
이것은 약간의 복잡성을 초래하며, 몇 가지 유틸리티 기능에 대해서만 기본 타사 소스를 조롱하여 테스트 할 것입니다. 물론, 그들은 깨질 위험이 높지만 재정렬 할 가치가 없습니다. 당신이 그것을 분리해야 할만 큼 복잡한 물체를 가지고 있다면, 나는 이것과 같은 것이 좋은 생각이라고 생각합니다.
답변
Winston Ewert의 답변에 동의하지만 시간 제약으로 인해 API가 이미 다른 곳에서 사용되는지 또는 무엇을 가지고 있는지에 따라 클래스를 분리하는 것이 불가능할 수도 있습니다.
그런 경우에는 모의 메소드 대신 클래스를 점증 적으로 테스트하기 위해 테스트를 작성했습니다. 테스트 getExpenses()
및 getRevenue()
다음 테스트 방법을 getProfit()
, 방법을 다음 공유 방법을 테스트합니다. 특정 방법을 다루는 테스트가 두 개 이상인 것은 사실이지만, 방법을 개별적으로 다루기 위해 통과 테스트를 작성 했으므로 테스트 된 입력을 통해 출력이 신뢰할 수 있는지 확인할 수 있습니다.
답변
더 귀하의 예를 단순화하기 위해, 당신에게있는 거 테스트 말 C()
에 따라, A()
그리고 B()
자신의 종속성이 각각을,. IMO는 당신의 시험이 성취하려고하는 것에 달려 있습니다.
당신의 행동을 테스트하는 경우 C()
주어진 알려진 행동을 A()
하고 B()
그것은 아마도 가장 쉽고 가장 밖으로 스텁하는 것입니다 A()
및 B()
. 이것은 아마도 순수 주의자들에 의해 단위 테스트라고 불릴 것입니다.
당신이 (에서 전체 시스템의 동작을 테스트하는 경우 C()
의 관점), 나는 떠날 것이다 A()
과 B()
같이 구현 및 종속성 밖으로 중 하나 스텁 또는 테스트로, 샌드 박스 환경을 설정 (그것이 시험을 가능하게하는 경우) 데이터 베이스. 이것을 통합 테스트라고 부릅니다.
두 가지 방법 모두 유효 할 수 있으므로 사전에 테스트 할 수 있도록 설계된 경우 장기적으로 더 나을 수 있습니다.