كيف يمكنك التأكد من طرح استثناء معين في اختبارات JUnit 4؟

StackOverflow https://stackoverflow.com/questions/156503

سؤال

كيف يمكنني استخدام JUnit4 اصطلاحيًا لاختبار أن بعض التعليمات البرمجية تطرح استثناءً؟

بينما يمكنني بالتأكيد أن أفعل شيئًا كهذا:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  boolean thrown = false;

  try {
    foo.doStuff();
  } catch (IndexOutOfBoundsException e) {
    thrown = true;
  }

  assertTrue(thrown);
}

وأذكر أن هناك تعليقًا توضيحيًا أو Assert.xyz أو شئ ما هذا أقل تعقيدًا بكثير وأكثر ارتباطًا بروح JUnit في هذه الأنواع من المواقف.

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

المحلول

وأداة JUnit 4 ديه الدعم لهذا:

@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {
    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);
}

والمرجعي: https://junit.org/junit4/faq.html#atests_7

نصائح أخرى

يحرر الآن بعد أن تم إصدار JUnit5، فإن الخيار الأفضل هو استخدامه Assertions.assertThrows() (يرى إجابتي الأخرى).

إذا لم تقم بالترحيل إلى JUnit 5، ولكن يمكنك استخدام JUnit 4.7، فيمكنك استخدام ExpectedException قاعدة:

public class FooTest {
  @Rule
  public final ExpectedException exception = ExpectedException.none();

  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    exception.expect(IndexOutOfBoundsException.class);
    foo.doStuff();
  }
}

وهذا أفضل بكثير من @Test(expected=IndexOutOfBoundsException.class) لأن الاختبار سوف يفشل إذا IndexOutOfBoundsException يتم طرحه من قبل foo.doStuff()

يرى هذا المقال للتفاصيل

كن حذرًا عند استخدام الاستثناء المتوقع، لأنه يؤكد فقط أن طريقة ألقى هذا الاستثناء، وليس أ سطر معين من التعليمات البرمجية في الاختبار.

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

try {
    methodThatShouldThrow();
    fail( "My method didn't throw when I expected it to" );
} catch (MyException expectedException) {
}

تطبيق الحكم.

وكما أجاب قبل، وهناك العديد من الطرق للتعامل مع استثناءات في أداة JUnit. ولكن مع جافا 8 هناك واحد آخر: استخدام امدا التعبير. مع امدا التعبير يمكننا تحقيق جملة مثل هذا:

@Test
public void verifiesTypeAndMessage() {
    assertThrown(new DummyService()::someMethod)
            .isInstanceOf(RuntimeException.class)
            .hasMessage("Runtime exception occurred")
            .hasMessageStartingWith("Runtime")
            .hasMessageEndingWith("occurred")
            .hasMessageContaining("exception")
            .hasNoCause();
}

وassertThrown تقبل واجهة وظيفية، والتي يمكن أن تنشأ مع تعبيرات لامدا، والمراجع أسلوب أو المراجع منشئ الحالات. assertThrown قبول تلك الواجهة تتوقع وتكون على استعداد للتعامل مع استثناء.

وهذا هو بسيط نسبيا تقنية لكنها قوية.

وإلقاء نظرة على هذا بلوق وظيفة واصفا هذا الأسلوب: <لأ href = "http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions هتمل "يختلط =" noreferrer "> http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

ويمكن الاطلاع على شفرة المصدر هنا: <لأ href = "https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/ java8 "يختلط =" noreferrer "> https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

الإفصاح: أنا صاحب بلوق ومشروع

