การทดสอบวิธีการส่วนตัวโดยใช้ mockito


104
ชั้นสาธารณะ A {

    วิธีโมฆะสาธารณะ (บูลีน b) {
          ถ้า (b == จริง)
               วิธีที่ 1 ();
          อื่น
               วิธีที่ 2 ();
    }

    โมฆะส่วนตัว method1 () {}
    โมฆะส่วนตัว method2 () {}
}
ชั้นเรียนสาธารณะ TestA {

    @ทดสอบ
    โมฆะสาธารณะ testMethod () {
      a = เยาะเย้ย (A.class);
      ก. วิธีการ (จริง);
      // วิธีการทดสอบเช่นตรวจสอบ (a) .method1 ();
    }
}

วิธีการทดสอบ private method เรียกว่าอย่างไรหรือไม่และจะทดสอบ private method โดยใช้ mockito ได้อย่างไร ???


คำตอบ:


81

คุณไม่สามารถทำได้ด้วย Mockito แต่คุณสามารถใช้Powermockเพื่อขยาย Mockito และล้อเลียนวิธีการส่วนตัว Powermock รองรับ Mockito นี่คือตัวอย่าง


19
ฉันสับสนกับคำตอบนี้ นี่เป็นการล้อเลียน แต่ชื่อเรื่องกำลังทดสอบวิธีการส่วนตัว
diyoda_

ฉันใช้ Powermock เพื่อล้อเลียนวิธีการส่วนตัว แต่ฉันจะทดสอบวิธีส่วนตัวด้วย Powermock ได้อย่างไร ฉันสามารถส่งอินพุตบางส่วนและคาดหวังเอาต์พุตบางส่วนจากวิธีการแล้วตรวจสอบเอาต์พุตได้อย่างไร
Rito

คุณทำไม่ได้คุณล้อเลียนอินพุตเอาต์พุตคุณไม่สามารถทดสอบการทำงานจริงได้
Talha

131

ไม่สามารถทำได้ผ่านทาง mockito จากวิกิของพวกเขา

ทำไม Mockito ไม่ล้อเลียนวิธีการส่วนตัว?

ประการแรกเราไม่ดันทุรังที่จะล้อเลียนวิธีการส่วนตัว เราไม่สนใจเมธอดส่วนตัวเพราะจากมุมมองของการทดสอบเมธอดส่วนตัวไม่มีอยู่จริง นี่คือเหตุผลสองสามประการที่ Mockito ไม่ล้อเลียนวิธีการส่วนตัว:

ต้องมีการแฮ็ก classloaders ที่ไม่มีสัญลักษณ์แสดงหัวข้อย่อยและเปลี่ยน api (คุณต้องใช้ตัวทดสอบที่กำหนดเองใส่คำอธิบายประกอบคลาส ฯลฯ )

มันง่ายมากที่จะแก้ไข - เพียงแค่เปลี่ยนการเปิดเผยวิธีการจากส่วนตัวเป็นการป้องกันด้วยแพ็กเกจ (หรือป้องกัน)

ฉันต้องใช้เวลาในการใช้งานและบำรุงรักษา และมันไม่สมเหตุสมผลที่ให้จุด # 2 และความจริงที่ว่ามันถูกนำไปใช้แล้วในเครื่องมือที่แตกต่างกัน (powermock)

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


1
มีข้อสันนิษฐานที่ร้ายแรงจากคำสั่งนั้น:> การเยาะเย้ยเมธอดส่วนตัวเป็นการบอกใบ้ว่ามีบางอย่างผิดปกติกับความเข้าใจของ OO ถ้าฉันกำลังทดสอบวิธีสาธารณะและเรียกวิธีการส่วนตัวฉันอยากจะล้อเลียนวิธีการส่วนตัว การดำเนินการตามข้อสันนิษฐานข้างต้นทำให้ไม่จำเป็นต้องใช้วิธีการส่วนตัว ความเข้าใจที่ไม่ดีเกี่ยวกับ OO นั้นเป็นอย่างไร
eggmatters

