Phương pháp mở rộng mô phỏng với Moq


172

Tôi có Giao diện từ trước ...

public interface ISomeInterface
{
    void SomeMethod();
}

và tôi đã mở rộng intreface này bằng cách sử dụng mixin ...

public static class SomeInterfaceExtensions
{
    public static void AnotherMethod(this ISomeInterface someInterface)
    {
        // Implementation here
    }
}

Tôi có một lớp học gọi nó mà tôi muốn kiểm tra ...

public class Caller
{
    private readonly ISomeInterface someInterface;

    public Caller(ISomeInterface someInterface)
    {
        this.someInterface = someInterface;
    }

    public void Main()
    {
        someInterface.AnotherMethod();
    }
}

và một thử nghiệm mà tôi muốn mô phỏng giao diện và xác minh cuộc gọi đến phương thức mở rộng ...

    [Test]
    public void Main_BasicCall_CallsAnotherMethod()
    {
        // Arrange
        var someInterfaceMock = new Mock<ISomeInterface>();
        someInterfaceMock.Setup(x => x.AnotherMethod()).Verifiable();

        var caller = new Caller(someInterfaceMock.Object);

        // Act
        caller.Main();

        // Assert
        someInterfaceMock.Verify();
    }

Chạy thử nghiệm này tuy nhiên tạo ra một ngoại lệ ...

System.ArgumentException: Invalid setup on a non-member method:
x => x.AnotherMethod()

Câu hỏi của tôi là, có một cách hay để chế nhạo cuộc gọi mixin?


3
Theo kinh nghiệm của tôi, các thuật ngữ mixin và phương thức mở rộng là những thứ riêng biệt. Tôi sẽ sử dụng cái sau trong trường hợp này để tránh trộn lẫn: P
Ruben Bartelink

Câu trả lời:


33

Bạn không thể "trực tiếp" mô phỏng phương thức tĩnh (do đó là phương thức mở rộng) với khung mô phỏng. Bạn có thể dùng thử Moles ( http://research.microsoft.com/en-us/projects/pex/doads.aspx ), một công cụ miễn phí của Microsoft thực hiện một cách tiếp cận khác. Dưới đây là mô tả của công cụ:

Moles là một khung công tác nhẹ để kiểm tra sơ khai và đường vòng trong .NET dựa trên các đại biểu.

Các nốt ruồi có thể được sử dụng để đi đường vòng bất kỳ phương thức .NET nào, kể cả các phương thức không ảo / tĩnh trong các loại kín.

Bạn có thể sử dụng Moles với bất kỳ khung thử nghiệm nào (nó độc lập về điều đó).


2
Ngoài Moles, còn có các khung mô phỏng (không miễn phí) khác sử dụng API profiler API của .NET để giả lập các đối tượng và do đó có thể thay thế bất kỳ cuộc gọi nào. Hai cái tôi biết là JustMockTypeMock Isolator của Telerik .
Marcel Gosselin

6
Về mặt lý thuyết, các nốt ruồi là tốt, nhưng tôi đã phát hiện ra ba vấn đề khi tôi dùng thử nó khiến tôi ngừng sử dụng nó ... 1) Nó không chạy trong Resharper NUnit runner 2) Bạn cần phải tự tạo một cụm nốt ruồi cho mỗi cụm lắp ghép 3 ) Bạn cần phải tự tạo lại một cụm nốt ruồi bất cứ khi nào phương thức khai thác thay đổi.
Russell Giddings

26

Tôi đã sử dụng một Wrapper để khắc phục vấn đề này. Tạo một đối tượng bao bọc và vượt qua phương thức giả định của bạn.

Xem Phương pháp mô phỏng tĩnh để kiểm tra đơn vị của Paul Irwin, nó có các ví dụ hay.