في Junit، هناك أربع طرق لاختبار الاستثناء.

  • بالنسبة إلى junit4.x، استخدم السمة الاختيارية "المتوقعة" للاختبار

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
    
  • بالنسبة إلى junit4.x، استخدم قاعدة ExceptedException

    public class XxxTest {
        @Rule
        public ExpectedException thrown = ExpectedException.none();
    
        @Test
        public void testFooThrowsIndexOutOfBoundsException() {
            thrown.expect(IndexOutOfBoundsException.class)
            //you can test the exception message like
            thrown.expectMessage("expected messages");
            foo.doStuff();
        }
    }
    
  • يمكنك أيضًا استخدام طريقة المحاولة/الالتقاط الكلاسيكية المستخدمة على نطاق واسع ضمن إطار عمل Junit 3

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        try {
            foo.doStuff();
            fail("expected exception was not occured.");
        } catch(IndexOutOfBoundsException e) {
            //if execution reaches here, 
            //it indicates this exception was occured.
            //so we need not handle it.
        }
    }
    
  • وأخيرًا، بالنسبة إلى junit5.x، يمكنك أيضًا استخدام AssurThrows على النحو التالي

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        Throwable exception = assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff());
        assertEquals("expected messages", exception.getMessage());
    }
    
  • لذا

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

ليرة تركية ؛ د

  • ما قبل JDK8 :سأوصي بالخير القديم try-catch حاجز.(ولا تنسى إضافة أ fail() التأكيد قبل catch حاجز)

  • ما بعد JDK8 :استخدم AssertJ أو lambdas المخصصة للتأكيد استثنائي سلوك.

بغض النظر عن Junit 4 أو JUnit 5.

القصة الطويلة

من الممكن أن تكتب بنفسك أ افعلها بنفسك try-catch حظر أو استخدام أدوات JUnit (@Test(expected = ...) أو ال @Rule ExpectedException ميزة قاعدة JUnit).

