จะเพิ่มความครอบคลุมการทดสอบให้กับตัวสร้างส่วนตัวได้อย่างไร?


110

นี่คือรหัส:

package com.XXX;
public final class Foo {
  private Foo() {
    // intentionally empty
  }
  public static int bar() {
    return 1;
  }
}

นี่คือการทดสอบ:

package com.XXX;
public FooTest {
  @Test 
  void testValidatesThatBarWorks() {
    int result = Foo.bar();
    assertEquals(1, result);
  }
  @Test(expected = java.lang.IllegalAccessException.class)
  void testValidatesThatClassFooIsNotInstantiable() {
    Class cls = Class.forName("com.XXX.Foo");
    cls.newInstance(); // exception here
  }
}

ใช้งานได้ดีชั้นเรียนได้รับการทดสอบแล้ว แต่ Cobertura กล่าวว่าไม่มีการครอบคลุมโค้ดของตัวสร้างส่วนตัวของคลาส เราจะเพิ่มความครอบคลุมการทดสอบให้กับคอนสตรัคเตอร์ส่วนตัวได้อย่างไร?


สำหรับฉันดูเหมือนว่าคุณกำลังพยายามบังคับใช้รูปแบบซิงเกิลตัน ถ้าเป็นเช่นนั้นคุณอาจชอบ dp4j.com (ซึ่งทำอย่างนั้น)
simpatico

ไม่ควร "ว่างเปล่าโดยเจตนา" แทนที่ด้วยข้อยกเว้นการขว้าง? ในกรณีนี้คุณสามารถเขียนการทดสอบที่คาดว่าจะมีข้อยกเว้นที่เฉพาะเจาะจงกับข้อความเฉพาะไม่? ไม่แน่ใจว่านี่คือ overkill
Ewoks

คำตอบ:


85

มีหลายวิธีที่คุณสามารถใช้การสะท้อนและอื่น ๆ ได้ แต่มันคุ้มค่าจริงหรือ? นี่คือคอนสตรัคเตอร์ที่ไม่ควรถูกเรียกใช่ไหม?

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

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


18
ฉันไม่ต้องการ "ลดความครอบคลุมเล็กน้อย" ในโครงการทั้งหมดเพียงเพราะตัวสร้างนี้โดยเฉพาะ ..
yegor256

36
@ Vincenzo: จากนั้น IMO คุณวางค่าไว้สูงเกินไปสำหรับตัวเลขง่ายๆ ความครอบคลุมเป็นตัวบ่งชี้การทดสอบ อย่าตกเป็นทาสของเครื่องมือ ประเด็นที่ครอบคลุมคือการให้ระดับความมั่นใจและแนะนำพื้นที่สำหรับการทดสอบเพิ่มเติม การเรียกตัวสร้างที่ไม่ได้ใช้อย่างเทียมไม่ได้ช่วยในจุดใดจุดหนึ่ง
Jon Skeet

19
@ JonSkeet: ฉันเห็นด้วยอย่างยิ่งกับ "อย่าตกเป็นทาสของเครื่องมือ" แต่มันก็ไม่ได้ดีที่จะจำทุก "จำนวนข้อบกพร่อง" ในทุกโครงการ จะแน่ใจได้อย่างไรว่าผลลัพธ์ 7/9 เป็นข้อ จำกัด ของ Cobertura ไม่ใช่ของโปรแกรมเมอร์ โปรแกรมเมอร์ใหม่จะต้องป้อนทุกความล้มเหลว (ซึ่งอาจมีมากในโครงการขนาดใหญ่) เพื่อตรวจสอบทีละชั้น
Eduardo Costa

5
สิ่งนี้ไม่ตอบคำถาม และโดยวิธีการที่ผู้จัดการบางคนดูที่ตัวเลขความครอบคลุม พวกเขาไม่สนใจว่าทำไม พวกเขารู้ว่า 85% ดีกว่า 75%
ACV

2
กรณีการใช้งานจริงในการทดสอบรหัสที่ไม่สามารถเข้าถึงได้คือการบรรลุความครอบคลุมการทดสอบ 100% เพื่อที่จะไม่มีใครต้องมองไปที่คลาสนั้นอีก หากความครอบคลุมติดอยู่ที่ 95% นักพัฒนาหลายคนอาจพยายามหาสาเหตุที่ทำให้เกิดปัญหานี้ซ้ำแล้วซ้ำเล่า
thisismydesign

140

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

