เยาะเย้ยวิธีการขยายด้วยขั้นต่ำ


174

ฉันมีส่วนต่อประสานก่อนหน้า ...

public interface ISomeInterface
{
    void SomeMethod();
}

และฉันได้ขยายอินเทอร์เฟซนี้โดยใช้มิกซ์อิน ...

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

ฉันมีคลาสที่เรียกสิ่งนี้ซึ่งฉันต้องการทดสอบ ...

public class Caller
{
    private readonly ISomeInterface someInterface;

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

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

และการทดสอบที่ฉันต้องการจำลองอินเทอร์เฟซและยืนยันการโทรไปยังวิธีการขยาย ...

    [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();
    }

การเรียกใช้การทดสอบนี้จะสร้างข้อยกเว้น ...

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

คำถามของฉันคือมีวิธีที่ดีในการเยาะเย้ยโทร mixin?


3
จากประสบการณ์ของฉันคำว่า mixin และวิธีการขยายเป็นสิ่งที่แยกจากกัน ฉันจะใช้อันหลังในตัวอย่างนี้เพื่อหลีกเลี่ยงการมิกซ์ยู: P
Ruben Bartelink

คำตอบ:


33

คุณไม่สามารถ "คงที่" จำลองวิธีแบบคงที่ (ดังนั้นวิธีการขยาย) ด้วยกรอบการเยาะเย้ย คุณสามารถลองใช้โมล ( http://research.microsoft.com/en-us/projects/pex/downloads.aspx ) ซึ่งเป็นเครื่องมือฟรีจาก Microsoft ที่ใช้วิธีการที่แตกต่างกัน นี่คือคำอธิบายของเครื่องมือ:

โมลเป็นเฟรมเวิร์กที่มีน้ำหนักเบาสำหรับการทดสอบสตับและการออกนอกเส้นทางใน. NET ซึ่งขึ้นอยู่กับผู้รับมอบสิทธิ์

โมลอาจถูกใช้เพื่ออ้อมใด ๆ วิธี. NET รวมถึงวิธีการที่ไม่ใช่เสมือน / คงที่ในประเภทที่ปิดผนึก

คุณสามารถใช้โมลกับกรอบการทดสอบใด ๆ (เป็นอิสระเกี่ยวกับเรื่องนั้น)


2
นอกจากไฝยังมีกรอบการเยาะเย้ยอื่น ๆ (ที่ไม่ใช่ฟรี) ที่ใช้ profiler API ของ. NET เพื่อจำลองวัตถุและดังนั้นจึงสามารถแทนที่การโทรใด ๆ ทั้งสองฉันรู้ว่ามีอยู่Telerik ของ JustMockและTypeMock Isolator
Marcel Gosselin

6
ทุ่นในทางทฤษฎีเป็นสิ่งที่ดี แต่ฉันพบสามประเด็นเมื่อฉันลองใช้มันซึ่งหยุดฉันใช้มัน ... 1) มันไม่ได้ทำงานใน Resharper NUnit Runner 2) คุณต้องสร้างชุดตัวตุ่นสำหรับชุดประกอบแต่ละชิ้นด้วยตนเอง 3 ) คุณต้องสร้างชุดประกอบตัวตุ่นใหม่ด้วยตนเองทุกครั้งที่มีการเปลี่ยนวิธีการสตับ
รัสเซลกิดดิงส์

26

ฉันใช้ Wrapper เพื่อแก้ไขปัญหานี้ สร้างวัตถุห่อหุ้มและผ่านวิธีการเยาะเย้ยของคุณ

ดูการเยาะเย้ยวิธีการคงที่สำหรับการทดสอบหน่วยโดย Paul Irwin มันมีตัวอย่างที่ดี