لكن هذه الطريقة ليست أنيقة جدًا ولا تمتزج جيدًا سهولة القراءة مع أدوات أخرى.علاوة على ذلك فإن أدوات JUnit بها بعض المخاطر.

  1. ال try-catch كتلة يجب عليك كتابة الكتلة حول السلوك الذي تم اختباره، وكتابة التأكيد في كتلة الالتقاط، قد يكون ذلك جيدًا ولكن الكثير يجدون أن هذا النمط يقاطع تدفق القراءة للاختبار.كما تحتاج إلى كتابة Assert.fail عند نهاية ال try احظر وإلا فقد يخطئ الاختبار في جانب واحد من التأكيدات؛ بي إم دي, com.findbugs أو سونار سوف يكتشف مثل هذه القضايا.

  2. ال @Test(expected = ...) الميزة مثيرة للاهتمام حيث يمكنك كتابة تعليمات برمجية أقل ثم كتابة هذا الاختبار من المفترض أنها أقل عرضة لأخطاء الترميز. لكن هذا النهج يفتقر إلى بعض المجالات.

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

      @Test(expected = WantedException.class)
      public void call2_should_throw_a_WantedException__not_call1() {
          // init tested
          tested.call1(); // may throw a WantedException
      
          // call to be actually tested
          tested.call2(); // the call that is supposed to raise an exception
      }
      
  3. ال ExpectedException القاعدة هي أيضًا محاولة لإصلاح التحذيرات السابقة، ولكنها تبدو غريبة بعض الشيء في استخدامها لأنها تستخدم أسلوب التوقع، ايزي موك يعرف المستخدمون هذا الأسلوب جيدًا.قد يكون الأمر مناسبًا للبعض، لكن إذا اتبعته التنمية المدفوعة بالسلوك (بي دي دي) أو ترتيب تأكيد الفعل (AAA) مبادئ ExpectedException لن تتناسب القاعدة مع أسلوب الكتابة.وبصرف النظر عن ذلك فإنه قد يعاني من نفس المشكلة مثل @Test الطريقة، اعتمادا على المكان الذي تضع فيه التوقعات.

    @Rule ExpectedException thrown = ExpectedException.none()
    
    @Test
    public void call2_should_throw_a_WantedException__not_call1() {
        // expectations
        thrown.expect(WantedException.class);
        thrown.expectMessage("boom");
    
        // init tested
        tested.call1(); // may throw a WantedException
    
        // call to be actually tested
        tested.call2(); // the call that is supposed to raise an exception
    }
    

    حتى الاستثناء المتوقع يتم وضعه قبل بيان الاختبار، فهو يقطع تدفق القراءة إذا كانت الاختبارات تتبع BDD أو AAA.

    انظر هذا أيضا تعليق قضية على JUnit من مؤلف ExpectedException. جونيت 4.13-بيتا-2 حتى أنه يستنكر هذه الآلية:

    طلب السحب رقم 1519:إهمال الاستثناء المتوقع

    توفر الطريقة Assert.assertThrows طريقة أفضل للتحقق من الاستثناءات.بالإضافة إلى ذلك، فإن استخدام ExceptedException يكون عرضة للخطأ عند استخدامه مع قواعد أخرى مثل TestWatcher لأن ترتيب القواعد مهم في هذه الحالة.

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

  1. هناك مشروع أدركته بعد إنشاء هذه الإجابة والذي يبدو واعدًا، إنه كذلك استثناء الصيد.

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

    مثال سريع مأخوذ من الصفحة الرئيسية :

    // given: an empty list
    List myList = new ArrayList();
    
    // when: we try to get the first element of the list
    when(myList).get(1);
    
    // then: we expect an IndexOutOfBoundsException
    then(caughtException())
            .isInstanceOf(IndexOutOfBoundsException.class)
            .hasMessage("Index: 1, Size: 0") 
            .hasNoCause();
    

    كما ترون، الكود واضح ومباشر، يمكنك اكتشاف الاستثناء في سطر محدد، وهو then API هو اسم مستعار يستخدم AssertJ APIs (يشبه استخدام assertThat(ex).hasNoCause()...). في مرحلة ما اعتمد المشروع على FEST-Assert سلف AssertJ. يحرر: يبدو أن المشروع يقوم بإعداد دعم Java 8 Lambdas.

    حاليا هذه المكتبة بها عيبان:

    • في وقت كتابة هذه السطور، من الجدير بالذكر أن هذه المكتبة تعتمد على Mockito 1.x لأنها تقوم بإنشاء محاكاة للكائن الذي تم اختباره خلف الكواليس.نظرًا لأن Mockito لم يتم تحديثه بعد لا يمكن لهذه المكتبة العمل مع الفئات النهائية أو الطرق النهائية.وحتى لو كان يعتمد على mockito 2 في الإصدار الحالي، فإن ذلك يتطلب الإعلان عن صانع صور عالمي (inline-mock-maker)، وهو أمر قد لا يكون ما تريده، لأن هذا المسخر له عيوب مختلفة عن المسخر العادي.

    • يتطلب تبعية اختبار أخرى.

    لن يتم تطبيق هذه المشكلات بمجرد دعم المكتبة لـ lambdas، ومع ذلك سيتم تكرار الوظيفة بواسطة مجموعة أدوات AssertJ.

    مع أخذ كل ذلك بعين الاعتبار، إذا كنت لا ترغب في استخدام أداة التقاط الاستثناءات، فسأوصي بالطريقة القديمة الجيدة لـ try-catch كتلة، على الأقل حتى JDK7.وبالنسبة لمستخدمي JDK 8، قد تفضل استخدام AssertJ لأنه يقدم أكثر من مجرد تأكيد الاستثناءات.

  2. مع JDK8، تدخل lambdas إلى ساحة الاختبار، وقد أثبتت أنها طريقة مثيرة للاهتمام لتأكيد السلوك الاستثنائي.تم تحديث AssertJ لتوفير واجهة برمجة تطبيقات جيدة وسلسة لتأكيد السلوك الاستثنائي.

    وعينة اختبار مع تأكيدج :

    @Test
    public void test_exception_approach_1() {
        ...
        assertThatExceptionOfType(IOException.class)
                .isThrownBy(() -> someBadIOOperation())
                .withMessage("boom!"); 
    }
    
    @Test
    public void test_exception_approach_2() {
        ...
        assertThatThrownBy(() -> someBadIOOperation())
                .isInstanceOf(Exception.class)
                .hasMessageContaining("boom");
    }
    
    @Test
    public void test_exception_approach_3() {
        ...
        // when
        Throwable thrown = catchThrowable(() -> someBadIOOperation());
    
        // then
        assertThat(thrown).isInstanceOf(Exception.class)
                          .hasMessageContaining("boom");
    }
    
  3. مع إعادة كتابة شبه كاملة لـ JUnit 5، كانت التأكيدات تحسين قليلاً، قد تكون مثيرة للاهتمام باعتبارها طريقة خارج الصندوق لتأكيد الاستثناء بشكل صحيح.لكن واجهة برمجة التطبيقات التأكيدية لا تزال ضعيفة بعض الشيء، ولا يوجد شيء بالخارج assertThrows.

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }
    

    كما لاحظت assertEquals لا يزال يعود void, ، وعلى هذا النحو لا يسمح بتسلسل التأكيدات مثل AssertJ.

    أيضًا إذا كنت تتذكر اسمًا يتعارض مع Matcher أو Assert, ، كن مستعدًا لمواجهة نفس الصدام مع Assertions.

