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




junit شرح (20)

حل جافا 8

إذا كنت ترغب في حل ما يلي:

  • يستخدم lambdas جافا 8
  • لا يعتمد على أي سحر 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!" );
}

https://code.i-harness.com

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

بينما يمكنني بالتأكيد فعل شيء كالتالي:

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

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

  assertTrue(thrown);
}

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


JUnit 5 الحل

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

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

مزيد من المعلومات عن JUnit 5 على improved


How about this: Catch a general general exception ، تأكد من أنه يخرج من كتلة catch ، ثم أكد أن صنف الاستثناء هو ما تتوقعه. سوف تفشل هذه التوكيد إذا كان a) الاستثناء من النوع الخطأ (على سبيل المثال ، إذا حصلت على Null Pointer بدلاً من ذلك) و b) لم يتم طرح الاستثناء مطلقًا.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}

IMHO ، أفضل طريقة للتحقق من وجود استثناءات في JUnit هو نمط try / catch / fail / assert:

// 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");


باستخدام تأكيد 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");

تعليمات Maven / Gradle هنا.


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

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


هذا يوفر الكثير من الميزات التي يمكن استخدامها لتحسين اختبارات JUnit.
If you see the below example I am testing 3 things on the exception.

  1. The Type of exception thrown
  2. The exception Message
  3. The cause of the exception


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");
    }

}

في حالتي دائما الحصول على 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));
}

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

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

assertThrown يقبل واجهة وظيفية ، يمكن إنشاء مثيلاتها باستخدام تعبيرات lambda أو مراجع أسلوب أو مراجع منشئ. يؤكد assertThrown قبول تلك الواجهة وتكون جاهزة لمعالجة استثناء.

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

إلقاء نظرة على هذا بلوق وظيفة تصف هذه التقنية: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

يمكن العثور على رمز المصدر هنا: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

الإفصاح: أنا مؤلف المدونة والمشروع.


لحل نفس المشكلة ، أعددت مشروعًا صغيرًا: http://code.google.com/p/catch-exception/

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

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

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


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

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();
    }
});

صفر الاعتماديات: لا حاجة إلى موكيتو ، لا حاجة إلى powermock ؛ ويعمل بشكل جيد مع الفصول النهائية.



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

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

تحرير الآن بعد أن أصدرت 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) لأن الاختبار ستفشل إذا تم طرح foo.doStuff() قبل foo.doStuff()

انظر هذه المادة لمزيد من التفاصيل


ليرة تركية، والدكتور

  • قبل JDK8: سأوصي try الجيدة القديمة - كتلة catch .

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

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

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

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

  1. كتلة try يجب أن تكتب الكتلة حول السلوك المختبر، وتكتب التوكيد في كتلة المصيد، التي قد تكون جيدة لكن الكثير يجد taht هذا الأسلوب يقطع تدفق القراءة للاختبار. كما تحتاج إلى كتابة Assert.fail في نهاية كتلة try وإلا قد يغيب الاختبار جانب واحد من التأكيدات. PMD ، findbugs أو Sonar سوف يكتشف مثل هذه القضايا.

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

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

      @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 هي أيضًا محاولة لإصلاح التحذيرات السابقة ، ولكنها تبدو محرجة بعض الشيء لاستخدامها لأنها تستخدم أسلوبًا متوقعًا ، يعرف مستخدمو EasyMock هذا النمط جيدًا. قد يكون مناسبًا للبعض ، لكن إذا اتبعت مبادئ تطوير السلوكيات (BDD) أو Arrange Act Assert (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.

    انظر أيضا هذه المسألة comment على JUnit للمؤلف ExpectedException .

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

  1. هناك مشروع تعرفت عليه بعد إنشاء هذه الإجابة التي تبدو واعدة ، ولكنها catch-exception .

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

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

    // 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 . تحرير: يبدو أن المشروع هو تختمر دعم جافا 8 Lambdas.

    حاليا هذه المكتبة لديها اثنين من أوجه القصور:

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

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

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

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

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

    واختبار عينة مع 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 ، تم improved التأكيدات قليلاً ، قد تثبت أنها مثيرة للاهتمام كوسيلة من خارج منطقة الجزاء لتأكيد الاستثناء بشكل صحيح. ولكن في الحقيقة ، لا تزال واجهة برمجة تطبيقات التأكيد ضعيفة بعض الشيء ، ولا يوجد شيء خارج assertThrows .

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

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

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

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

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


I wanted to comment with my solution to this problem, which avoided needing any of the exception related JUnit code.

I used assertTrue(boolean) combined with try/catch to look for my expected exception to be thrown. إليك مثال على ذلك:

public void testConstructor() {
    boolean expectedExceptionThrown;
    try {
        // Call constructor with bad arguments
        double a = 1;
        double b = 2;
        double c = a + b; // In my example, this is an invalid option for c
        new Triangle(a, b, c);
        expectedExceptionThrown = false; // because it successfully constructed the object
    }
    catch(IllegalArgumentException e) {
        expectedExceptionThrown = true; // because I'm in this catch block
    }
    catch(Exception e) {
        expectedExceptionThrown = false; // because it threw an exception but not the one expected
    }
    assertTrue(expectedExceptionThrown);
}

Junit4 solution with Java8 is to use this function:

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));
}

Usage is then:

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

Note that the only limitation is to use a final object reference in lambda expression. This solution allows to continue test assertions instead of expecting thowable at method level using @Test(expected = IndexOutOfBoundsException.class) solution.


My solution using Java 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
        }
    }
}

You have to define a FunctionalInterface, because Runnable doesn't declare the required throws .

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

The method can be used as follows:

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);

Take for example, you want to write Junit for below mentioned code fragment

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

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

The above code is to test for some unknown exception that may occur and the below one is to assert some exception with custom message.

 @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"});
}

There are two ways of writing test case

  1. Annotate the test with the exception which is thrown by the method. Something like this @Test(expected = IndexOutOfBoundsException.class)
  2. You can simply catch the exception in the test class using the try catch block and assert on the message that is thrown from the method in test class.

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

I hope this answers your query Happy learning...


We can use an assertion fail after the method that must return an exception:

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());
}






assert