@Test
public void testConstructorIsPrivate() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  Constructor<Foo> constructor = Foo.class.getDeclaredConstructor();
  assertTrue(Modifier.isPrivate(constructor.getModifiers()));
  constructor.setAccessible(true);
  constructor.newInstance();
}

25
แต่นี่เป็นการกำจัดเสียงรบกวนในรายงานความครอบคลุมโดยการเพิ่มเสียงรบกวนลงในชุดทดสอบ ผมคงจบประโยคที่ "วางอุดมคติไว้เฉยๆ" :)
Christopher Orr

11
หากต้องการให้การทดสอบนี้มีความหมายคุณควรยืนยันด้วยว่าระดับการเข้าถึงของตัวสร้างคือสิ่งที่คุณคาดหวังไว้
Jeremy

การเพิ่มการสะท้อนความชั่วร้ายบวกกับแนวคิดของ Jeremy และชื่อที่มีความหมายเช่น "testIfConstructorIsPrivateWithoutRaisingExceptions" ฉันเดาว่านี่คือคำตอบ "THE"
Eduardo Costa

1
สิ่งนี้ไม่ถูกต้องทางไวยากรณ์ใช่หรือไม่? คืออะไรconstructor? ไม่ควรConstructorกำหนดพารามิเตอร์และไม่ใช่ประเภทดิบ?
Adam Parkin

2
สิ่งนี้ผิด: constructor.isAccessible()ส่งคืนเท็จเสมอแม้ในตัวสร้างสาธารณะ assertTrue(Modifier.isPrivate(constructor.getModifiers()));หนึ่งควรใช้
timomeinen

78

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

/**
 * Verifies that a utility class is well defined.
 * 
 * @param clazz
 *            utility class to verify.
 */
public static void assertUtilityClassWellDefined(final Class<?> clazz)
        throws NoSuchMethodException, InvocationTargetException,
        InstantiationException, IllegalAccessException {
    Assert.assertTrue("class must be final",
            Modifier.isFinal(clazz.getModifiers()));
    Assert.assertEquals("There must be only one constructor", 1,
            clazz.getDeclaredConstructors().length);
    final Constructor<?> constructor = clazz.getDeclaredConstructor();
    if (constructor.isAccessible() || 
                !Modifier.isPrivate(constructor.getModifiers())) {
        Assert.fail("constructor is not private");
    }
    constructor.setAccessible(true);
    constructor.newInstance();
    constructor.setAccessible(false);
    for (final Method method : clazz.getMethods()) {
        if (!Modifier.isStatic(method.getModifiers())
                && method.getDeclaringClass().equals(clazz)) {
            Assert.fail("there exists a non-static method:" + method);
        }
    }
}

ฉันได้วางโค้ดและตัวอย่างทั้งหมดไว้ในhttps://github.com/trajano/maven-jee6/tree/master/maven-jee6-test


11
+1 ไม่เพียงช่วยแก้ปัญหาโดยไม่ต้องหลอกล่อเครื่องมือ แต่ยังทดสอบมาตรฐานการเข้ารหัสของการตั้งค่าคลาสยูทิลิตี้ ฉันต้องเปลี่ยนการทดสอบความสามารถในการเข้าถึงเพื่อใช้Modifier.isPrivateตามที่isAccessibleส่งคืนtrueสำหรับคอนสตรัคเตอร์ส่วนตัวในบางกรณี (ล้อเลียนการรบกวนห้องสมุด?)
David Harkness

4
ฉันต้องการเพิ่มสิ่งนี้ในคลาส Assert ของ JUnit แต่ไม่ต้องการให้เครดิตกับงานของคุณ เราว่ามันดีมาก มันจะดีมากถ้ามีAssert.utilityClassWellDefined()ใน JUnit 4.12+ คุณได้พิจารณาคำขอดึงหรือไม่?
Visionary Software Solutions

โปรดทราบว่าการใช้setAccessible()เพื่อทำให้ตัวสร้างสามารถเข้าถึงได้ทำให้เกิดปัญหากับเครื่องมือครอบคลุมโค้ดของ Sonar (เมื่อฉันทำสิ่งนี้คลาสจะหายไปจากรายงานการครอบคลุมโค้ดของ Sonar)
Adam Parkin

ขอบคุณฉันรีเซ็ตค่าสถานะที่สามารถเข้าถึงได้ บางทีอาจเป็นข้อผิดพลาดของ Sonar เอง?
Archimedes Trajano

