캐시를 많이 사용하는 단위 테스트 방법에 대한 모범 사례?


17

캐시에서 객체와 객체 목록을 필터링하고 필터링하고 저장하는 많은 비즈니스 로직 방법이 있습니다.

치다

IList<TObject> AllFromCache() { ... }

TObject FetchById(guid id) { ... }

IList<TObject> FilterByPropertry(int property) { ... }

Fetch..그리고 Filter..부를 것이다 AllFromCache이없는 경우 캐시와 수익을 채우는하고 있는지 단지에서 반환한다.

나는 일반적으로 이것들을 단위 테스트하는 것을 부끄러워합니다. 이 유형의 구조에 대한 단위 테스트의 모범 사례는 무엇입니까?

TestInitialize에서 캐시를 채우고 TestCleanup에서 제거하는 것을 고려했지만 그것이 나에게 맞지 않습니다.

답변:


18

진정한 단위 테스트를 원한다면 캐시를 모의해야합니다. 캐시와 동일한 인터페이스를 구현하는 모의 객체를 작성하십시오. 그러나 캐시 대신 수신되는 호출을 추적하고 항상 실제 값을 반환합니다 테스트 사례에 따라 캐시가 반환되어야합니다.

물론 캐시 자체에는 단위 테스트가 필요합니다.이를 위해서는 캐시에 의존하는 것을 모방해야합니다.

실제 캐시 객체를 사용하지만 알려진 상태로 초기화하고 테스트 후 정리하는 것은 통합 테스트와 비슷합니다. 여러 장치를 함께 테스트하기 때문입니다.


+1 이것은 최선의 접근법이다. 논리를 확인하기위한 단위 테스트와 실제로 캐시가 제대로 작동하는지 확인하기위한 통합 테스트
Tom Squires

10

단일 책임 원칙은 여기에 당신의 가장 친한 친구입니다.

우선 AllFromCache ()를 리포지토리 클래스로 이동하고 GetAll ()이라고합니다. 캐시에서 검색한다는 것은 리포지토리의 구현 세부 사항이며 호출 코드로 알 수 없습니다.

이를 통해 필터링 클래스를 쉽고 편리하게 테스트 할 수 있습니다. 더 이상 어디에서 왔는지 신경 쓰지 않습니다.

둘째, 데이터베이스에서 데이터를 가져 오는 클래스를 캐싱 래퍼로 래핑합니다.

AOP 는이를위한 좋은 기술입니다. 그것은 아주 잘하는 몇 가지 중 하나입니다.

PostSharp 와 같은 도구를 사용하면 선택한 속성으로 표시된 메소드가 캐시되도록 설정할 수 있습니다. 그러나 이것이 캐싱하는 유일한 방법 인 경우 AOP 프레임 워크를 가질 필요가 없습니다. 동일한 인터페이스를 사용하는 리포지토리와 캐싱 래퍼 만 있으면이를 호출 클래스에 주입 할 수 있습니다.

예.

public class ProductManager
{
    private IProductRepository ProductRepository { get; set; }

    public ProductManager
    {
        ProductRepository = productRepository;
    }

    Product FetchById(guid id) { ... }

    IList<Product> FilterByPropertry(int property) { ... }
}

public interface IProductRepository
{
    IList<Product> GetAll();
}

public class SqlProductRepository : IProductRepository
{
    public IList<Product> GetAll()
    {
        // DB Connection, fetch
    }
}

public class CachedProductRepository : IProductRepository
{
    private IProductRepository ProductRepository { get; set; }

    public CachedProductRepository (IProductRepository productRepository)
    {
        ProductRepository = productRepository;
    }

    public IList<Product> GetAll()
    {
        // Check cache, if exists then return, 
        // if not then call GetAll() on inner repository
    }
}

ProductManager에서 리포지토리 구현 지식을 어떻게 제거 했습니까? 데이터 추출을 처리하는 클래스, 데이터 검색을 처리하는 클래스 및 캐싱을 처리하는 클래스를 통해 단일 책임 원칙을 준수한 방법도 참조하십시오.

이제 이러한 리포지토리 중 하나를 사용하여 ProductManager를 인스턴스화하고 캐싱을 수행 할 수 있습니다. 이것은 나중에 캐시의 결과라고 생각되는 혼란스러운 버그가 발생할 때 매우 유용합니다.

productManager = new ProductManager(
                         new SqlProductRepository()
                         );

