ใช้ Mockito เพื่อทดสอบคลาสนามธรรม


213

ฉันต้องการทดสอบคลาสนามธรรม แน่นอนว่าฉันสามารถเขียนจำลองที่สืบทอดมาจากคลาสได้ด้วยตนเอง

ฉันสามารถทำสิ่งนี้โดยใช้กรอบการเยาะเย้ย (ฉันใช้ Mockito) แทนการประดิษฐ์จำลองด้วยมือของฉันได้หรือไม่? อย่างไร?


2
ในฐานะของ Mockito 1.10.12 , Mockito สนับสนุนการสอดแนม / เยาะเย้ยคลาสนามธรรมโดยตรง:SomeAbstract spy = spy(SomeAbstract.class);
Pesche

6
ในฐานะของ Mockito 2.7.14 คุณสามารถเยาะเย้ย classess นามธรรมที่ต้องใช้อาร์กิวเมนต์ตัวสร้างผ่านmock(MyAbstractClass.class, withSettings().useConstructor(arg1, arg2).defaultAnswer(CALLS_REAL_METHODS))
Gediminas Rimsa

คำตอบ:


315

คำแนะนำต่อไปนี้ให้คุณทดสอบคลาสนามธรรมโดยไม่ต้องสร้างคลาสย่อย "ของจริง" Mock คือคลาสย่อย

ใช้Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS)แล้วจำลองวิธีการเชิงนามธรรมใด ๆ ที่ถูกเรียกใช้

ตัวอย่าง:

public abstract class My {
  public Result methodUnderTest() { ... }
  protected abstract void methodIDontCareAbout();
}

public class MyTest {
    @Test
    public void shouldFailOnNullIdentifiers() {
        My my = Mockito.mock(My.class, Mockito.CALLS_REAL_METHODS);
        Assert.assertSomething(my.methodUnderTest());
    }
}

หมายเหตุ: ความงามของโซลูชันนี้คือคุณไม่จำเป็นต้องใช้วิธีที่เป็นนามธรรมตราบใดที่มันไม่เคยถูกเรียกใช้

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


14
ดังที่ระบุไว้ด้านล่างสิ่งนี้ไม่ได้ผลเมื่อคลาสนามธรรมเรียกวิธีนามธรรมเพื่อทดสอบซึ่งมักจะเป็นกรณี
Richard Nichols

11
สิ่งนี้ใช้ได้จริงเมื่อคลาส abstract เรียกใช้เมธอด abstract เพียงใช้ไวยากรณ์ doReturn หรือ doNothing แทน Mockito เมื่อทำการขัดวิธีการแบบนามธรรมและถ้าคุณทำการโทรแบบคอนกรีตใด ๆ ให้แน่ใจว่าการขัดการโทรแบบนามธรรมมาก่อน
Gonen ฉัน

2
ฉันจะฉีดการพึ่งพาในวัตถุประเภทนี้ได้อย่างไร (คลาสนามธรรมที่เย้ยหยันที่เรียกวิธีการจริง)
ซามูเอล

2
สิ่งนี้จะทำงานในรูปแบบที่ไม่คาดคิดถ้าคลาสที่มีปัญหานั้นมีอินสแตนซ์เริ่มต้น Mockito ข้าม initializers สำหรับ mocks ซึ่งหมายความว่าตัวแปรอินสแตนซ์ที่เริ่มต้นอินไลน์จะเป็นโมฆะโดยไม่คาดคิดซึ่งอาจทำให้เกิด NPE
digitalbath

1
เกิดอะไรขึ้นถ้าคอนสตรัคคลาสนามธรรมใช้พารามิเตอร์อย่างน้อยหนึ่งพารามิเตอร์
SD

68

หากคุณเพียงแค่ต้องทดสอบวิธีที่เป็นรูปธรรมบางอย่างโดยไม่ต้องสัมผัสใด ๆ ของบทคัดย่อคุณสามารถใช้CALLS_REAL_METHODS(ดูคำตอบของ Morten ) แต่ถ้าวิธีคอนกรีตภายใต้การทดสอบเรียกบางส่วนของบทคัดย่อหรือวิธีการอินเตอร์เฟซที่ไม่ได้ใช้งาน - Mockito จะบ่นว่า "ไม่สามารถเรียกใช้เมธอดจริงบนส่วนต่อประสาน Java"

(ใช่มันเป็นการออกแบบที่มีหมัด แต่มีกรอบบางอย่างเช่น Tapestry 4 ซึ่งบังคับให้คุณทำ)