ฉันดูรายงานโซนาร์ของฉันสำหรับการรายงานเกี่ยวกับปลั๊กอินบาติกมาเวนของฉันดูเหมือนว่าจะครอบคลุมอย่างถูกต้อง site.trajano.net/batik-maven-plugin/cobertura/index.html
Archimedes Trajano

19

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

@Test(expected=IllegalAccessException.class)
public void testConstructorPrivate() throws Exception {
    MyUtilityClass.class.newInstance();
    fail("Utility class constructor should be private");
}

ฉันไปตามคำแนะนำของ Javid Jamae และใช้การไตร่ตรอง แต่เพิ่มการยืนยันเพื่อจับใครก็ตามที่ยุ่งกับชั้นเรียนที่กำลังทดสอบ (และตั้งชื่อการทดสอบเพื่อระบุระดับความชั่วร้ายระดับสูง)

@Test
public void evilConstructorInaccessibilityTest() throws Exception {
    Constructor[] ctors = MyUtilityClass.class.getDeclaredConstructors();
    assertEquals("Utility class should only have one constructor",
            1, ctors.length);
    Constructor ctor = ctors[0];
    assertFalse("Utility class constructor should be inaccessible", 
            ctor.isAccessible());
    ctor.setAccessible(true); // obviously we'd never do this in production
    assertEquals("You'd expect the construct to return the expected type",
            MyUtilityClass.class, ctor.newInstance().getClass());
}

นี่เป็นเรื่องที่มากเกินไป แต่ฉันต้องยอมรับว่าฉันชอบความรู้สึกคลุมเครือที่อบอุ่นของการครอบคลุมวิธีการ 100%


มันอาจจะมากเกินไป แต่ถ้าอยู่ใน Unitils หรือคล้ายกันฉันจะใช้มัน
Stewart

1 เริ่มต้นที่ดี แต่ผมไปกับการทดสอบที่สมบูรณ์มากขึ้นของ Archimedes
David Harkness

ตัวอย่างแรกไม่ได้ผล - IllegalAccesException หมายถึงตัวสร้างจะไม่ถูกเรียกดังนั้นจึงไม่มีการบันทึกความครอบคลุม
Tom McIntyre

IMO วิธีแก้ปัญหาในข้อมูลโค้ดแรกเป็นวิธีที่สะอาดที่สุดและง่ายที่สุดในการสนทนานี้ เพียงแค่บรรทัดด้วยfail(...)ไม่จำเป็น
Piotr Wittchen

9

ด้วยJava 8คุณสามารถค้นหาโซลูชันอื่น ๆ

ฉันคิดว่าคุณแค่ต้องการสร้างคลาสยูทิลิตี้ด้วยวิธีการแบบคงที่สาธารณะ หากคุณสามารถใช้ Java 8 คุณสามารถใช้interfaceแทนได้

package com.XXX;

public interface Foo {

  public static int bar() {
    return 1;
  }
}

ไม่มีผู้สร้างและไม่มีคำบ่นจาก Cobertura ตอนนี้คุณต้องทดสอบเฉพาะสายงานที่คุณสนใจจริงๆ


1
อย่างไรก็ตามน่าเสียดายที่คุณไม่สามารถประกาศให้อินเทอร์เฟซเป็น "ขั้นสุดท้าย" ได้ซึ่งจะป้องกันไม่ให้ทุกคนเข้าร่วมคลาสย่อยมิฉะนั้นจะเป็นแนวทางที่ดีที่สุด
Michael Berry

5

เหตุผลที่อยู่เบื้องหลังรหัสทดสอบที่ไม่ได้ทำอะไรเลยคือเพื่อให้ได้ความครอบคลุมของรหัส 100% และสังเกตเห็นเมื่อความครอบคลุมของรหัสลดลง ไม่อย่างนั้นใคร ๆ ก็มักจะคิดว่าเฮ้ฉันไม่มีรหัสครอบคลุม 100% อีกต่อไป แต่เป็นไปได้ว่าเป็นเพราะตัวสร้างส่วนตัวของฉัน ทำให้ง่ายต่อการมองเห็นวิธีการที่ยังไม่ได้ทดลองโดยไม่ต้องตรวจสอบว่าเป็นเพียงตัวสร้างส่วนตัว เมื่อโค้ดเบสของคุณเติบโตขึ้นคุณจะรู้สึกอบอุ่นที่ได้มอง 100% แทนที่จะเป็น 99%

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

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

    @Test
    public void callPrivateConstructorsForCodeCoverage() throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException
    {
        Class<?>[] classesToConstruct = {Foo.class};
        for(Class<?> clazz : classesToConstruct)
        {
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            assertNotNull(constructor.newInstance());
        }
    }
