แนวปฏิบัติที่เหมาะสมที่สุดสำหรับวิธีทดสอบหน่วยที่ใช้แคชอย่างหนัก


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 () ที่ดึงมาจากแคชเป็นรายละเอียดการใช้งานของพื้นที่เก็บข้อมูลและไม่ควรรู้โดยรหัสการโทร

สิ่งนี้ทำให้การทดสอบคลาสการกรองของคุณนั้นง่ายและสะดวก ไม่สนใจว่าคุณจะรับจากที่ใด

ประการที่สองห่อคลาสที่รับข้อมูลจากฐานข้อมูล (หรือที่ใดก็ได้) ใน wrapper แคช

AOPเป็นเทคนิคที่ดีสำหรับเรื่องนี้ เป็นหนึ่งในบางสิ่งที่ทำได้ดีมาก

การใช้เครื่องมือเช่นPostSharpคุณสามารถตั้งค่าเพื่อให้วิธีการใด ๆ ที่ทำเครื่องหมายด้วยแอตทริบิวต์ที่เลือกไว้จะถูกแคช อย่างไรก็ตามถ้านี่เป็นสิ่งเดียวที่คุณกำลังแคชคุณไม่จำเป็นต้องไปไกลเท่าที่มีกรอบ AOP เพียงแค่มี Repository และ Caching Wrapper ที่ใช้อินเทอร์เฟซเดียวกันและฉีดเข้าไปในคลาสที่เรียก

เช่น.

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 () เพียงแค่ ... รับทั้งหมด จากที่ไหนสักแห่ง


คุณจะทำอย่างไรถ้าคุณใช้ CachedProductRepository ใน ProductManager แต่ต้องการใช้วิธีการที่อยู่ใน SQLProductRepository
โจนาธาน

@ โจนาธาน: "เพียงแค่มี Repository และ Caching Wrapper ที่ใช้อินเทอร์เฟซเดียวกัน" - หากมีอินเทอร์เฟซเดียวกันคุณสามารถใช้วิธีการเดียวกันได้ รหัสการโทรไม่จำเป็นต้องรู้อะไรเกี่ยวกับการใช้งาน
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.