วิธีแก้ปัญหาคือการย้อนกลับวิธีการนี้ - ใช้พฤติกรรมการเยาะเย้ยสามัญ (เช่นทุกสิ่งที่ถูกเยาะเย้ย / ถูก stubbed) และใช้doCallRealMethod()เพื่อเรียกวิธีการที่เป็นรูปธรรมภายใต้การทดสอบอย่างชัดเจน เช่น

public abstract class MyClass {
    @SomeDependencyInjectionOrSomething
    public abstract MyDependency getDependency();

    public void myMethod() {
        MyDependency dep = getDependency();
        dep.doSomething();
    }
}

public class MyClassTest {
    @Test
    public void myMethodDoesSomethingWithDependency() {
        MyDependency theDependency = mock(MyDependency.class);

        MyClass myInstance = mock(MyClass.class);

        // can't do this with CALLS_REAL_METHODS
        when(myInstance.getDependency()).thenReturn(theDependency);

        doCallRealMethod().when(myInstance).myMethod();
        myInstance.myMethod();

        verify(theDependency, times(1)).doSomething();
    }
}

อัปเดตเพื่อเพิ่ม:

สำหรับวิธีที่ไม่เป็นโมฆะคุณจะต้องใช้thenCallRealMethod()แทนเช่น:

when(myInstance.myNonVoidMethod(someArgument)).thenCallRealMethod();

มิฉะนั้น Mockito จะบ่นว่า "ตรวจพบการขัดที่ยังไม่เสร็จ"


9
สิ่งนี้จะใช้ได้ในบางกรณีอย่างไรก็ตาม Mockito ไม่ได้เรียกตัวสร้างของคลาสนามธรรมพื้นฐานด้วยวิธีนี้ สิ่งนี้อาจทำให้ "วิธีการจริง" ล้มเหลวเนื่องจากสถานการณ์ที่ไม่คาดคิดกำลังถูกสร้างขึ้น ดังนั้นวิธีนี้จะไม่ทำงานในทุกกรณีเช่นกัน
Richard Nichols

3
ใช่คุณไม่สามารถพึ่งพาสถานะของวัตถุได้ทั้งหมดเพียงรหัสในวิธีการที่ถูกเรียก
David Moles

โอ้ดังนั้นวิธีการวัตถุได้แยกออกจากรัฐที่ดี
haelix

17

คุณสามารถทำได้โดยใช้สายลับ (ใช้ Mockito รุ่น 1.8 ขึ้นไป)

public abstract class MyAbstract {
  public String concrete() {
    return abstractMethod();
  }
  public abstract String abstractMethod();
}

public class MyAbstractImpl extends MyAbstract {
  public String abstractMethod() {
    return null;
  }
}

// your test code below

MyAbstractImpl abstractImpl = spy(new MyAbstractImpl());
doReturn("Blah").when(abstractImpl).abstractMethod();
assertTrue("Blah".equals(abstractImpl.concrete()));

14

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

เมื่อทำการทดสอบคลาสนามธรรมคุณต้องการดำเนินการวิธีที่ไม่เป็นนามธรรมของหัวเรื่องภายใต้การทดสอบ (SUT) ดังนั้นกรอบการเยาะเย้ยไม่ใช่สิ่งที่คุณต้องการ

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

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

ทางเลือกอื่นคือทำให้กรณีทดสอบของคุณเป็นนามธรรมด้วยวิธีนามธรรมในการสร้าง SUT (กล่าวอีกนัยหนึ่งกรณีทดสอบจะใช้รูปแบบการออกแบบวิธีแม่แบบ)


8

ลองใช้คำตอบที่กำหนดเอง

ตัวอย่างเช่น:

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class CustomAnswer implements Answer<Object> {

    public Object answer(InvocationOnMock invocation) throws Throwable {

        Answer<Object> answer = null;

        if (isAbstract(invocation.getMethod().getModifiers())) {

            answer = Mockito.RETURNS_DEFAULTS;

        } else {

            answer = Mockito.CALLS_REAL_METHODS;
        }

        return answer.answer(invocation);
    }
}

มันจะคืนค่าการเยาะเย้ยสำหรับวิธีนามธรรมและจะเรียกวิธีการที่แท้จริงสำหรับวิธีการที่เป็นรูปธรรม


5