1
@eggmatters อ้างอิงจาก Baeldung "เทคนิคการเยาะเย้ยควรนำไปใช้กับการอ้างอิงภายนอกของคลาสไม่ใช่กับคลาสนั้น ๆ หากการเยาะเย้ยวิธีการส่วนตัวเป็นสิ่งสำคัญสำหรับการทดสอบคลาสของเรามันมักจะบ่งบอกถึงการออกแบบที่ไม่ดี" นี่คือกระทู้เด็ดเกี่ยวกับมันsoftwareengineering.stackexchange.com/questions/100959/…
Jason Glez

34

นี่คือตัวอย่างเล็ก ๆ น้อย ๆ ในการใช้งานpowermock

public class Hello {
    private Hello obj;
    private Integer method1(Long id) {
        return id + 10;
    }
} 

ในการทดสอบmethod1 ให้ใช้รหัส:

Hello testObj = new Hello();
Integer result = Whitebox.invokeMethod(testObj, "method1", new Long(10L));

ในการตั้งค่าวัตถุส่วนตัวobj ให้ใช้สิ่งนี้:

Hello testObj = new Hello();
Hello newObject = new Hello();
Whitebox.setInternalState(testObj, "obj", newObject);

ลิงค์ของคุณชี้ไปที่ repo จำลองอำนาจเท่านั้น @Mindaugas
Xavier

@ ซาเวียร์จริง. คุณสามารถใช้ได้ถ้าคุณต้องการในโครงการของคุณ
Mindaugas Jaraminas

1
มหัศจรรย์ !!! อธิบายได้ดีด้วยตัวอย่างง่ายๆเหล่านี้เกือบทุกอย่าง :) เพราะจุดประสงค์คือเพื่อทดสอบโค้ดไม่ใช่สิ่งที่กรอบงานทั้งหมดให้ :)
siddhusingh

โปรดอัปเดตสิ่งนี้ Whitebox ไม่ได้เป็นส่วนหนึ่งของ API สาธารณะอีกต่อไป
user447607

17

