it-swarm.asia

كيفية اختبار أنه لا يوجد استثناء؟

أعلم أن إحدى الطرق للقيام بذلك هي:

@Test
public void foo(){
   try{
      //execute code that you expect not to throw Exceptions.
   }
   catch(Exception e){
      fail("Should not have thrown any exception");
   }
}

هل هناك أي طريقة أنظف للقيام بذلك. (ربما باستخدام Junit's @Rule؟)

170
Ankit Dhingra

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

لقد لاحظت هذا السؤال يحظى باهتمام من وقت لآخر ، لذا سأوسع قليلاً.

خلفية لاختبار وحدة

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

أو ، كما هو محدد في فن اختبار الوحدات ، الإصدار الثاني من تأليف روي أوشروف ، صفحة 11:

اختبار A unit عبارة عن جزء تلقائي من التعليمات البرمجية يستدعي وحدة العمل الجاري اختبارها ، ثم يتحقق من بعض الافتراضات حول النتيجة النهائية الفردية لتلك الوحدة. يتم دائمًا كتابة اختبار الوحدة باستخدام إطار اختبار الوحدة. يمكن كتابته بسهولة ويعمل بسرعة. انها جديرة بالثقة ، وقابلة للقراءة ، وقابلة للصيانة. إنه ثابت في نتائجه طالما لم يتغير رمز الإنتاج.

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

enter image description here

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

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

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

التعامل مع الاختبارات الصحيحة والمعيبة

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

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

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

بإضافة اختبار آخر (nonExistingUserById_ShouldThrow_IllegalArgumentException) يستخدم خلل إدخال ويتوقع استثناء ، يمكنك معرفة ما إذا كانت طريقتك تفعل ما يفترض القيام به مع إدخال خاطئ.

TL، DR

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

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

164
Jeroen Vannevel

لقد تعثرت بسبب هذا بسبب "الحبار: S2699" لشركة SonarQube: "أضف تأكيدًا واحدًا على الأقل إلى حالة الاختبار هذه."

كان لدي اختبار بسيط كان هدفه الوحيد هو الدخول دون رمي استثناءات.

النظر في هذا الكود البسيط:

public class Printer {

    public static void printLine(final String line) {
        System.out.println(line);
    }
}

أي نوع من التأكيد يمكن إضافته لاختبار هذه الطريقة؟ بالتأكيد ، يمكنك إجراء تجربة للتجول حوله ، لكن هذا هو رمز الشفرة فقط.

الحل يأتي من JUnit نفسها.

في حالة عدم طرح أي استثناء وتريد توضيح هذا السلوك بشكل صريح ، ما عليك سوى إضافة expected كما في المثال التالي:

@Test(expected = Test.None.class /* no exception expected */)
public void test_printLine() {
    Printer.printLine("line");
}

Test.None.class هو القيمة الافتراضية للقيمة المتوقعة.

58
Sven Döring

يجعل Java 8 هذا الأمر أسهل بكثير ، كما أن Kotlin/Scala مضاعفة جدًا.

يمكننا كتابة فئة فائدة صغيرة

class MyAssertions{
  public static void assertDoesNotThrow(FailingRunnable action){
    try{
      action.run()
    }
    catch(Exception ex){
      throw new Error("expected action not to throw, but it did!", ex)
    }
  }
}

@FunctionalInterface interface FailingRunnable { void run() throws Exception }

ثم يصبح رمزك ببساطة:

@Test
public void foo(){
  MyAssertions.assertDoesNotThrow(() -> {
    //execute code that you expect not to throw Exceptions.
  }
}

إذا لم يكن لديك حق الوصول إلى Java-8 ، فسوف أستخدم منشأة Java قديمة مؤلمة: كتل التعليمات البرمجية aribitrary وتعليق بسيط

//setup
Component component = new Component();

//act
configure(component);

//assert 
/*assert does not throw*/{
  component.doSomething();
}

وأخيراً ، مع kotlin ، اللغة التي أحببتها مؤخرًا:

fun (() -> Any?).shouldNotThrow() 
    = try { invoke() } catch (ex : Exception){ throw Error("expected not to throw!", ex) }

@Test fun `when foo happens should not throw`(){

  //...

  { /*code that shouldn't throw*/ }.shouldNotThrow()
}

على الرغم من أن هناك متسعًا كبيرًا للتعبير عن كيفية التعبير عن ذلك بالضبط ، إلا أنني كنت دائمًا من المعجبين بـ { التأكيدات بطلاقة }.


بخصوص

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

هذا صحيح من حيث المبدأ ولكنه غير صحيح في الختام.

تسمح Java باستثناءات لتدفق التحكم. يتم ذلك بواسطة وقت تشغيل JRE نفسه في واجهات برمجة التطبيقات مثل Double.parseDouble عبر NumberFormatException و Paths.get عبر InvalidPathException.

نظرًا لأنك كتبت مكونًا يتحقق من صحة سلاسل الأعداد لـ Double.ParseDouble ، أو ربما باستخدام Regex ، أو ربما محلل مكتوب بخط اليد ، أو ربما شيء يتضمن بعض قواعد المجال الأخرى التي تقيد نطاق مضاعفة إلى شيء محدد ، وأفضل طريقة لاختبار هذا المكون؟ أعتقد أنه سيكون هناك اختبار واضح للتأكيد على أنه عند تحليل السلسلة الناتجة ، لن يتم طرح أي استثناء. أود أن أكتب هذا الاختبار باستخدام إما كتلة assertDoesNotThrow أو /*comment*/{code} أعلاه. شيء مثل

@Test public void given_validator_accepts_string_result_should_be_interpretable_by_doubleParseDouble(){
  //setup
  String input = "12.34E+26" //a string double with domain significance

  //act
  boolean isValid = component.validate(input)

  //assert -- using the library 'assertJ', my personal favourite 
  assertThat(isValid).describedAs(input + " was considered valid by component").isTrue();
  assertDoesNotThrow(() -> Double.parseDouble(input));
}

كما أشجعك على تحديد هذا الاختبار في input باستخدام Theories أو Parameterized بحيث يمكنك بسهولة إعادة استخدام هذا الاختبار للمدخلات الأخرى. بدلاً من ذلك ، إذا كنت تريد أن تكون غريبًا ، فيمكنك استخدام أداة إنشاء الاختبارهذا ). TestNG لديه دعم أفضل للاختبارات البارامترات.

ما أجده غير مرغوب فيه بشكل خاص هو التوصية باستخدام @Test(expectedException=IllegalArgumentException.class) ، هذا الاستثناء واسع بشكل خطير . إذا تغيرت التعليمات البرمجية الخاصة بك بحيث يكون المكون ضمن مُنشئ الاختبار if(constructorArgument <= 0) throw IllegalArgumentException() ، وكان الاختبار يوفر 0 لهذه الوسيطة لأنه كان مناسبًا - وهذا أمر شائع جدًا ، لأن إنشاء بيانات اختبار جيدة يمثل مشكلة صعبة بشكل مدهش - ، سيكون الاختبار أخضرًا على الرغم من أنه لا يختبر شيئًا. مثل هذا الاختبار هو أسوأ من عديمة الفائدة.

29
Groostav

مع تأكيدات بطلاقة AssertJ 3.7.0 :

Assertions.assertThatCode(() -> toTest.method())
    .doesNotThrowAnyException();
16
denu

إذا كنت محظوظًا بما يكفي لالتقاط جميع الأخطاء في التعليمات البرمجية الخاصة بك. يمكنك القيام به بغباء

class DumpTest {
    Exception ex;
    @Test
    public void testWhatEver() {
        try {
            thisShouldThroughError();
        } catch (Exception e) {
            ex = e;
        }
        assertEquals(null,ex);
    }
}
16
Ben Tennyson

توفر JUnit 5 (Jupiter) ثلاث وظائف للتحقق من غياب/وجود استثناء:

assertAll​()

Asserts that all المتوفر executablesname__
لا تلقي استثناءات.

assertDoesNotThrow​()

Asserts تنفيذ حكم
الموردة executablename __/suppliername__
لا يرمي أي نوع من استثناء .

هذه الوظيفة متاحة
منذ JUnit 5.2.0 (29 أبريل 2018).

assertThrows​()

Asserts تنفيذ الموفر المتوفر executablename__
رميات استثناء من expectedTypename__
وإرجاع استثناء .

مثال

package test.mycompany.myapp.mymodule;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class MyClassTest {

    @Test
    void when_string_has_been_constructed_then_myFunction_does_not_throw() {
        String myString = "this string has been constructed";
        assertAll(() -> MyClass.myFunction(myString));
    }

    @Test
    void when_string_has_been_constructed_then_myFunction_does_not_throw__junit_v520() {
        String myString = "this string has been constructed";
        assertDoesNotThrow(() -> MyClass.myFunction(myString));
    }

    @Test
    void when_string_is_null_then_myFunction_throws_IllegalArgumentException() {
        String myString = null;
        assertThrows(
            IllegalArgumentException.class,
            () -> MyClass.myFunction(myString));
    }

}
7
olibre

يضيف JUnit5 طريقة assertAll () لهذا الغرض الدقيق.

assertAll( () -> foo() )

المصدر: JUnit 5 API

5
razalghul

يمكنك القيام بذلك باستخدامRule ثم استدعاء أسلوب reportMissingExceptionWithMessage كما هو موضح أدناه: هذا هو Scala ، ولكن يمكن القيام به بسهولة على نحو مماثل في Java.

 enter image description here 

2
Crenguta S

استخدم assertNull (...)

@Test
public void foo() {
    try {
        //execute code that you expect not to throw Exceptions.
    } catch (Exception e){
        assertNull(e);
    }
}
1
Mike Rapadas

إذا كنت ترغب في اختبار ما إذا كان هدف الاختبار يستهلك الاستثناء. فقط اترك الاختبار كـ (متعاون وهمي باستخدام jMock2):

@Test
public void consumesAndLogsExceptions() throws Exception {

    context.checking(new Expectations() {
        {
            oneOf(collaborator).doSth();
            will(throwException(new NullPointerException()));
        }
    });

    target.doSth();
 }

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

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

@Test
public void consumesAndLogsExceptions() throws Exception {
    Exception e = new NullPointerException();
    context.checking(new Expectations() {
        {
            allowing(collaborator).doSth();
            will(throwException(e));

            oneOf(consumer).consume(e);
        }
    });

    target.doSth();
 }

لكن في بعض الأحيان يكون التصميم أكثر من اللازم إذا كنت ترغب فقط في تسجيل الدخول. في هذه الحالة ، هذه المقالة ( http://Java.dzone.com/articles/monitoring-declarative-transac ، http://blog.novoj.net/2008/09/20/testing-aspect - نقاط - هل هناك طريقة سهلة/ ) قد تساعد إذا أصرت على tdd في هذه الحالة.

1
Yugang Zhou

يمكنك توقع عدم استثناء هذا الاستثناء عن طريق إنشاء قاعدة.

@Rule
public ExpectedException expectedException = ExpectedException.none();
1
LazerBanana

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

import org.assertj.core.api.Assertions;
import org.junit.Test;

public class AssertionExample {

    @Test
    public void testNoException(){
        assertNoException();
    }    

    private void assertException(){
        Assertions.assertThatThrownBy(this::doNotThrowException).isInstanceOf(Exception.class);
    }

    private void assertNoException(){
        Assertions.assertThatThrownBy(() -> assertException()).isInstanceOf(AssertionError.class);
    }

    private void doNotThrowException(){
        //This method will never throw exception
    }
}
0
MLS