أود أن أختتم ذلك اليوم (2017-03-03) تأكيدجسهولة الاستخدام، وواجهة برمجة التطبيقات (API) القابلة للاكتشاف، والوتيرة السريعة للتطوير بحكم الأمر الواقع تعد تبعية الاختبار هي الحل الأفضل مع JDK8 بغض النظر عن إطار عمل الاختبار (JUnit أم لا)، ويجب أن تعتمد JDKs السابقة بدلاً من ذلك على try-catch كتل حتى لو كانوا يشعرون عالي الكعب.

تم نسخ هذه الإجابة من سؤال آخر التي ليس لها نفس الرؤية، أنا نفس المؤلف.

الآن بعد أن تم إصدار JUnit 5، فإن الخيار الأفضل هو استخدامه Assertions.assertThrows() (انظر دليل مستخدم يونيو 5).

فيما يلي مثال للتحقق من طرح الاستثناء واستخدامه حقيقة لتقديم تأكيدات على رسالة الاستثناء:

public class FooTest {
  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    IndexOutOfBoundsException e = assertThrows(
        IndexOutOfBoundsException.class, foo::doStuff);

    assertThat(e).hasMessageThat().contains("woops!");
  }
}

المزايا على الأساليب في الإجابات الأخرى هي:

  1. بنيت في JUnit
  2. ستحصل على رسالة استثناء مفيدة إذا لم يقم الكود الموجود في lambda بطرح استثناء، وتتبع المكدس إذا ألقى استثناءً مختلفًا
  3. مختصرا
  4. يسمح لاختباراتك باتباع Arrange-Act-Assert
  5. يمكنك الإشارة بدقة إلى الرمز الذي تتوقع طرح الاستثناء فيه
  6. لا تحتاج إلى إدراج الاستثناء المتوقع في ملف throws بند
  7. يمكنك استخدام إطار التأكيد الذي تختاره لإجراء تأكيدات حول الاستثناء الذي تم اكتشافه

سيتم إضافة طريقة مماثلة ل org.junit Assert في الوحدة 4.13.

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

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

  try {
    foo.doStuff();
  } catch (Throwable ex) {
    e = ex;
  }

  assertTrue(e instanceof IndexOutOfBoundsException);
}

بي دي دي حل النمط: الوحدة 4 + قبض على الاستثناء + تأكيدج

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(foo).doStuff();

    then(caughtException()).isInstanceOf(IndexOutOfBoundsException.class);

}

مصدر الرمز

التبعيات

eu.codearte.catch-exception:catch-exception:1.3.3

وعن طريق تأكيد AssertJ ، والتي يمكن استخدامها جنبا إلى جنب مع أداة JUnit:

import static org.assertj.core.api.Assertions.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  Foo foo = new Foo();

  assertThatThrownBy(() -> foo.doStuff())
        .isInstanceOf(IndexOutOfBoundsException.class);
}

وهذا أفضل من @Test(expected=IndexOutOfBoundsException.class) لأنه يضمن خط المتوقع في اختبار ألقى استثناء، ويتيح لك مراجعة مزيد من التفاصيل حول الاستثناء، مثل رسالة وأسهل:

assertThatThrownBy(() ->
       {
         throw new Exception("boom!");
       })
    .isInstanceOf(Exception.class)
    .hasMessageContaining("boom");