คิดเกี่ยวกับเรื่องนี้ในแง่ของพฤติกรรมไม่ใช่ในแง่ของวิธีการที่มี วิธีการที่เรียกว่าmethodมีพฤติกรรมเฉพาะถ้าbเป็นจริง มันมีพฤติกรรมที่แตกต่างกันหากbเป็นเท็จ ซึ่งหมายความว่าคุณควรจะเขียนทั้งสองแตกต่างกันสำหรับการทดสอบmethod; หนึ่งสำหรับแต่ละกรณี ดังนั้นแทนที่จะมีการทดสอบเชิงวิธีการสามแบบ (หนึ่งสำหรับmethodหนึ่งสำหรับmethod1หนึ่งสำหรับmethod2คุณมีการทดสอบเชิงพฤติกรรมสองแบบ

ที่เกี่ยวข้องกับสิ่งนี้ (ฉันแนะนำสิ่งนี้ในเธรด SO อื่นเมื่อเร็ว ๆ นี้และได้รับการเรียกว่าคำสี่ตัวอักษรดังนั้นอย่าลังเลที่จะใช้สิ่งนี้กับเกลือหนึ่งเม็ด) ฉันคิดว่าการเลือกชื่อการทดสอบที่แสดงถึงพฤติกรรมที่ฉันกำลังทดสอบนั้นเป็นประโยชน์มากกว่าชื่อของวิธีการ จึงไม่เรียกการทดสอบของคุณtestMethod(), testMethod1(), testMethod2()และอื่น ๆ ฉันชอบชื่อที่เหมือนcalculatedPriceIsBasePricePlusTax()หรือtaxIsExcludedWhenExcludeIsTrue()บ่งบอกถึงพฤติกรรมที่ฉันกำลังทดสอบ จากนั้นในแต่ละวิธีทดสอบให้ทดสอบเฉพาะพฤติกรรมที่ระบุ พฤติกรรมดังกล่าวส่วนใหญ่จะเกี่ยวข้องกับการเรียกใช้วิธีการสาธารณะเพียงครั้งเดียว แต่อาจเกี่ยวข้องกับการเรียกใช้วิธีการส่วนตัวหลายครั้ง

หวังว่านี่จะช่วยได้


13

ในขณะที่ Mockito ไม่ได้ให้ความสามารถในการที่คุณจะได้บรรลุผลเดียวกันโดยใช้ Mockito + ระดับ JUnit ReflectionUtils หรือฤดูใบไม้ผลิReflectionTestUtilsระดับ โปรดดูตัวอย่างด้านล่างที่นำมาจากที่นี่เพื่ออธิบายวิธีเรียกใช้เมธอดส่วนตัว:

ReflectionTestUtils.invokeMethod(student, "saveOrUpdate", "From Unit test");

ตัวอย่างที่สมบูรณ์ด้วย ReflectionTestUtils และ Mockito สามารถพบได้ในหนังสือMockito for Spring


ReflectionTestUtils.invokeMethod (นักเรียน, "saveOrUpdate", "อาร์กิวเมนต์ 1", "อาร์กิวเมนต์ 2", "อาร์กิวเมนต์ 3"); อาร์กิวเมนต์สุดท้ายของ invokeMethod ใช้ Vargs ซึ่งสามารถรับอาร์กิวเมนต์หลายตัวที่ต้องการส่งผ่านไปยังเมธอดส่วนตัว มันได้ผล.
ทิม

คำตอบนี้ควรมีการโหวตเพิ่มขึ้นซึ่งเป็นวิธีที่ง่ายที่สุดในการทดสอบวิธีการส่วนตัว
สูงสุด

9

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

ฉันใช้การฉีดพึ่งพาที่เหมาะสมหรือไม่? ฉันอาจจำเป็นต้องย้ายเมธอดส่วนตัวไปยังคลาสแยกต่างหากและทำการทดสอบหรือไม่ วิธีการเหล่านี้ต้องเป็นแบบส่วนตัวหรือไม่? ... ไม่สามารถผิดนัดหรือป้องกันได้หรือ

ในตัวอย่างข้างต้นวิธีการทั้งสองที่เรียกว่า "สุ่ม" อาจจำเป็นต้องวางไว้ในคลาสของตนเองทดสอบแล้วจึงฉีดเข้าไปในคลาสด้านบน


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

2
สังเกต supertonsky ฉันหมายถึงกรณีทั่วไป ฉันยอมรับว่าในกรณีข้างต้นไม่ควรอยู่ในชั้นเรียนแยกต่างหาก (+1 ในความคิดเห็นของคุณ - มันเป็นจุดที่ถูกต้องมากที่คุณทำในการโปรโมตสมาชิกส่วนตัว)
Jaco Van Niekerk

4
@supertonsky ฉันไม่พบคำตอบที่น่าพอใจสำหรับปัญหานี้ มีสาเหตุหลายประการที่ฉันอาจใช้สมาชิกส่วนตัวและบ่อยครั้งที่พวกเขาไม่ได้ระบุกลิ่นรหัสและฉันจะได้รับประโยชน์อย่างมากจากการทดสอบพวกเขา ดูเหมือนผู้คนจะปัดเรื่องนี้โดยพูดว่า "อย่าทำ"
LuddyPants

3
ขออภัยฉันเลือกที่จะลดคะแนนโดยอิงจาก "หากคุณต้องการ" ทดสอบวิธีการส่วนตัวอาจเป็นการระบุว่าคุณต้องลดคะแนนการออกแบบของคุณ " โอเคยุติธรรมเพียงพอ แต่เหตุผลประการหนึ่งที่ต้องทำการทดสอบเลยก็คือเนื่องจากภายใต้กำหนดเวลาที่คุณไม่มีเวลาคิดทบทวนการออกแบบใหม่คุณกำลังพยายามใช้การเปลี่ยนแปลงอย่างปลอดภัยที่จะต้องดำเนินการกับ วิธีการส่วนตัว ในโลกแห่งอุดมคตินั้นไม่จำเป็นต้องเปลี่ยนวิธีการส่วนตัวเพราะการออกแบบจะสมบูรณ์แบบหรือไม่? แน่นอนว่า แต่ในโลกที่สมบูรณ์แบบ แต่มันเป็นเรื่องที่น่าสงสัยเพราะในโลกที่สมบูรณ์แบบที่ต้องการการทดสอบทุกอย่างก็ใช้ได้ :)
John Lockwood

2
@จอห์น. คะแนนที่ได้รับการรับรองการโหวตของคุณ (+1) ขอบคุณสำหรับความคิดเห็นเช่นกัน - ฉันเห็นด้วยกับคุณในประเด็นที่คุณทำ ในกรณีเช่นนี้ฉันสามารถเห็นตัวเลือกหนึ่งในสองตัวเลือก: วิธีนี้จะสร้างเป็นแพ็คเกจส่วนตัวหรือมีการป้องกันและการทดสอบหน่วยที่เขียนตามปกติ หรือ (และนี่คือการแลบลิ้นและการปฏิบัติที่ไม่ดี) มีการเขียนวิธีการหลักอย่างรวดเร็วเพื่อให้แน่ใจว่ายังใช้งานได้ อย่างไรก็ตามคำตอบของฉันเป็นไปตามสถานการณ์ของโค้ดใหม่ที่เขียนขึ้นและไม่ได้ปรับโครงสร้างใหม่เมื่อคุณอาจไม่สามารถเปลี่ยนแปลงการออกแบบเดิมได้
Jaco Van Niekerk

6

ฉันสามารถทดสอบวิธีการส่วนตัวภายในโดยใช้ม็อกโตโต้โดยใช้การสะท้อน นี่คือตัวอย่างพยายามตั้งชื่อให้มันเข้าท่า

//Service containing the mock method is injected with mockObjects

@InjectMocks
private ServiceContainingPrivateMethod serviceContainingPrivateMethod;

//Using reflection to change accessibility of the private method

Class<?>[] params = new Class<?>[]{PrivateMethodParameterOne.class, PrivateMethodParameterTwo.class};
    Method m = serviceContainingPrivateMethod .getClass().getDeclaredMethod("privateMethod", params);
    //making private method accessible
    m.setAccessible(true); 
    assertNotNull(m.invoke(serviceContainingPrivateMethod, privateMethodParameterOne, privateMethodParameterTwo).equals(null));

6
  1. โดยการใช้การสะท้อนสามารถเรียกวิธีส่วนตัวจากชั้นเรียนทดสอบ ในกรณีนี้,

    // วิธีทดสอบจะเป็นแบบนี้ ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        // invoke the private method for test
        privateMethod.invoke(A, null);
    
        }
    }
  2. หากเมธอดส่วนตัวเรียกใช้เมธอดส่วนตัวอื่น ๆ เราจำเป็นต้องสอดแนมวัตถุและขีดฆ่าวิธีอื่นคลาสทดสอบจะเป็นเช่น ...

    // วิธีทดสอบจะเป็นแบบนี้ ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        A spyA = spy(a);
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        doReturn("Test").when(spyA, "method2"); // if private method2 is returning string data
        // invoke the private method for test
        privateMethod.invoke(spyA , null);
    
        }
    }