productManager = new ProductManager(
                         new CachedProductRepository(new SqlProductRepository())
                         );

(IOC 컨테이너를 사용하는 경우 더 좋습니다. 적응 방법이 분명해야합니다.)

그리고 ProductManager 테스트에서

IProductRepository repo = MockRepository.GenerateStrictMock<IProductRepository>();

캐시를 전혀 테스트 할 필요가 없습니다.

이제 질문은 다음과 같습니다. CachedProductRepository를 테스트해야합니까? 나는 제안하지 않는다. 캐시는 꽤 불확실하다. 프레임 워크는 통제 할 수없는 작업을 수행합니다. 예를 들어 너무 가득 차면 물건을 제거하는 것과 같습니다. 당신은 푸른 달에 한번 실패한 테스트로 끝날 것이고, 왜 그런지 결코 이해하지 못할 것입니다.

그리고 위에서 제안한 변경 사항을 적용하면 실제로 테스트 할 논리가 많지 않습니다. 정말 중요한 테스트 인 필터링 방법이 있으며 GetAll ()의 세부 사항에서 완전히 추상화됩니다. GetAll () 그냥 ... 모두 가져옵니다. 어딘가에서.


ProductManager에서 CachedProductRepository를 사용하지만 SQLProductRepository에있는 메소드를 사용하려면 어떻게해야합니까?
Jonathan

@Jonathan : "같은 인터페이스를 사용하는 리포지토리와 캐싱 래퍼가 있어야합니다."동일한 인터페이스를 사용하는 경우 동일한 방법을 사용할 수 있습니다. 호출 코드는 구현에 대해 아무것도 알 필요가 없습니다.
pdr

3

당신의 제안 된 접근 방식은 내가 할 일입니다. 설명이 주어지면 캐시에 객체가 있는지 여부에 관계없이 메소드의 결과는 동일해야합니다. 여전히 동일한 결과를 얻어야합니다. 각 테스트 전에 특정 방식으로 캐시를 설정하면 테스트하기가 쉽습니다. guid가 null있거나 객체에 요청 된 속성이없는 경우와 같은 추가 사례가있을 수 있습니다. 그것들도 테스트 할 수 있습니다.

또한 객체가 처음 캐시에 있는지 여부에 관계없이 메서드가 반환 된 후 캐시에 객체가있을 것으로 예상 할 수 있습니다 . 어떤 사람들은 (자신 포함)에 대해 신경 주장 하듯이, 논쟁입니다 무엇을 당신이 당신의 인터페이스에서 다시 얻을 수없는 방법 당신은 그것을 얻을 (즉 인터페이스가 작동이 예상대로 당신의 테스트,하지가 특정 구현을 가지고). 중요하다고 생각되면 테스트 할 기회가 있습니다.


1

TestInitialize에서 캐시를 채우고 TestCleanup에서 제거하는 것을 고려했지만 그 느낌이 맞지 않습니다.

실제로, 이것이 유일한 올바른 방법입니다. 이것이 바로 두 가지 기능입니다. 전제 조건을 설정하고 정리합니다. 전제 조건이 충족되지 않으면 프로그램이 작동하지 않을 수 있습니다.


0

최근에 캐시를 사용하는 일부 테스트를 진행하고있었습니다. 캐시와 함께 작동하는 클래스 주위에 래퍼를 만든 다음이 래퍼가 호출되었다는 주장이있었습니다.

캐시와 함께 작동하는 기존 클래스가 정적이기 때문에 주로이 작업을 수행했습니다.


0

캐싱 논리는 테스트하지만 채우기 논리는 테스트하지 않는 것 같습니다. 따라서 테스트 할 필요가없는 것을 채우는 것이 좋습니다.

귀하의 AllFromCache()방법은 캐시를 채우는 것을 담당하며 이는 값 공급자와 같은 다른 것에 위임되어야합니다. 따라서 코드는 다음과 같습니다

private Supplier<TObject> supplier;

IList<TObject> AllFromCache() {
    if (!cacheInitialized) {
        //whatever logic needed to fill the cache
        cache.putAll(supplier.getValues());
        cacheInitialized = true;
    }

    return  cache.getAll();
}

이제 테스트 공급 업체를 조롱하여 미리 정의 된 값을 반환 할 수 있습니다. 이렇게하면 실제 필터링 및 가져 오기를 테스트하고 개체를로드하지 않을 수 있습니다.

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