سؤال

شعاري لجافا هو "لمجرد أن جافا لديها كتل ثابتة ، فهذا لا يعني أنك يجب أن تستخدمها." بنك جانبا ، هناك الكثير من الحيل في جافا التي تجعل اختبار كابوس.أكثر ما أكرهه هما الفئات المجهولة والكتل الثابتة.لدينا الكثير من التعليمات البرمجية القديمة التي تستخدم الكتل الثابتة وهذه إحدى النقاط المزعجة في اختبارات وحدة الكتابة لدينا.هدفنا هو أن نكون قادرين على كتابة اختبارات الوحدة للفئات التي تعتمد على هذه التهيئة الثابتة مع الحد الأدنى من تغييرات التعليمات البرمجية.

اقتراحي لزملائي حتى الآن هو نقل جسم الكتلة الثابتة إلى طريقة ثابتة خاصة واستدعائها staticInit.يمكن بعد ذلك استدعاء هذه الطريقة من داخل الكتلة الثابتة.بالنسبة لاختبار الوحدة، يمكن بسهولة الاستهزاء بفئة أخرى تعتمد على هذه الفئة staticInit مع JMockit لعدم القيام بأي شيء.دعونا نرى هذا في المثال.

public class ClassWithStaticInit {
  static {
    System.out.println("static initializer.");
  }
}

سيتم التغيير إلى

public class ClassWithStaticInit {
  static {
    staticInit();
  }

  private static void staticInit() {
    System.out.println("static initialized.");
  }
}

حتى نتمكن من القيام بما يلي في JUnit.

public class DependentClassTest {
  public static class MockClassWithStaticInit {
    public static void staticInit() {
    }
  }

  @BeforeClass
  public static void setUpBeforeClass() {
    Mockit.redefineMethods(ClassWithStaticInit.class, MockClassWithStaticInit.class);
  }
}

ولكن هذا الحل يأتي أيضا مع مشاكله الخاصة.لا يمكنك الركض DependentClassTest و ClassWithStaticInitTest على نفس JVM لأنك تريد بالفعل تشغيل الكتلة الثابتة ClassWithStaticInitTest.

ما هي طريقتك لإنجاز هذه المهمة؟أو أي حلول أفضل غير مستندة إلى JMockit تعتقد أنها ستعمل بشكل أكثر نظافة؟

هل كانت مفيدة؟

المحلول

عندما أواجه هذه المشكلة، عادةً ما أفعل نفس الشيء الذي وصفته، باستثناء أنني أجعل الطريقة الثابتة محمية حتى أتمكن من استدعائها يدويًا.علاوة على ذلك، أتأكد من أنه يمكن استدعاء الطريقة عدة مرات دون مشاكل (وإلا فلن تكون أفضل من المُهيئ الثابت فيما يتعلق بالاختبارات).

يعمل هذا بشكل جيد إلى حد معقول، ويمكنني بالفعل اختبار ما إذا كانت طريقة التهيئة الثابتة تفعل ما أتوقعه/أريده أن يفعله.في بعض الأحيان يكون من الأسهل الحصول على رمز تهيئة ثابت، ولا يستحق الأمر بناء نظام معقد للغاية لاستبداله.

عندما أستخدم هذه الآلية، أتأكد من توثيق أن الطريقة المحمية يتم عرضها فقط لأغراض الاختبار، على أمل ألا يتم استخدامها من قبل مطورين آخرين.هذا بالطبع قد لا يكون حلاً قابلاً للتطبيق، على سبيل المثال إذا كانت واجهة الفصل مرئية خارجيًا (إما كمكون فرعي من نوع ما لفرق أخرى، أو كإطار عمل عام).إنه حل بسيط للمشكلة، ولا يتطلب إعداده مكتبة تابعة لجهة خارجية (وهو ما يعجبني).

نصائح أخرى

باور موك هو إطار عمل وهمي آخر يمتد إلى EasyMock وMockito.مع PowerMock يمكنك ذلك بسهولة إزالة السلوك غير المرغوب فيه من فئة، على سبيل المثال مُهيئ ثابت.في المثال الخاص بك، يمكنك ببساطة إضافة التعليقات التوضيحية التالية إلى حالة اختبار JUnit الخاصة بك:

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("some.package.ClassWithStaticInit")

لا يستخدم PowerMock وكيل Java وبالتالي لا يتطلب تعديل معلمات بدء تشغيل JVM.يمكنك ببساطة إضافة ملف الجرة والشروح أعلاه.

سيؤدي هذا إلى الوصول إلى المزيد من JMockit "المتقدم".اتضح أنه يمكنك إعادة تعريف كتل التهيئة الثابتة في JMockit عن طريق إنشاء ملف public void $clinit() طريقة.لذا، بدلًا من إجراء هذا التغيير

public class ClassWithStaticInit {
  static {
    staticInit();
  }

  private static void staticInit() {
    System.out.println("static initialized.");
  }
}

قد نغادر أيضًا ClassWithStaticInit كما هو وقم بما يلي في MockClassWithStaticInit:

public static class MockClassWithStaticInit {
  public void $clinit() {
  }
}

وهذا سيسمح لنا في الواقع بعدم إجراء أي تغييرات في الفئات الموجودة.

في بعض الأحيان، أجد مُهيئات ثابتة في الفئات التي يعتمد عليها الكود الخاص بي.إذا لم أتمكن من إعادة بناء الكود، سأستخدمه باور موك@SuppressStaticInitializationFor تعليق توضيحي لمنع المُهيئ الثابت:

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("com.example.ClassWithStaticInit")
public class ClassWithStaticInitTest {

    ClassWithStaticInit tested;

    @Before
    public void setUp() {
        tested = new ClassWithStaticInit();
    }

    @Test
    public void testSuppressStaticInitializer() {
        asserNotNull(tested);
    }

    // more tests...
}

اقرأ المزيد عن قمع السلوك غير المرغوب فيه.

تنصل:PowerMock هو مشروع مفتوح المصدر تم تطويره بواسطة اثنين من زملائي.

يبدو لي أنك تعالج أحد الأعراض:تصميم رديء مع تبعيات على التهيئة الثابتة.ربما بعض إعادة البناء هو الحل الحقيقي.يبدو أنك قد قمت بالفعل ببعض إعادة البناء مع ملفك staticInit() وظيفة، ولكن ربما يلزم استدعاء هذه الوظيفة من المنشئ، وليس من مُهيئ ثابت.إذا تمكنت من التخلص من فترة التهيئة الثابتة، فستكون أفضل حالًا.أنت وحدك من يستطيع اتخاذ هذا القرار (لا أستطيع رؤية قاعدة التعليمات البرمجية الخاصة بك) ولكن بعض عمليات إعادة البناء ستساعد بالتأكيد.

أما بالنسبة للسخرية، فأنا أستخدم EasyMock، لكني واجهت نفس المشكلة.الآثار الجانبية للمهيئات الثابتة في التعليمات البرمجية القديمة تجعل الاختبار صعبًا.كانت إجابتنا هي إعادة هيكلة المُهيئ الثابت.

يمكنك كتابة رمز الاختبار الخاص بك في Groovy والسخرية بسهولة من الطريقة الثابتة باستخدام البرمجة الوصفية.

Math.metaClass.'static'.max = { int a, int b -> 
    a + b
}

Math.max 1, 2

إذا لم تتمكن من استخدام Groovy، فستحتاج حقًا إلى إعادة بناء الكود (ربما لإدخال شيء مثل المُهيئ).

أطيب التحيات

أفترض أنك تريد حقًا نوعًا من المصنع بدلاً من المُهيئ الثابت.

من المحتمل أن يكون مزيجًا من المصنع الفردي والمصنع المجرد قادرًا على توفير نفس الوظيفة كما هو الحال اليوم، مع قابلية اختبار جيدة، ولكن هذا من شأنه أن يضيف الكثير من التعليمات البرمجية النموذجية، لذلك قد يكون من الأفضل مجرد محاولة إعادة البناء قم بإزالة الأشياء الثابتة تمامًا أو إذا كان بإمكانك على الأقل التخلص من بعض الحلول الأقل تعقيدًا.

من الصعب معرفة ما إذا كان ذلك ممكنًا دون رؤية الكود الخاص بك.

لست على دراية كبيرة بأطر عمل Mock، لذا يرجى تصحيحي إذا كنت مخطئًا، لكن ألا يمكن أن يكون لديك كائنان Mock مختلفان لتغطية المواقف التي ذكرتها؟مثل

public static class MockClassWithEmptyStaticInit {
  public static void staticInit() {
  }
}

و

public static class MockClassWithStaticInit {
  public static void staticInit() {
    System.out.println("static initialized.");
  }
}

ثم يمكنك استخدامها في حالات الاختبار المختلفة الخاصة بك

@BeforeClass
public static void setUpBeforeClass() {
  Mockit.redefineMethods(ClassWithStaticInit.class, 
                         MockClassWithEmptyStaticInit.class);
}

و

@BeforeClass
public static void setUpBeforeClass() {
  Mockit.redefineMethods(ClassWithStaticInit.class, 
                         MockClassWithStaticInit.class);
}

على التوالى.

ليست إجابة حقًا، ولكن فقط أتساءل - ألا توجد طريقة "لعكس" المكالمة إلى Mockit.redefineMethods?
في حالة عدم وجود مثل هذه الطريقة الواضحة، ألا ينبغي أن يؤدي تنفيذها مرة أخرى بالطريقة التالية إلى حل المشكلة؟

Mockit.redefineMethods(ClassWithStaticInit.class, ClassWithStaticInit.class);

إذا كانت هذه الطريقة موجودة، فيمكنك تنفيذها في الفصل الدراسي. @AfterClass الطريقة والاختبار ClassWithStaticInitTest مع كتلة التهيئة الثابتة "الأصلية"، كما لو لم يتغير شيء، من نفس JVM.

هذا مجرد حدس، لذا ربما أفتقد شيئًا ما.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top