** แนวทางคือการรวมการสะท้อนและการสอดแนมวัตถุ ** method1 และ ** method2 เป็นเมธอดส่วนตัวและ method1 เรียก method2


4

ฉันไม่เข้าใจความจำเป็นของคุณในการทดสอบวิธีส่วนตัว ปัญหาหลักคือวิธีสาธารณะของคุณมีโมฆะเป็นประเภทการส่งคืนดังนั้นคุณจึงไม่สามารถทดสอบวิธีสาธารณะของคุณได้ ดังนั้นคุณถูกบังคับให้ทดสอบวิธีการส่วนตัวของคุณ ฉันเดาถูกหรือเปล่า ??

วิธีแก้ปัญหาที่เป็นไปได้บางประการ (AFAIK):

  1. ล้อเลียนวิธีการส่วนตัวของคุณ แต่คุณก็ยังไม่ "ทดสอบวิธีการของคุณ" จริง

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

    public class A{
    
    SomeClass classObj = null;
    
    public void publicMethod(){
       privateMethod();
    }
    
    private void privateMethod(){
         classObj = new SomeClass();
    }
    
    }

    [ที่นี่คุณสามารถทดสอบเมธอดส่วนตัวได้โดยตรวจสอบการเปลี่ยนสถานะของ classObj จาก null เป็น null]

  3. Refactor รหัสของคุณเล็กน้อย (หวังว่านี่จะไม่ใช่รหัสเดิม) ปัจจัยในการเขียนวิธีการของฉันคือเราควรส่งคืนบางสิ่ง (int / a boolean) ค่าที่ส่งคืนอาจหรืออาจจะไม่ถูกใช้โดยการใช้งาน แต่จะถูกใช้โดยการทดสอบอย่างแน่นอน

    รหัส.

    public class A
    { 
        public int method(boolean b)
        {
              int nReturn = 0;
              if (b == true)
                   nReturn = method1();
              else
                   nReturn = method2();
        }
    
        private int method1() {}
    
        private int method2() {}
    
    }