จากนั้นเพิ่มคลาสลงในอาร์เรย์เมื่อคุณไป


5

Cobertura เวอร์ชันใหม่กว่ามีการสนับสนุนในตัวสำหรับการละเว้น getters / setters / constructor เล็กน้อย:

https://github.com/cobertura/cobertura/wiki/Ant-Task-Reference#ignore-trivial

ละเว้นเรื่องเล็กน้อย

การละเว้นเล็กน้อยช่วยให้สามารถยกเว้นตัวสร้าง / วิธีการที่มีโค้ดหนึ่งบรรทัด ตัวอย่างบางส่วน ได้แก่ การเรียกไปยัง super constrctor เท่านั้นวิธี getter / setter เป็นต้นหากต้องการรวมอาร์กิวเมนต์ที่ไม่สำคัญให้เพิ่มสิ่งต่อไปนี้:

<cobertura-instrument ignoreTrivial="true" />

หรือใน Gradle build:

cobertura {
    coverageIgnoreTrivial = true
}

4

อย่า. อะไรคือประเด็นในการทดสอบตัวสร้างที่ว่างเปล่า? เนื่องจาก cobertura 2.0 มีตัวเลือกในการละเว้นกรณีที่ไม่สำคัญดังกล่าว (ร่วมกับ setters / getters) คุณสามารถเปิดใช้งานใน maven ได้โดยเพิ่มส่วนการกำหนดค่าลงในปลั๊กอิน cobertura maven:

<configuration>
  <instrumentation>
    <ignoreTrivial>true</ignoreTrivial>                 
  </instrumentation>
</configuration>

หรือคุณสามารถใช้ครอบคลุมคำอธิบายประกอบ@CoverageIgnore :


3

ในที่สุดก็มีทางออก!

public enum Foo {;
  public static int bar() {
    return 1;
  }
}

การทดสอบคลาสที่โพสต์ในคำถามนั้นเป็นอย่างไร คุณไม่ควรคิดว่าคุณสามารถเปลี่ยนทุกคลาสที่มีคอนสตรัคเตอร์ส่วนตัวให้เป็น enum หรือที่คุณต้องการ
Jon Skeet

@JonSkeet ฉันทำได้สำหรับชั้นเรียนที่มีปัญหา และคลาสยูทิลิตี้ส่วนใหญ่ที่มีเมธอดคงที่เท่านั้น มิฉะนั้นชั้นเรียนที่มีตัวสร้างส่วนตัวเพียงคนเดียวจะไม่มีความรู้สึกใด ๆ
kan

1
คลาสที่มีคอนสตรัคเตอร์ส่วนตัวอาจถูกสร้างอินสแตนซ์จากวิธีการแบบคงที่สาธารณะแม้ว่าจะเป็นเรื่องง่ายที่จะได้รับความครอบคลุม แต่โดยพื้นฐานแล้วฉันจะชอบคลาสใดก็ได้ที่ขยายEnum<E>ไปเป็น enum จริงๆ ... ฉันเชื่อว่าแสดงเจตนาได้ดีกว่า
Jon Skeet

4
ว้าวฉันอย่างต้องการรหัสซึ่งจะทำให้ความรู้สึกมากกว่าจำนวนพลสวย (ความครอบคลุมไม่ได้รับประกันคุณภาพและไม่สามารถครอบคลุมได้ 100% ในทุกกรณีการทดสอบของคุณควรแนะนำโค้ดของคุณอย่างดีที่สุด - อย่าให้ข้ามหน้าผาแห่งความตั้งใจที่แปลกประหลาด)
Jon Skeet

1
@Kan: การเพิ่มการเรียกหลอกให้กับตัวสร้างเพื่อบลัฟเครื่องมือไม่ควรเป็นเจตนา ใครก็ตามที่อาศัยเมตริกเดียวในการพิจารณาคุณภาพชีวิตของโครงการก็อยู่บนเส้นทางสู่การทำลายล้างแล้ว
Apoorv Khurasia

1

ฉันไม่รู้เกี่ยวกับ Cobertura แต่ฉันใช้ Clover และมีวิธีการเพิ่มการยกเว้นการจับคู่รูปแบบ ตัวอย่างเช่นฉันมีรูปแบบที่ยกเว้นบรรทัดการบันทึก apache-commons ดังนั้นจึงไม่ถูกนับรวมในความครอบคลุม