12
Tôi thích câu trả lời này bởi vì những gì nó đang nói (không cần nói trực tiếp) là bạn cần thay đổi mã của mình để làm cho nó có thể kiểm tra được. Đó chỉ là cách nó hoạt động. Hiểu rằng trong thiết kế microchip / IC / ASIC, những con chip đó không chỉ được thiết kế để hoạt động mà còn được thiết kế để có thể kiểm tra được, bởi vì nếu bạn không thể kiểm tra một vi mạch, điều đó là vô ích - bạn không thể đảm bảo nó sẽ công việc. Phần mềm cũng vậy. Nếu bạn chưa xây dựng nó để có thể kiểm tra được thì nó ... vô dụng. Xây dựng nó để có thể kiểm tra, trong một số trường hợp có nghĩa là viết lại mã (và sử dụng các hàm bao), sau đó xây dựng các kiểm tra tự động kiểm tra nó.
Michael Plautz

2
Tôi đã tạo một thư viện nhỏ bao bọc Dapper, Dapper.Contrib và IDbConnection. github.com/codeapologist/Data
sát thương.Dapper

15

Tôi thấy rằng tôi phải khám phá bên trong phương thức mở rộng mà tôi đang cố gắng giả định đầu vào và giả định những gì đang diễn ra bên trong tiện ích mở rộng.

Tôi đã xem bằng cách sử dụng một phần mở rộng như thêm mã trực tiếp vào phương thức của bạn. Điều này có nghĩa là tôi cần phải chế giễu những gì xảy ra bên trong tiện ích mở rộng hơn là chính tiện ích mở rộng.


11

Bạn có thể giả định một giao diện thử nghiệm kế thừa từ giao diện thực và có một thành viên có cùng chữ ký với phương thức mở rộng.

Sau đó, bạn có thể mô phỏng giao diện kiểm tra, thêm giao diện thực vào giả và gọi phương thức kiểm tra trong thiết lập.

Việc triển khai giả của bạn sau đó có thể gọi bất kỳ phương thức nào bạn muốn hoặc chỉ cần kiểm tra phương thức đó được gọi là:

IReal //on which some extension method is defined
{
    ... SomeRegularMethod(...);
}

static ExtensionsForIReal
{
    static ... SomeExtensionMethod(this IReal iReal,...);
}

ITest: IReal
{
    //This is a regular method with same name and signature as the extension without the "this IReal iReal" parameter
    ... SomeExtensionMethod(...);
}

var someMock = new Mock<ITest>();
Mock.As<IReal>(); //ad IReal to the mock
someMock.Setup(x => x.SomeExtensionMethod(...)).Verifiable(); //Calls SomeExtensionMethod on ITest
someMock.As<IReal>().Setup(x => x.SomeRegularMethod(...)).Verifiable(); //Calls SomeRegularMethod on IReal

Cảm ơn giải pháp Håvard S trong bài đăng này về cách triển khai một bản giả hỗ trợ hai giao diện. Khi tôi tìm thấy nó, việc điều chỉnh nó với giao diện thử nghiệm và phương thức tĩnh là một cuộc dạo chơi trên bánh.


Bạn có thể chỉ cho chúng tôi xin chữ ký mẫu cho SomeNotAnExtensionMethodSomeNotAnExtensionMethod? Bây giờ tôi có ý tưởng làm thế nào để tạo chữ ký phương thức mở rộng bên trong một giao diện ...
Peter Csala

1
@Peter Csala: Xin lỗi nếu điều này không rõ ràng. Tôi đã cập nhật bài đăng để làm cho nó rõ ràng hơn và đổi tên thành someNotAnExtensionMethod thành someRegularMethod.
pasx