تعليمات مخضرم / Gradle هنا.

للحل نفس المشكلة لم إقامة مشروع صغير: http://code.google.com/p/catch-exception/

وباستخدام هذا المساعد الصغير كنت أكتب

verifyException(foo, IndexOutOfBoundsException.class).doStuff();

وهذا هو أقل مطول من حكم ExpectedException من أداة JUnit 4.7. بالمقارنة مع الحل التي قدمتها skaffman، يمكنك تحديد في أي سطر من التعليمات البرمجية تتوقع استثناء. آمل أن يساعد هذا.

تحديث: يحتوي JUnit5 على تحسين لاختبار الاستثناءات: assertThrows.

المثال التالي هو من: دليل مستخدم يونيو 5

 @Test
void exceptionTesting() {
    Throwable exception = assertThrows(IllegalArgumentException.class, () -> 
    {
        throw new IllegalArgumentException("a message");
    });
    assertEquals("a message", exception.getMessage());
}

الإجابة الأصلية باستخدام JUnit 4.

هناك عدة طرق لاختبار طرح الاستثناء.لقد ناقشت أيضًا الخيارات أدناه في رسالتي كيفية كتابة اختبارات وحدة رائعة باستخدام JUnit

تعيين expected معامل @Test(expected = FileNotFoundException.class).

@Test(expected = FileNotFoundException.class) 
public void testReadFile() { 
    myClass.readFile("test.txt");
}

استخدام try catch

public void testReadFile() { 
    try {
        myClass.readFile("test.txt");
        fail("Expected a FileNotFoundException to be thrown");
    } catch (FileNotFoundException e) {
        assertThat(e.getMessage(), is("The file test.txt does not exist!"));
    }

}

اختبار مع ExpectedException قاعدة.

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void testReadFile() throws FileNotFoundException {

    thrown.expect(FileNotFoundException.class);
    thrown.expectMessage(startsWith("The file test.txt"));
    myClass.readFile("test.txt");
}

يمكنك قراءة المزيد عن اختبار الاستثناءات في JUnit4 wiki لاختبار الاستثناءات و bad.robot - توقع استثناءات قاعدة JUnit.

ويمكنك أيضا القيام بذلك:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
    try {
        foo.doStuff();
        assert false;
    } catch (IndexOutOfBoundsException e) {
        assert true;
    }
}

وIMHO، وأفضل طريقة للتحقق من الاستثناءات في أداة JUnit هو محاولة / صيد / فشل / تأكيد نمط:

// this try block should be as small as possible,
// as you want to make sure you only catch exceptions from your code
try {
    sut.doThing();
    fail(); // fail if this does not throw any exception
} catch(MyException e) { // only catch the exception you expect,
                         // otherwise you may catch an exception for a dependency unexpectedly
    // a strong assertion on the message, 
    // in case the exception comes from anywhere an unexpected line of code,
    // especially important if your checking IllegalArgumentExceptions
    assertEquals("the message I get", e.getMessage()); 
}