1

อีกทางเลือกหนึ่งคือการสร้างตัวเริ่มต้นแบบคงที่คล้ายกับรหัสต่อไปนี้

class YourClass {
  private YourClass() {
  }
  static {
     new YourClass();
  }

  // real ops
}

วิธีนี้ถือว่าคอนสตรัคเตอร์ส่วนตัวได้รับการทดสอบแล้วและค่าใช้จ่ายรันไทม์นั้นไม่สามารถวัดได้โดยทั่วไป ฉันทำสิ่งนี้เพื่อให้ได้รับความคุ้มครอง 100% โดยใช้ EclEmma แต่ก็น่าจะใช้ได้กับทุกเครื่องมือการครอบคลุม ข้อเสียเปรียบของโซลูชันนี้คือคุณเขียนโค้ดการผลิต (ตัวเริ่มต้นแบบคงที่) เพื่อวัตถุประสงค์ในการทดสอบเท่านั้น


ฉันทำสิ่งนี้ไม่น้อย ถูกในราคาไม่แพงราคาถูกเหมือนสกปรก แต่มีประสิทธิภาพ
pholser

ด้วย Sonar สิ่งนี้ทำให้ชั้นเรียนพลาดไปโดยสิ้นเชิงจากการครอบคลุมของรหัส
Adam Parkin

1

ClassUnderTest testClass = Whitebox.invokeConstructor (ClassUnderTest.class);


นี่น่าจะเป็นคำตอบที่ถูกต้องเนื่องจากตอบได้ตรงกับสิ่งที่ถาม
Chakian

0

บางครั้ง Cobertura ทำเครื่องหมายรหัสที่ไม่ได้ตั้งใจให้ดำเนินการว่า 'ไม่ครอบคลุม' ไม่มีอะไรผิดปกติ ทำไมคุณถึงกังวลกับการมี99%ความคุ้มครองแทน100%?

ในทางเทคนิคแล้วคุณยังสามารถเรียกตัวสร้างนั้นด้วยการสะท้อนกลับได้ แต่ฟังดูผิดมากสำหรับฉัน (ในกรณีนี้)


0

ถ้าฉันจะเดาจุดประสงค์ของคำถามของคุณฉันจะพูดว่า:

  1. คุณต้องการการตรวจสอบที่สมเหตุสมผลสำหรับผู้ก่อสร้างส่วนตัวที่ทำงานจริงและ
  2. คุณต้องการให้โคลเวอร์ยกเว้นคอนสตรัคเตอร์ว่างสำหรับคลาส util

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

ตัวอย่างเช่นถ้าฉันชุด [ส่วนตัว] คอนสตรัคขึ้นชั้นเรียนของฉันทุ่งอินสแตนซ์ที่จะa 5จากนั้นฉันสามารถ (หรือต้อง) ทดสอบ:

@Test
public void testInit() {
    MyClass myObj = MyClass.newInstance(); //Or whatever factory method you put
    Assert.assertEquals(5, myObj.getA()); //Or if getA() is private then test some other property/method that relies on a being 5
}

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

<clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
    <methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+Util *( *) *"/>
</clover-setup>

ฉันจงใจทิ้งสิ่ง.*ต่อไปนี้)เนื่องจากตัวสร้างดังกล่าวไม่ได้ตั้งใจที่จะทิ้งข้อยกเว้น (ไม่ได้ตั้งใจที่จะทำอะไรเลย)

แน่นอนว่าอาจมีกรณีที่สามที่คุณอาจต้องการมีตัวสร้างว่างสำหรับคลาสที่ไม่ใช่ยูทิลิตี้ ในกรณีเช่นนี้ฉันขอแนะนำให้คุณใส่methodContextลายเซ็นที่แน่นอนของตัวสร้าง

<clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
    <methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+Util *( *) *"/>
    <methodContext name="myExceptionalClassCtor" regexp="^private MyExceptionalClass()$"/>
</clover-setup>

หากคุณมีคลาสพิเศษมากมายเช่นนี้คุณสามารถเลือกที่จะแก้ไขตัวสร้างส่วนตัวทั่วไป reg-ex ที่ฉันแนะนำและลบออกUtilจากมัน ในกรณีนี้คุณจะต้องตรวจสอบให้แน่ใจว่าผลข้างเคียงของคอนสตรัคเตอร์ของคุณยังคงได้รับการทดสอบและครอบคลุมโดยวิธีการอื่น ๆ ในคลาส / โครงการของคุณ

