2011-02-01 9 views
9

이 문제를 해결하는 방법을 궁금합니다. 나는 nhibernate를 사용하고 유창하다.단위 테스트 및 nhibernate?

나는이

public class User 
{ 
    public virtual int UserId {get; private set;} 
} 

같은 도메인 클래스이 그것이 자동 생성으로 설정하고 ID 사람들을 중지로 NHibernate에 수행 할 때 규칙 것 같다 있습니다.

이제 단위 테스트를 할 때 문제가 발생합니다.

나는 나의 서비스 레이어를 테스트 할 뿐이라서 조롱 한 레포에는 내 모든 nhibernate 코드가있다. 이 문제가 발생하면 문제가 발생합니다.

User user = repo.GetUser(email); 

이것은 사용자 개체를 반환해야합니다.

그래서 지금 문제는 여기에 내가 그 사용자 개체를 만들고 반환 부분에 넣어 필요가

은이

repo.Setup(x => x.GetUser(It.IsAny<string>())).Return(/* UserObject here */) 

할 MOQ를 사용하고 싶습니다. 문제는 내가 실제로 서비스 레이어로 (일부 컬렉션에 대한 몇 가지 LINQ를 할 나중에 사용하기 때문에 아이디를 설정할 필요가 자리하고있는 곳

그래서 나는

User user = new User() 
{ 
    UserId = 10, 
} 

같은 것을 할 것입니다 그러나 이것은이다 내 데이터베이스에 있지 않아야 내 repo에 없어야합니다.) 그래서 설정해야하지만 개인 설정이므로 설정할 수 없습니다.

그럼 어떻게해야합니까? 나는 그저 사적인 것을 제거해야합니까 아니면 다른 방법이 있습니까?

답변

14

당신은 가짜 Repository 객체가 가짜 User 개체를 반환 할 수 있습니다 : 여기 관찰 할 몇 가지가

var stubUser = new Mock<User>(); 
stubUser.Setup(s => s.UserId).Returns(10); 

var stubRepo = new Mock<IUserRepository>(); 
stubRepo.Setup(s => s.GetUser(It.IsAny<string>())).Return(stubUser); 

있습니다을 :

  1. MOQ 할 수있는 구체적인 클래스의 가짜 멤버는 가상으로 표시됩니다. 일부 시나리오에서는 적용되지 않을 수도 있습니다.이 경우 Moq를 통해 객체를 가짜로 만드는 유일한 방법은 인터페이스를 구현하는 것입니다.
    그러나이 경우에는 User 클래스의 속성에 대해 NHibernate already imposes the same requirement이 지연로드를 수행하기 때문에 솔루션이 제대로 작동합니다.
  2. 가짜 객체가 다른 가짜 객체를 반환하면 가끔씩 지정된 단위 테스트가 발생할 수 있습니다. 이러한 상황에서 스텁과 모의 객체로 구성된 풍부한 객체 모델의 구성은 테스트가 정확히 무엇인지 판단하기가 어려워 지므로 테스트 자체를 읽을 수없고 유지하기 어렵게 만듭니다. 그것은 명확하게, 완벽하게 훌륭한 단위 테스트 연습이지만, 의식적으로 사용해야합니다.

관련 리소스 :

+0

덕분에이 그냥 작동, 난 또 다른 확장 방법을 아주. 나는 2 번 증상이있을 수 있습니다. 그냥 많은 것을 조롱해야합니다. 나는 그것을 시험 할 필요가있는 것만을 지키려고 노력하고있다. 예를 들어, 메소드에 사용되지 않았기 때문에 사용자의 userName을 조롱하지 않습니다. autofixture moq를 사용하고 싶습니다. 그러나 실제로 사용하는 방법을 알 수 없으며 실제 튜토리얼이없는 것 같아서 이것이 제가 도움이되는지 아닌지를 판단 할 수 없습니다. – chobo2

2

엔리코의 대답은 단위 테스트에 장소입니다. 이 문제는 Moq을 사용하고 싶지 않은 다른 환경에서도 발생하기 때문에 또 다른 해결책을 제시합니다. 프로덕션 코드에서이 기술을 정기적으로 사용합니다. 일반적인 사용 패턴은 클래스 멤버가 읽기 전용이지만 다른 특정 클래스는이를 수정해야합니다. 하나의 예는 정상적으로 읽기 전용이며 상태 시스템 또는 비즈니스 로직 클래스에 의해서만 설정되어야하는 상태 필드 일 수 있습니다.

기본적으로 속성을 설정하는 메서드가 포함 된 정적 중첩 클래스를 통해 전용 멤버에 액세스 할 수 있습니다.

public class User { 
    public int Id { get; private set; } 