قد يكون assertTrue قوي بعض الشيء بالنسبة لبعض الناس، لذلك assertThat(e.getMessage(), containsString("the message"); قد يكون من الأفضل.

أداة JUnit 5 الحل

@Test
void testFooThrowsIndexOutOfBoundsException() {    
  Throwable exception = expectThrows( IndexOutOfBoundsException.class, foo::doStuff );

  assertEquals( "some message", exception.getMessage() );
}

وعن بقية المقال حول أداة JUnit 5 على http://junit.org/junit5/ مستندات / الحالي / المستخدم دليل / # اختبارات الكتابة-تأكيدات

وحاولت العديد من الطرق هنا، ولكنها كانت إما معقدة أو لا تلبي تماما متطلبات بلدي. في الواقع، يمكن للمرء أن إرسال أسلوب مساعد بكل بساطة:

public class ExceptionAssertions {
    public static void assertException(BlastContainer blastContainer ) {
        boolean caughtException = false;
        try {
            blastContainer.test();
        } catch( Exception e ) {
            caughtException = true;
        }
        if( !caughtException ) {
            throw new AssertionFailedError("exception expected to be thrown, but was not");
        }
    }
    public static interface BlastContainer {
        public void test() throws Exception;
    }
}

استخدم مثل هذا:

assertException(new BlastContainer() {
    @Override
    public void test() throws Exception {
        doSomethingThatShouldExceptHere();
    }
});

وصفر تبعيات: لا حاجة لmockito، لا حاجة powermock. ويعمل على ما يرام مع الطبقات النهائية.

والجواب الأكثر مرونة وأنيقة للأداة JUnit 4 وجدت في في Mkyong بلوق . فمن لديه مرونة try/catch باستخدام الشرح @Rule. أنا أحب هذا النهج لأنه يمكنك قراءة سمات معينة من استثناء مرغوب.

package com.mkyong;

import com.mkyong.examples.CustomerService;
import com.mkyong.examples.exception.NameNotFoundException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.hasProperty;

public class Exception3Test {

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void testNameNotFoundException() throws NameNotFoundException {

        //test specific type of exception
        thrown.expect(NameNotFoundException.class);

        //test message
        thrown.expectMessage(is("Name is empty!"));

        //test detail
        thrown.expect(hasProperty("errCode"));  //make sure getters n setters are defined.
        thrown.expect(hasProperty("errCode", is(666)));

        CustomerService cust = new CustomerService();
        cust.findByName("");

    }

}

حل جافا 8

إذا كنت تريد الحل وهو:

  • يستخدم Java 8 lambdas
  • يفعل لا تعتمد على أي سحر JUnit
  • يسمح لك بالتحقق من وجود استثناءات متعددة ضمن طريقة اختبار واحدة
  • يتم التحقق من وجود استثناء يتم طرحه بواسطة مجموعة محددة من الأسطر ضمن طريقة الاختبار الخاصة بك بدلاً من أي سطر غير معروف في طريقة الاختبار بأكملها
  • يعطي كائن الاستثناء الفعلي الذي تم طرحه حتى تتمكن من فحصه بشكل أكبر

إليك وظيفة الأداة المساعدة التي كتبتها:

public final <T extends Throwable> T expectException( Class<T> exceptionClass, Runnable runnable )
{
    try
    {
        runnable.run();
    }
    catch( Throwable throwable )
    {
        if( throwable instanceof AssertionError && throwable.getCause() != null )
            throwable = throwable.getCause(); //allows "assert x != null : new IllegalArgumentException();"
        assert exceptionClass.isInstance( throwable ) : throwable; //exception of the wrong kind was thrown.
        assert throwable.getClass() == exceptionClass : throwable; //exception thrown was a subclass, but not the exact class, expected.
        @SuppressWarnings( "unchecked" )
        T result = (T)throwable;
        return result;
    }
    assert false; //expected exception was not thrown.
    return null; //to keep the compiler happy.
}

(مأخوذة من مدونتي)

استخدامه على النحو التالي:

@Test
public void testThrows()
{
    RuntimeException e = expectException( RuntimeException.class, () -> 
        {
            throw new RuntimeException( "fail!" );
        } );
    assert e.getMessage().equals( "fail!" );
}

JUnit لديه دعم مدمج لهذا، مع السمة "المتوقعة".

في حالتي أنا دائما الحصول RuntimeException من ديسيبل، ولكن الرسائل تختلف. واستثناء يجب أن يتم التعامل معها على التوالي. هنا هو كيف اختبرته:

@Test
public void testThrowsExceptionWhenWrongSku() {

    // Given
    String articleSimpleSku = "999-999";
    int amountOfTransactions = 1;
    Exception exception = null;

    // When
    try {
        createNInboundTransactionsForSku(amountOfTransactions, articleSimpleSku);
    } catch (RuntimeException e) {
        exception = e;
    }

    // Then
    shouldValidateThrowsExceptionWithMessage(exception, MESSAGE_NON_EXISTENT_SKU);
}

private void shouldValidateThrowsExceptionWithMessage(final Exception e, final String message) {
    assertNotNull(e);
    assertTrue(e.getMessage().contains(message));
}

وجعل مجرد المنظر التي يمكن أن تحول وتشغيله، مثل هذا:

public class ExceptionMatcher extends BaseMatcher<Throwable> {
    private boolean active = true;
    private Class<? extends Throwable> throwable;

    public ExceptionMatcher(Class<? extends Throwable> throwable) {
        this.throwable = throwable;
    }

    public void on() {
        this.active = true;
    }

    public void off() {
        this.active = false;
    }

    @Override
    public boolean matches(Object object) {
        return active && throwable.isAssignableFrom(object.getClass());
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("not the covered exception type");
    }
}

لاستخدامه:

وإضافة public ExpectedException exception = ExpectedException.none();، ثم:

ExceptionMatcher exMatch = new ExceptionMatcher(MyException.class);
exception.expect(exMatch);
someObject.somethingThatThrowsMyException();
exMatch.off();

في JUnit 4 أو الأحدث يمكنك اختبار الاستثناءات على النحو التالي

@Rule
public ExpectedException exceptions = ExpectedException.none();


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

  1. نوع الاستثناء الذي تم طرحه
  2. رسالة الاستثناء
  3. سبب الاستثناء


public class MyTest {

    @Rule
    public ExpectedException exceptions = ExpectedException.none();

    ClassUnderTest classUnderTest;

    @Before
    public void setUp() throws Exception {
        classUnderTest = new ClassUnderTest();
    }

    @Test
    public void testAppleisSweetAndRed() throws Exception {

        exceptions.expect(Exception.class);
        exceptions.expectMessage("this is the exception message");
        exceptions.expectCause(Matchers.<Throwable>equalTo(exceptionCause));

        classUnderTest.methodUnderTest("param1", "param2");
    }

}

ويمكننا استخدام تأكيد تفشل بعد الأسلوب الذي يجب أن يعود استثناء:

try{
   methodThatThrowMyException();
   Assert.fail("MyException is not thrown !");
} catch (final Exception exception) {
   // Verify if the thrown exception is instance of MyException, otherwise throws an assert failure
   assertTrue(exception instanceof MyException, "An exception other than MyException is thrown !");
   // In case of verifying the error message
   MyException myException = (MyException) exception;
   assertEquals("EXPECTED ERROR MESSAGE", myException.getMessage());
}

بالإضافة إلى ماذا NamShubWriter قال: فتأكد من:

  • مثيل ExpectedException هو عام (سؤال ذو صلة)
  • الاستثناء المتوقع ليس كذلك تم إنشاء مثيل له في طريقة @Before.هذا بريد يشرح بوضوح جميع تعقيدات أمر تنفيذ JUnit.

يفعل لا افعل هذا:

@Rule    
public ExpectedException expectedException;

@Before
public void setup()
{
    expectedException = ExpectedException.none();
}

أخيراً، هذا يوضح منشور المدونة بوضوح كيفية التأكد من طرح استثناء معين.

وأنا نوصي assertj-core مكتبة للتعامل مع استثناء في اختبار أداة JUnit

في جافا 8، مثل هذا:

//given

//when
Throwable throwable = catchThrowable(() -> anyService.anyMethod(object));

//then
AnyException anyException = (AnyException) throwable;
assertThat(anyException.getMessage()).isEqualTo("........");
assertThat(exception.getCode()).isEqualTo(".......);

وحل Junit4 مع Java8 هو استخدام هذه الوظيفة:

public Throwable assertThrows(Class<? extends Throwable> expectedException, java.util.concurrent.Callable<?> funky) {
    try {
        funky.call();
    } catch (Throwable e) {
        if (expectedException.isInstance(e)) {
            return e;
        }
        throw new AssertionError(
                String.format("Expected [%s] to be thrown, but was [%s]", expectedException, e));
    }
    throw new AssertionError(
            String.format("Expected [%s] to be thrown, but nothing was thrown.", expectedException));
}

والاستخدام ومن ثم:

    assertThrows(ValidationException.class,
            () -> finalObject.checkSomething(null));

لاحظ أن القيد الوحيد هو استخدام مرجع كائن final في التعبير امدا. هذا الحل يسمح لمواصلة التأكيدات اختبار بدلا من تتوقع thowable على مستوى طريقة استخدام الحل @Test(expected = IndexOutOfBoundsException.class).

وخذ على سبيل المثال، كنت تريد أن تكتب أداة JUnit للجزء التعليمات البرمجية المذكورة أدناه

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

    throw new ArrayIndexOutOfBoundsException("Array is out of bound");
}

ورمز أعلاه هو اختبار لبعض استثناء غير معروف التي قد تحدث وأقل من واحد هو التأكيد على بعض استثناء مع رسالة مخصصة.

 @Rule
public ExpectedException exception=ExpectedException.none();

private Demo demo;
@Before
public void setup(){

    demo=new Demo();
}
@Test(expected=ArithmeticException.class)
public void testIfItThrowsAnyException() {

    demo.divideByZeroDemo(5, 0);

}

@Test
public void testExceptionWithMessage(){


    exception.expectMessage("Array is out of bound");
    exception.expect(ArrayIndexOutOfBoundsException.class);
    demo.exceptionWithMessage(new String[]{"This","is","a","demo"});
}

باستخدام Java 8، يمكنك إنشاء طريقة تأخذ رمزًا للتحقق والاستثناء المتوقع كمعلمات:

private void expectException(Runnable r, Class<?> clazz) { 
    try {
      r.run();
      fail("Expected: " + clazz.getSimpleName() + " but not thrown");
    } catch (Exception e) {
      if (!clazz.isInstance(e)) fail("Expected: " + clazz.getSimpleName() + " but " + e.getClass().getSimpleName() + " found", e);
    }
  }

ثم داخل الاختبار الخاص بك:

expectException(() -> list.sublist(0, 2).get(2), IndexOutOfBoundsException.class);

فوائد:

  • عدم الاعتماد على أي مكتبة
  • التحقق المحلي - أكثر دقة ويسمح بالحصول على تأكيدات متعددة مثل هذه ضمن اختبار واحد إذا لزم الأمر
  • سهل الاستخدام

وبلدي الحل باستخدام جافا 8 lambdas:

public static <T extends Throwable> T assertThrows(Class<T> expected, ThrowingRunnable action) throws Throwable {
    try {
        action.run();
        Assert.fail("Did not throw expected " + expected.getSimpleName());
        return null; // never actually
    } catch (Throwable actual) {
        if (!expected.isAssignableFrom(actual.getClass())) { // runtime '!(actual instanceof expected)'
            System.err.println("Threw " + actual.getClass().getSimpleName() 
                               + ", which is not a subtype of expected " 
                               + expected.getSimpleName());
            throw actual; // throw the unexpected Throwable for maximum transparency
        } else {
            return (T) actual; // return the expected Throwable for further examination
        }
    }
}

لديك لتحديد FunctionalInterface، لأن Runnable لا تعلن throws المطلوبة.

@FunctionalInterface
public interface ThrowingRunnable {
    void run() throws Throwable;
}

ويمكن استخدام هذه الطريقة كما يلي:

class CustomException extends Exception {
    public final String message;
    public CustomException(final String message) { this.message = message;}
}
CustomException e = assertThrows(CustomException.class, () -> {
    throw new CustomException("Lorem Ipsum");
});
assertEquals("Lorem Ipsum", e.message);

هناك طريقتان لكتابة حالة الاختبار

  1. قم بتعليق الاختبار مع الاستثناء الذي تم طرحه بواسطة الطريقة.شيء من هذا القبيل @Test(expected = IndexOutOfBoundsException.class)
  2. يمكنك ببساطة التقاط الاستثناء في فئة الاختبار باستخدام كتلة محاولة الالتقاط والتأكيد على الرسالة التي تم طرحها من الطريقة في فئة الاختبار.

    try{
    }
    catch(exception to be thrown from method e)
    {
         assertEquals("message", e.getmessage());
    }
    

آمل أن يجيب هذا على استعلامك التعلم السعيد ...

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