3

มีวิธีทดสอบวิธีจากสมาชิกส่วนตัวด้วย Mockito จริงๆ สมมติว่าคุณมีคลาสดังนี้:

public class A {
    private SomeOtherClass someOtherClass;
    A() {
        someOtherClass = new SomeOtherClass();
    }
    public void method(boolean b){
        if (b == true)
            someOtherClass.method1();
        else
            someOtherClass.method2();
    }

}

public class SomeOtherClass {
    public void method1() {}
    public void method2() {}
}

หากคุณต้องการทดสอบa.methodจะเรียกใช้วิธีการจากSomeOtherClassคุณสามารถเขียนสิ่งที่ต้องการด้านล่าง

@Test
public void testPrivateMemberMethodCalled() {
    A a = new A();
    SomeOtherClass someOtherClass = Mockito.spy(new SomeOtherClass());
    ReflectionTestUtils.setField( a, "someOtherClass", someOtherClass);
    a.method( true );

    Mockito.verify( someOtherClass, Mockito.times( 1 ) ).method1();
}

ReflectionTestUtils.setField(); จะขัดขวางสมาชิกส่วนตัวด้วยสิ่งที่คุณสามารถสอดแนมได้


2

ใส่การทดสอบของคุณในแพ็คเกจเดียวกัน แต่เป็นโฟลเดอร์ซอร์สที่แตกต่างกัน (src / main / java เทียบกับ src / test / java) และทำให้วิธีการเหล่านั้นเป็นแพ็กเกจส่วนตัว ความสามารถในการทดสอบ Imo สำคัญกว่าความเป็นส่วนตัว


6
เหตุผลเดียวที่ถูกต้องคือการทดสอบส่วนหนึ่งของระบบเดิม หากคุณเริ่มทดสอบเมธอด private / package-private คุณจะเปิดเผยอ็อบเจ็กต์ของคุณภายใน การทำเช่นนี้มักจะส่งผลให้โค้ดที่เปลี่ยนสภาพได้ไม่ดี PRefer องค์ประกอบเพื่อให้คุณสามารถทดสอบได้ด้วยความดีทั้งหมดของระบบเชิงวัตถุ
Brice

1
ตกลง - นั่นเป็นวิธีที่ต้องการ อย่างไรก็ตามหากคุณต้องการทดสอบวิธีการส่วนตัวด้วยม็อกโตะจริงๆนี่เป็นตัวเลือกเดียว (typesafe) ที่คุณมี คำตอบของฉันค่อนข้างรีบร้อนฉันควรชี้ให้เห็นถึงความเสี่ยงเช่นเดียวกับที่คุณและคนอื่น ๆ เคยทำ
Roland Schneider

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

0

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

ArgumentCaptor<ByteArrayOutputStream> csvOutputCaptor = ArgumentCaptor.forClass(ByteArrayOutputStream.class);
//Do your thing..
verify(this.awsService).uploadFile(csvOutputCaptor.capture());
....
assertEquals(csvOutputCaptor.getValue().toString(), "blabla");
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.