    public static class Reveal { 
     public static void SetId(User user, int id) { 
      user.Id = id; 
     } 
    } 
} 

당신은 다음과 같이 사용 : 공용을 제공하는 것처럼

물론
User user = new User(); 
User.Reveal.SetId(user, 43); 

는,이 후 거의 쉽게 속성 값을 설정하는 사람을 가능하게 한 예는 천 개 단어 가치가있다 세터. 그러나이 기술 몇 가지 장점이 있습니다

  • 하여, 속성 세터 또는
  • 프로그래머가 명시 적으로 Reveal 클래스와 속성을 설정하는 이상한 구문을 사용해야합니다 SetId() 메소드에 대한 프롬프트 그들에게 메시지를 더 인텔리 없다 그들은 아마도이
  • 쉽게 만 t을 찾는 경우 표준 액세스 패턴

을 우회 어떤 코드를 볼 수 Reveal 클래스의 용도에 정적 분석을 수행 할 수있는 일을하지 말아야 o 단위 테스트 목적을 위해 사유 재산을 수정하고, 당신은 그 대상을 Moq 할 수있다, 그렇다면 엔리코의 제안을 여전히 추천 할 것이다. 그러나이 기술은 때때로 유용 할 수 있습니다.

+0

+1. 유사한 옵션 - InternalsVisibleToAttribute, protected 및 subclass, 리플렉션으로 균열, 적절한 경고 텍스트를 사용하여 ObsoleteAttribute를 사용하여 SetId를 설정하고, 오버로드 된 생성자 (가능한 경우 사용자 환경 설정)로 전달합니다. POCO/DTO. 내가 Moq을 사랑하는 것보다 훨씬 많은 것, Enrico는 과다 명세의 위험에 대해 권리가있다. – TrueWill

0

저장소를 조롱하는 대신, 테스트를 위해 메모리 내장 SQLite 데이터베이스를 사용하는 것이 좋습니다. 그것은 당신에게 당신이 찾고있는 속도를 줄 것이고 또한 많은 것들을 너무 쉽게 만들 것입니다. 작동하는 샘플을보고 싶다면 내 GitHub 프로젝트 중 하나를 볼 수 있습니다 : https://github.com/dlidstrom/GridBook.

1

엔티티 클래스를 조롱하지 않으려는 또 다른 방법은 리플렉션을 사용하여 개인/보호 된 ID를 설정하는 것입니다.

네, 저는 이것이 대개 매우 유리하게 보지 않으며 종종 어딘가에서 열악한 디자인의 표시로 인용된다는 것을 알고 있습니다. 그러나이 경우, NHibernate 엔티티에 보호 된 ID를 갖는 것이 표준 패러다임이므로 매우 합리적인 해결책으로 보인다.

우리는 최소한 멋지게 구현할 수 있습니다. 필자의 경우 엔티티의 95 %가 모두 고유 한 식별자로 단일 Guid를 사용하고 정수 만 사용합니다.따라서 우리의 엔티티 클래스는 일반적으로 매우 간단 HasID 인터페이스 구현 : 실제 엔티티 클래스에서

public interface IHasID<T> 
{ 
    T ID { get; } 
} 

을, 우리는이처럼 구현할 수 :이 ID는의 기본 키로 NHibernate에 매핑됩니다

public class User : IHasID<Guid> 
{ 
    Guid ID { get; protected set; } 
} 

을 평범한 태도. 우리의 단위 테스트에서이의 설정에

, 우리는 편리한 확장 방법을 제공하기 위해이 인터페이스를 사용할 수 있습니다 : 우리는이 작업을 수행 할 수있는 HasID 인터페이스가 필요가 없습니다

public static T WithID<T, K>(this T o, K id) where T : class, IHasID<K> 
{ 
    if (o == null) return o; 
    o.GetType().InvokeMember("ID", BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, o, new object[] { id }); 
    return o; 
} 

을하지만, 의미 약간의 추가 코드는 건너 뛸 수 있습니다. 예를 들어 ID가 실제로 지원되는지 여부는 확인할 필요가 없습니다.

사용에 난 보통 그냥 생성자의 마지막에 그것을 체인 있도록 확장 메서드는, 원래의 객체를 반환 : 실제로

var testUser = new User("Test User").WithID(new Guid("DC1BA89C-9DB2-48ac-8CE2-E61360970DF7")); 

또는 GUID의 내가 무엇 ID 실제로 상관하지 않기 때문에,

public static T WithNewGuid<T>(this T o) where T : class, IHasID<Guid> 
{ 
    if (o == null) return o; 
    o.GetType().InvokeMember("ID", BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, o, new object[] { Guid.NewGuid() }); 
    return o; 
} 

및 사용에

가 :

var testUser = new User("Test User").WithNewGuid();