12
ฉันชอบคำตอบนี้เพราะสิ่งที่พูด (โดยไม่บอกโดยตรง) คือคุณต้องแก้ไขรหัสของคุณเพื่อให้สามารถทดสอบได้ นั่นเป็นวิธีการทำงาน เข้าใจว่าในการออกแบบ microchip / IC / ASIC ชิปเหล่านั้นจะต้องไม่เพียง แต่ถูกออกแบบมาให้ทำงานเท่านั้น แต่ยังออกแบบให้สามารถทดสอบได้อีกด้วยเพราะถ้าคุณไม่สามารถทดสอบไมโครชิพได้มันก็ไร้ประโยชน์ - คุณไม่สามารถรับประกันได้ งาน. กันไปสำหรับซอฟต์แวร์ หากคุณยังไม่ได้สร้างมันเพื่อให้สามารถทดสอบได้มันเป็น ... ไร้ประโยชน์ สร้างให้สามารถทดสอบได้ซึ่งในบางกรณีหมายถึงการเขียนรหัสใหม่ (และใช้โปรแกรมเสริม) แล้วสร้างการทดสอบอัตโนมัติที่ทดสอบ
Michael Plautz

2
ฉันสร้างไลบรารีขนาดเล็กที่ล้อม Dapper, Dapper.Contrib และ IDbConnection github.com/codeapologist/DataAbstractions.Dapper
Drew Sumido

15

ฉันพบว่าฉันต้องค้นพบด้านในของวิธีการขยายที่ฉันพยายามจำลองอินพุตสำหรับและจำลองสิ่งที่เกิดขึ้นภายในส่วนขยาย

ฉันดูโดยใช้ส่วนขยายเป็นการเพิ่มรหัสโดยตรงกับวิธีการของคุณ นั่นหมายความว่าฉันต้องล้อเลียนสิ่งที่เกิดขึ้นภายในส่วนขยายแทนที่จะเป็นส่วนขยาย


11

คุณสามารถจำลองอินเตอร์เฟสการทดสอบที่สืบทอดจากของจริงและมีสมาชิกที่มีลายเซ็นเดียวกันกับวิธีการขยาย

จากนั้นคุณสามารถจำลองอินเทอร์เฟซการทดสอบเพิ่มของจริงในการจำลองและเรียกวิธีการทดสอบในการตั้งค่า

การใช้งาน mock ของคุณสามารถเรียกใช้วิธีการใดก็ได้ที่คุณต้องการหรือเพียงตรวจสอบว่าวิธีการนั้นเรียกว่า:

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

ขอบคุณโซลูชันHåvard S ในโพสต์นี้สำหรับวิธีการใช้จำลองที่สนับสนุนสองอินเตอร์เฟส เมื่อฉันพบมันการปรับมันด้วยส่วนต่อการทดสอบและวิธีการคงที่คือการเดินเค้ก


คุณช่วยแสดงตัวอย่างลายเซ็นให้SomeNotAnExtensionMethodกับเราได้SomeNotAnExtensionMethodไหม ตอนนี้ฉันมีความคิดว่าจะสร้างลายเซ็นวิธีการส่วนขยายภายในอินเทอร์เฟซได้อย่างไร ...
Peter Csala

1
@Peter Csala: ขออภัยหากนี่ยังไม่ชัดเจน ฉันอัปเดตโพสต์เพื่อให้ชัดเจนขึ้นและเปลี่ยนชื่อ SomeNotAnExtensionMethod เป็น SomeRegularMethod
pasx