<clover-setup initString="${build.dir}/clovercoverage.db" enabled="${with.clover}">
    <methodContext name="prvtCtor" regexp="^private *[a-zA-Z0-9_$]+ *( *) .*"/>
</clover-setup>

0
@Test
public void testTestPrivateConstructor() {
    Constructor<Test> cnt;
    try {
        cnt = Test.class.getDeclaredConstructor();
        cnt.setAccessible(true);

        cnt.newInstance();
    } catch (Exception e) {
        e.getMessage();
    }
}

Test.java เป็นไฟล์ต้นฉบับของคุณซึ่งมีตัวสร้างส่วนตัวของคุณ


มันจะเป็นการดีที่จะอธิบายว่าทำไมโครงสร้างนี้จึงช่วยในเรื่องการรายงานข่าว
Markus

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

0

สิ่งต่อไปนี้ใช้ได้ผลกับฉันในคลาสที่สร้างด้วยคำอธิบายประกอบ Lombok @UtilityClass ซึ่งจะเพิ่มตัวสร้างส่วนตัวโดยอัตโนมัติ

@Test
public void testConstructorIsPrivate() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
    Constructor<YOUR_CLASS_NAME> constructor = YOUR_CLASS_NAME.class.getDeclaredConstructor();
    assertTrue(Modifier.isPrivate(constructor.getModifiers())); //this tests that the constructor is private
    constructor.setAccessible(true);
    assertThrows(InvocationTargetException.class, () -> {
        constructor.newInstance();
    }); //this add the full coverage on private constructor
}

แม้ว่า constructor.setAccessible (true) ควรทำงานเมื่อตัวสร้างส่วนตัวถูกเขียนด้วยตนเองโดยที่คำอธิบายประกอบ Lombok ไม่ทำงานเนื่องจากมันบังคับ Constructor.newInstance () ทดสอบจริงว่า constructor ถูกเรียกใช้และนี่เป็นการครอบคลุมของ costructor เอง ด้วย assertThrows คุณป้องกันไม่ให้การทดสอบล้มเหลวและคุณจัดการข้อยกเว้นเนื่องจากเป็นข้อผิดพลาดที่คุณคาดหวัง แม้ว่านี่จะเป็นวิธีแก้ปัญหาชั่วคราวและฉันไม่ชอบแนวคิดของ "การครอบคลุมบรรทัด" เทียบกับ "ความครอบคลุมของฟังก์ชัน / พฤติกรรม" เราสามารถพบความเข้าใจในการทดสอบนี้ ในความเป็นจริงคุณแน่ใจว่า Utility Class มีตัวสร้างส่วนตัวที่แสดงข้อยกเว้นอย่างถูกต้องเมื่อเรียกใช้ผ่าน reflaction หวังว่านี่จะช่วยได้


สวัสดี @ShanteshwarInde ขอบคุณมาก. ข้อมูลของฉันได้รับการแก้ไขและทำตามคำแนะนำของคุณ ความนับถือ.
Riccardo Solimena

0

ตัวเลือกที่ฉันชอบในปี 2019: ใช้ลอมบอก

โดยเฉพาะคำอธิบายประกอบ@UtilityClass (น่าเสียดายที่เป็นเพียง "การทดลอง" ในขณะที่เขียน แต่มันทำงานได้ดีและมีมุมมองเชิงบวกดังนั้นในไม่ช้าจะได้รับการอัปเกรดเป็นแบบเสถียร)

คำอธิบายประกอบนี้จะเพิ่มตัวสร้างส่วนตัวเพื่อป้องกันการสร้างอินสแตนซ์และจะทำให้คลาสสิ้นสุด เมื่อรวมกับlombok.addLombokGeneratedAnnotation = trueในlombok.configกรอบการทดสอบทั้งหมดจะไม่สนใจโค้ดที่สร้างขึ้นโดยอัตโนมัติเมื่อคำนวณความครอบคลุมการทดสอบทำให้คุณสามารถข้ามความครอบคลุมของโค้ดที่สร้างขึ้นโดยอัตโนมัติโดยไม่มีการแฮ็กหรือการสะท้อนกลับ


-2

คุณทำไม่ได้

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


3
ไม่ถูกต้อง คุณสามารถสร้างอินสแตนซ์ผ่านการสะท้อนตามที่ระบุไว้ข้างต้น
theotherian

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