Làm thế nào để bạn truyền iRealtham số SomeExtensionMethodtrong trường hợp ITest? Nếu bạn vượt qua nó như một tham số đầu tiên thì làm thế nào để bạn thiết lập? Setup( x=> x.SomeExtensionMethod(x, ...)điều này sẽ gây ra một ngoại lệ thời gian chạy.
Peter Csala

Bạn không vượt qua nó. Từ quan điểm của mock ITest chứa một phương thức thông thường không có tham số này để khớp với chữ ký của các cuộc gọi với phương thức mở rộng trong mã của bạn, do đó bạn sẽ không bao giờ nhận được IReal ở đây và trong mọi trường hợp, việc triển khai IReal / ITest là chính giả . Nếu bạn muốn truy cập một số thuộc tính của IReal bị giả định trong someExtensionMethod, bạn nên thực hiện tất cả những điều đó trong giả, ví dụ: object _mockCache = whatever... `Setup (x => x.SomeExtensionMethod (...) .. Callback (() => bạn có thể truy cập _mockCache tại đây);)
pasx

8

Bạn có thể dễ dàng giả định một phương thức mở rộng với JustMock . API giống như phương pháp chế nhạo thông thường. Hãy xem xét những điều sau đây

public static string Echo(this Foo foo, string strValue) 
{ 
    return strValue; 
}

Để sắp xếp và xác minh phương pháp này, hãy sử dụng như sau:

string expected = "World";

var foo = new Foo();
Mock.Arrange(() => foo.Echo(Arg.IsAny<string>())).Returns(expected);

string result = foo.Echo("Hello");

Assert.AreEqual(expected, result);

Đây cũng là một liên kết đến tài liệu: Phương pháp mở rộng Mocking


2

Tôi thích sử dụng trình bao bọc (mẫu bộ điều hợp) khi tôi tự bọc đối tượng. Tôi không chắc chắn tôi sẽ sử dụng nó để gói một phương thức mở rộng, không phải là một phần của đối tượng.

Tôi sử dụng Thuộc tính Lazy tiêm nội bộ thuộc loại Hành động, Func, Vị ngữ hoặc đại biểu và cho phép tiêm (hoán đổi) phương pháp trong khi thử nghiệm đơn vị.

    internal Func<IMyObject, string, object> DoWorkMethod
    {
        [ExcludeFromCodeCoverage]
        get { return _DoWorkMethod ?? (_DoWorkMethod = (obj, val) => { return obj.DoWork(val); }); }
        set { _DoWorkMethod = value; }
    } private Func<IMyObject, string, object> _DoWorkMethod;

Sau đó, bạn gọi Func thay vì phương thức thực tế.

    public object SomeFunction()
    {
        var val = "doesn't matter for this example";
        return DoWorkMethod.Invoke(MyObjectProperty, val);
    }

Để có một ví dụ đầy đủ hơn, hãy xem http://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/


Điều này là tốt, nhưng độc giả nên lưu ý rằng _DoWorkMethod là một trường mới của lớp mà mỗi phiên bản của lớp hiện phải phân bổ thêm một trường. Điều này hiếm khi xảy ra, nhưng đôi khi nó phụ thuộc vào số lượng phiên bản bạn phân bổ bất cứ lúc nào. Bạn có thể giải quyết vấn đề này bằng cách tạo _DoWorkMethod tĩnh. Nhược điểm của nó là nếu bạn có các bài kiểm tra đơn vị chạy đồng thời, hai bài kiểm tra đơn vị khác nhau có thể sửa đổi cùng một giá trị tĩnh sẽ hoàn thành.
bảo vệ zumalififard

-1

Vì vậy, nếu bạn đang sử dụng Moq và muốn mô phỏng kết quả của phương thức Tiện ích mở rộng, thì bạn có thể sử dụng SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn())ví dụ của lớp giả có phương thức tiện ích mở rộng mà bạn đang cố gắng giả định.

Nó không hoàn hảo, nhưng với mục đích thử nghiệm đơn vị, nó hoạt động tốt.


Tôi tin rằng đó là SetReturnsDefault <T> ()
David

Điều đó không bao giờ trả lại ví dụ cụ thể. Chỉ null trong trường hợp của một lớp c # tùy chỉnh!
HelloWorld
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.