คุณส่งiRealพารามิเตอร์ไปยังSomeExtensionMethodในกรณีที่เป็นITestอย่างไร หากคุณผ่านเป็นพารามิเตอร์ตัวแรกคุณจะตั้งค่าอย่างไร Setup( x=> x.SomeExtensionMethod(x, ...)สิ่งนี้จะทำให้เกิดข้อยกเว้นรันไทม์
Peter Csala

คุณไม่ผ่านมัน จากมุมมองของ mock ITest มีวิธีการปกติโดยไม่มีพารามิเตอร์นี้เพื่อให้ตรงกับลายเซ็นของการเรียกไปยังวิธีการขยายในรหัสของคุณดังนั้นคุณจะไม่ได้รับ IReal ที่นี่และในกรณีใด ๆ การใช้ IReal / ITest . หากคุณต้องการเข้าถึงคุณสมบัติบางอย่างของ IReal ที่เยาะเย้ยใน SomeExtensionMethod คุณควรทำทุกอย่างใน Mock เช่น: object _mockCache = whatever... `Setup (x => x.SomeExtensionMethod (... ) .. Callback (() => คุณสามารถ เข้าถึง _mockCache ที่นี่);)
pasx

8

คุณสามารถจำลองวิธีการขยายได้อย่างง่ายดายด้วย JustMock API นั้นเหมือนกับวิธีการเยาะเย้ยวิธีปกติ พิจารณาดังต่อไปนี้

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

ในการจัดเรียงและตรวจสอบวิธีการนี้ให้ใช้สิ่งต่อไปนี้:

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);

นี่คือลิงค์ไปยังเอกสารประกอบ: ส่วนขยายวิธีการเยาะเย้ย


2

ฉันชอบที่จะใช้กระดาษห่อ (รูปแบบอะแดปเตอร์) เมื่อฉันห่อวัตถุเอง ฉันไม่แน่ใจว่าฉันใช้วิธีนี้สำหรับห่อส่วนขยายซึ่งไม่ได้เป็นส่วนหนึ่งของวัตถุ

ฉันใช้คุณสมบัติ Lazy Injectable ภายในของทั้งประเภท Action, Func, Predicate หรือผู้รับมอบสิทธิ์และอนุญาตให้มีการฉีด (สลับออก) วิธีการระหว่างการทดสอบหน่วย

    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;

จากนั้นคุณเรียก Func แทนวิธีการที่เกิดขึ้นจริง

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

สำหรับตัวอย่างที่สมบูรณ์ยิ่งขึ้นลองดูhttp://www.rhyous.com/2016/08/11/unit-testing-calls-to-complex-extension-methods/


นี่เป็นสิ่งที่ดี แต่ผู้อ่านควรทราบว่า _DoWorkMethod เป็นฟิลด์ใหม่ของคลาสซึ่งแต่ละอินสแตนซ์ของคลาสจะต้องจัดสรรฟิลด์เพิ่มเติมอีกหนึ่งฟิลด์ เป็นเรื่องยากที่เรื่องนี้ แต่บางครั้งก็ขึ้นอยู่กับจำนวนอินสแตนซ์ที่คุณจัดสรรในแต่ละครั้ง คุณสามารถแก้ไขปัญหานี้ได้โดยการทำให้ _DoWorkMethod คงที่ ข้อเสียคือถ้าคุณมีการทดสอบหน่วยทำงานพร้อมกันการทดสอบหน่วยที่แตกต่างกันสองครั้งที่อาจแก้ไขค่าคงที่เดียวกันจะเสร็จสมบูรณ์
zumalifeguard

-1

ดังนั้นหากคุณกำลังใช้ Moq และต้องการที่จะเยาะเย้ยผลลัพธ์ของวิธีการส่วนขยายจากนั้นคุณสามารถใช้ SetupReturnsDefault<ReturnTypeOfExtensionMethod>(new ConcreteInstanceToReturn())กับอินสแตนซ์ของคลาสจำลองที่มีวิธีการขยายที่คุณพยายามที่จะจำลอง

มันไม่สมบูรณ์แบบ แต่สำหรับวัตถุประสงค์การทดสอบหน่วยมันทำงานได้ดี


ฉันเชื่อว่าเป็น SetReturnsDefault <T> ()
David

ที่ไม่เคยส่งคืนตัวอย่างที่เป็นรูปธรรม เพียงโมฆะในกรณีของคลาส c # แบบกำหนดเอง!
HelloWorld
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.