Friday, March 18, 2011

Unit Testing: Giới thiệu về Moq

Moq cũng giống như RhynoMock hay TypeMock, là một thư viện .NET framework cho việc tạo các mock object. Nó hầu như sử dụng C# Lambda expression để tạo các kỳ vọng và kết quả trả về. Tuy nhiên, nó cũng bị chỉ trích vì hầu như không có sự phân biệt nào giữa Mock và Stub.
Moq cho phép bạn dể dàng thay đổi behaviour (hành vi) từ “Bắt buộc” sang “Tùy chọn” (Trong đó tùy chọn là ngầm định). Hành vi bắt buộc không cho phép bất kỳ triệu gọi nào đến mock object trừ khi trước đó đã được khai báo kỳ vọng tương ứng. Hành vi tùy chọn sẽ trả lại giá trị ngầm định nếu không được khai báo kỳ vọng. Ngoài ra bạn có thể thực hiện cấu hình hành vi theo ý đồ của bạn khi sử dụng.

Để giới thiệu về cách sử dụng Moq, chúng ta sẽ sử dụng lại ví dụ có trong bài viết về Unit Testing–Mock không phải là Stub. Với yêu cầu của bài toán, ta sẽ tạo interface IWarehouse và class Order với phương thức Order.Fill sử dụng IWarehouse như sau:
public interface IWarehouse
{
    bool HasInventory(string productName, int quantity);
    
    void Remove(string productName, int quantity);
}

public class Order
{
    public string ProductName { get; private set; }

    public int Quantity { get; private set; }

    public bool IsFilled { get; private set; }

    public Order(string productName, int quantity)
    {
        ProductName = productName;
        Quantity = quantity;

        IsFilled = false;
    }

    public void Fill(IWarehouse warehouse)
    {
        if (!warehouse.HasInventory(ProductName, Quantity))
        {
            return;
        }
        
        warehouse.Remove(ProductName, Quantity);

        IsFilled = true;
    }
}
Phương thức mà bạn muốn kiểm thử ở đây chính là Order.Fill(IWarehouse). Tuy nhiên, rõ ràng bạn không muốn thực hiện truy cập cơ sở dữ liệu hoặc web service để lấy về thông tin tồn kho (nếu sử dụng đối tượng thật), bởi vì khi đó bạn phải cài đặt nó, và do đó có thể xảy ra lỗi trong quá trình thực thi cũng như chưa chắc đảm bảo được việc cài đặt của bạn là chính xác và chạy đúng.
Do đó, để giải quyết vấn đề này, thì ta sẽ sử dụng mock obect và thiết lập sao cho phương thức warehouse.HasInventory(ProductName, Quantity) được thực hiện đúng theo mong muốn của từng trường hợp kiểm thử, người ta gọi đó là kỳ vọng.
Dưới đây là đoạn code thiết lập kỳ vọng:
// Khởi tạo đối tượng Order và thiết lập dữ liệu
Order order = new Order(TALISKER, 50);

// Khởi tạo một đối tượng mock sử dụng Moq
var warehouseMock = new Mock<IWarehouse>();

// Đảm bảo rằng sẽ trả lại true 
// nếu kiểm tra số lượng tồn kho 50 đối với sản phẩm TALISKER
warehouseMock
    .Setup(warehouse => warehouse.HasInventory(TALISKER, 50))
    .Returns(true);

// Đảm bảo rằng sản phẩm TALISKER sẽ được xuất kho 50 sản phẩm
warehouseMock
    .Setup(warehouse => warehouse.Remove(TALISKER, 50));
Tiếp theo, tùy thuộc vào quan điểm của bạn để kiểm tra kết quả, bạn có thể sử dụng kiểm tra bằng xác nhận trạng thái (state verification) hoặc xác nhận hành vi (behaviour verification) để viết đoạn code xác nhận. Ở đây, tôi đã sử dụng cả hai việc kiểm tra xác nhận trạng thái và xác nhận hành vi.
// Thực thi việc giao hàng theo đơn hàng
order.Fill(warehouseMock.Object);

// Xác nhận hành vi cho cả 2 kỳ vọng
// 1. Việc kiểm tra tồn kho được thực thi 
//    và trả về đúng giá trị mong muồn
// 2. Việc xuất kho được thực hiện
warehouseMock.Verify();

// Hoặc bạn có thể viết như sau để xác nhận từng kỳ vọng
// warehouseMock
//    .Verify(warehouse => warehouse.HasInventory(TALISKER, 50));
// warehouseMock
//    .Verify(warehouse => warehouse.Remove(TALISKER, 50));

// Xác nhận trạng thái: Đảm bảo đơn hàng sau khi  
// được tiến hành giao hàng đã đổi trạng thái đúng
Assert.IsTrue(order.IsFilled);
Việc xác nhận hành vi sẽ xác nhận để đảm bảo cả hai phương thức kỳ vọng đã được gọi một cách chính xác. Trong trường hợp xác nhận, bạn không cần quan tâm giá trị trả về. Điều quan trọng nhất trong trường hợp này là các phương thức HasInventory và Remove đã được gọi. Mỗi khi điều này được thực hiện, ta có thể xem như hành vi được kỳ vọng đã được xác nhận.
Đối việc xác nhận trạng thái, vì trong trường hợp kiểm thử này ta muốn kiểm tra xem khi đủ điều kiện giao hàng, giao dịch giao hàng được tiến hành thì liệu trạng thái của đơn hàng có được tự động chuyển sang trạng thái đã giao hàng hay chưa. Do đó, ta cần phải xác nhận trạng thái để đảm bảo rằng nghiệp vụ mong muốn của chúng ta được thực hiện đúng.
Nhiều người cho rằng xác nhận hành vi sẽ tốt hơn xác nhận trạng thái (hoặc ngược lại). Đối với quan điểm của tôi, tôi thích sử dụng cả hai xác nhận này. Trường hợp kiểm thử ở trên là ví dụ rất tốt cho quan điểm này, tôi có thể xác nhận hành vi của các kỳ vọng cũng như xác nhận trạng thái của đơn hàng sau khi nó được giao hàng.
Qua bài giới thiệu này, hy vọng rằng các bạn đã hình thành được khái niệm và cách sử dụng Moq để tạo các mock object trong việc kiểm thử TDD. Các ví dụ chi tiết hơn về Moq và tài liệu mô tả về nó, bạn có thể truy cập vào Moq Quick Start.

1 comment:

  1. Bài viết quá hay. Mặc dù không phải ý của bạn, nhưng từ ngữ bạn diễn dịch rất xuất sắc, đọc một lần là có thể hiểu ngay state testing và behavior testing

    ReplyDelete