สิ่งที่ทำให้ฉันรู้สึกแย่เกี่ยวกับการเรียนนามธรรมที่เยาะเย้ยคือความจริงที่ว่าตัวสร้างเริ่มต้นของคุณ YourAbstractClass () ไม่ได้ถูกเรียก ด้วย ArrayList ที่ว่างเปล่าหรือ LinkedList)

คลาสนามธรรมของฉัน (โดยทั่วไปแล้วจะสร้างซอร์สโค้ดคลาส) ไม่ได้จัดเตรียม setter injection สำหรับองค์ประกอบลิสต์หรือคอนสตรัคเตอร์ที่เริ่มต้นองค์ประกอบรายการ (ซึ่งฉันพยายามเพิ่มด้วยตนเอง)

เฉพาะแอททริบิวต์คลาสเท่านั้นที่ใช้การกำหนดค่าเริ่มต้น: private List dep1 = new ArrayList; รายการส่วนตัว dep2 = ArrayList ใหม่

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

น่าเสียดายที่ PowerMock เท่านั้นที่จะช่วยเหลือคุณได้อีก


2

สมมติว่าคลาสทดสอบของคุณอยู่ในแพ็คเกจเดียวกัน (ภายใต้รูทซอร์สที่ต่างกัน) กับคลาสที่คุณทดสอบคุณสามารถสร้างจำลองได้:

YourClass yourObject = mock(YourClass.class);

และเรียกวิธีการที่คุณต้องการทดสอบเช่นเดียวกับวิธีอื่น ๆ

คุณต้องให้ความคาดหวังสำหรับแต่ละวิธีที่เรียกว่าด้วยความคาดหวังในวิธีที่เป็นรูปธรรมใด ๆ ที่เรียกใช้วิธีการ super - ไม่แน่ใจว่าคุณจะทำเช่นนั้นกับ Mockito ได้อย่างไร แต่ฉันเชื่อว่าเป็นไปได้ด้วย EasyMock

ทั้งหมดนี้กำลังทำคือการสร้างอินสแตนซ์ที่เป็นรูปธรรมYouClassและช่วยให้คุณประหยัดการใช้งานที่ว่างเปล่าของแต่ละวิธีนามธรรม

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


3
แต่การใช้การเยาะเย้ยจะไม่ทดสอบวิธีที่เป็นรูปธรรมของ YourClass หรือฉันผิด นี่ไม่ใช่สิ่งที่ฉันแสวงหา
ripper234

1
ถูกต้องข้างต้นจะไม่ทำงานถ้าคุณต้องการเรียกใช้วิธีการที่เป็นรูปธรรมในระดับนามธรรม
Richard Nichols

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

แต่คุณยังคงทดสอบการเยาะเย้ยไม่ใช่วิธีที่เป็นรูปธรรม
Jonatan Cloutier

2

คุณสามารถขยายคลาสนามธรรมด้วยคลาสนิรนามในแบบทดสอบของคุณ ตัวอย่าง (ใช้ Junit 4):

private AbstractClassName classToTest;

@Before
public void preTestSetup()
{
    classToTest = new AbstractClassName() { };
}

// Test the AbstractClassName methods.

2

Mockito อนุญาตให้เรียนวิชานามธรรมโดยใช้@Mockคำอธิบายประกอบ:

public abstract class My {

    public abstract boolean myAbstractMethod();

    public void myNonAbstractMethod() {
        // ...
    }
}

@RunWith(MockitoJUnitRunner.class)
public class MyTest {

    @Mock(answer = Answers.CALLS_REAL_METHODS)
    private My my;

    @Test
    private void shouldPass() {
        BDDMockito.given(my.myAbstractMethod()).willReturn(true);
        my.myNonAbstractMethod();
        // ...
    }
}

ข้อเสียคือไม่สามารถใช้งานได้หากคุณต้องการพารามิเตอร์ Constructor


0

คุณสามารถยกตัวอย่างคลาสที่ไม่ระบุชื่อฉีด mocks ของคุณแล้วทดสอบคลาสนั้น

@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    @Mock
    MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.classUnderTest = getInstance();
    }

    private ClassUnderTest getInstance() {
        return new ClassUnderTest() {

            private ClassUnderTest init(
                    MyDependencyService myDependencyService
            ) {
                this.myDependencyService = myDependencyService;
                return this;
            }

            @Override
            protected void myMethodToTest() {
                return super.myMethodToTest();
            }
        }.init(myDependencyService);
    }
}

โปรดจำไว้ว่าการมองเห็นจะต้องprotectedสำหรับคุณสมบัติของระดับนามธรรมmyDependencyServiceClassUnderTest


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