จะทดสอบการจัดการข้อผิดพลาดในการทดสอบทั่วไปได้อย่างไร?

ฉันเริ่มใช้ common test เป็นกรอบการทดสอบของฉันใน erlang

สมมติว่าฉันมีฟังก์ชันที่ฉันคาดว่าจะยอมรับเฉพาะจำนวนบวก และควรจะพัดในกรณีอื่น

positive_number(X) when > 0 -> {positive, X}.

และฉันต้องการทดสอบสิ่งนั้น

positive_number(-5).

จะไม่สำเร็จลุล่วงได้

ฉันจะทดสอบพฤติกรรมนี้ได้อย่างไร ในภาษาอื่น ฉันจะบอกว่ากรณีทดสอบคาดว่าจะมีข้อผิดพลาดหรือข้อยกเว้น และล้มเหลวหากทำงานภายใต้การทดสอบไม่แสดงข้อผิดพลาดใดๆ สำหรับพารามิเตอร์ที่ไม่ถูกต้องที่ไม่ถูกต้อง จะทำอย่างไรกับการทดสอบทั่วไป?

อัปเดต:

ฉันสามารถทำให้มันใช้งานได้

test_credit_invalid_input(_) ->
  InvalidArgument = -1,
  try mypackage:positive_number(InvalidArgument) of
    _ -> ct:fail(not_failing_as_expected)
  catch
    error:function_clause -> ok
  end.

แต่ฉันคิดว่านี่มันละเอียดเกินไป ฉันต้องการบางอย่างเช่น:

assert_error(mypackage:positive_number, [-1], error:function_clause)

ฉันคิดว่าการทดสอบทั่วไปมีสิ่งนี้ในบางแห่งและฉันขาดความรู้ที่ถูกต้องเกี่ยวกับกรอบการทำงานที่ทำให้ฉันต้องใช้วิธีการแก้ปัญหาแบบละเอียด

อัปเดต: ได้รับแรงบันดาลใจจากคำตอบของ Michael ฉันจึงสร้างฟังก์ชันต่อไปนี้:

assert_fail(Fun, Args, ExceptionType, ExceptionValue, Reason) ->
  try apply(Fun, Args) of
    _ -> ct:fail(Reason)
  catch
    ExceptionType:ExceptionValue -> ok
  end.

และการทดสอบของฉันก็กลายเป็น:

test_credit_invalid_input(_) ->
  InvalidArgument = -1,
  assert_fail(fun mypackage:positive_number/1,
              [InvalidArgument],
              error,
              function_clause,
              failed_to_catch_invalid_argument).

แต่ฉันคิดว่ามันใช้งานได้เพราะมันอ่านง่ายขึ้นเล็กน้อยที่จะมีการเรียก assert_fail มากกว่าการมี try....catch ในทุกกรณีทดสอบ

ฉันยังคงคิดว่าควรมีการใช้งานที่ดีกว่านี้ใน Common Test, IMO เป็นการทำซ้ำที่น่าเกลียดที่จะมีการนำรูปแบบการทดสอบนี้ไปใช้ซ้ำๆ ในทุกโครงการ


person Jonas Fagundes    schedule 15.11.2015    source แหล่งที่มา


คำตอบ (1)


แปลงข้อยกเว้นให้เป็นนิพจน์และจับคู่:

test_credit_invalid_input(_) ->
    InvalidArgument = -1,
    {'EXIT', {function_clause, _}}
            = (catch mypackage:positive_number(InvalidArgument)).

นั่นจะเปลี่ยนข้อยกเว้นของคุณให้กลายเป็นการไม่มีข้อยกเว้นและในทางกลับกัน อาจจะเป็นแบบสั้นๆ เท่าที่คุณคาดหวังได้

คุณสามารถใช้มาโครหรือฟังก์ชันได้ตลอดเวลาเพื่อซ่อนคำฟุ่มเฟือย

แก้ไข (อีกครั้ง: ความคิดเห็น):

หากคุณใช้โครงสร้าง try catch แบบเต็ม เช่นเดียวกับในคำถามของคุณ คุณจะสูญเสียข้อมูลเกี่ยวกับกรณีความล้มเหลวใดๆ เนื่องจากข้อมูลนั้นถูกโยนทิ้งไปเพื่อสนับสนุนอะตอม 'not_failing_as_expected' หรือ 'failed_to_catch_invalid_argument'

กำลังลองใช้ค่าความล้มเหลวที่คาดหวังบนเชลล์:

1> {'EXIT', {function_clause, _}}
        = (catch mypackage:positive_number(-4)).             
{'EXIT',{function_clause,[{mypackage,positive_number,
                                 [-4],
                                 [{file,"mypackage.erl"},{line,7}]},
                      {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,661}]},
                      {erl_eval,expr,5,[{file,"erl_eval.erl"},{line,434}]},
                      {erl_eval,expr,5,[{file,"erl_eval.erl"},{line,441}]},
                      {shell,exprs,7,[{file,"shell.erl"},{line,676}]},
                      {shell,eval_exprs,7,[{file,"shell.erl"},{line,631}]},
                      {shell,eval_loop,3,[{file,"shell.erl"},{line,616}]}]}}

การรับค่าความสำเร็จที่คาดหวังบนเชลล์:

2> {'EXIT', {function_clause, _}} = (catch mypackage:positive_number(3)). 
** exception error: no match of right hand side value {positive,3}

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

ในกรณีง่ายๆ เช่นนี้ สิ่งเหล่านี้ไม่สำคัญมากนัก แต่โดยหลักแล้ว มันสำคัญ เพราะในภายหลังด้วยกรณีที่ซับซ้อนมากขึ้น ซึ่งบางทีค่าที่ทำให้ฟังก์ชันของคุณล้มเหลวอาจไม่ได้ฮาร์ดโค้ดเหมือนอย่างที่เป็นอยู่นี้ คุณอาจ ไม่รู้ว่าค่าใดที่ทำให้ฟังก์ชันของคุณล้มเหลว หรือค่าส่งคืนใด นี่อาจเป็นความแตกต่างระหว่างการมองย้อนกลับไปที่น่าเกลียดสักครู่หนึ่งหรือสองกับการตระหนักว่าปัญหาคืออะไร หรือการใช้เวลา 15 นาทีในการตั้งค่าการทดสอบเพื่อค้นหาว่าเกิดอะไรขึ้นจริงๆ... หรือที่แย่กว่านั้นคือถ้าเป็น heisenbug คุณอาจใช้เวลาหลายชั่วโมงเพื่อค้นหามัน!

person Michael    schedule 15.11.2015
comment
โซลูชันนี้ใช้งานได้ แต่สร้างข้อความแสดงข้อผิดพลาดที่น่าสับสนมากขึ้นเกี่ยวกับความล้มเหลวในการทดสอบจากโซลูชันก่อนหน้าของฉัน ฉันชอบแนวคิดเกี่ยวกับฟังก์ชันตัวช่วย - person Jonas Fagundes; 16.11.2015
comment
@JonasFagundes คุณสามารถรวมมาโคร EUnit และใช้งานได้ดีภายใต้การทดสอบทั่วไป - person Adam Lindberg; 16.11.2015
comment
@JonasFagundes ข้อความแสดงข้อผิดพลาดที่ทำให้เกิดความสับสนมีความสำคัญหรือไม่? ฉันรู้ว่าการติดตามสแต็กของ Erlang นั้นไม่สวยนัก แต่ส่วนใหญ่เป็นเพราะ Erlang ใช้งานได้ คุณจะรู้ว่าฟังก์ชันใดล้มเหลว ที่ไหนและทำไมจากมัน... โดยที่ราวกับว่าคุณใช้ try catch เช่นเดียวกับในคำถามของคุณ คุณจะทิ้งทั้งหมดไป ข้อมูลนั้นและแทนที่ด้วย 'not_failing_as_expected' ฉันเข้าใจความต้องการของคุณที่จะทำเช่นนี้ในระดับหนึ่ง แต่ฉันคิดว่ามันเป็นวิธีที่ผิดจริงๆ ฉันจะเพิ่มสิ่งนี้ลงในคำตอบเพื่อให้ชัดเจน ไม่สามารถทำได้ในความคิดเห็นจริงๆ - person Michael; 16.11.2015
comment
@JonasFagundes แน่นอนคุณสามารถปรับเปลี่ยนโซลูชันฟังก์ชันตัวช่วยที่คุณเลือกเพื่อรวมสิ่งนี้ได้เช่นกันหากคุณไม่สนใจที่จะเพิ่มคำฟุ่มเฟือยที่สับสนให้กับข้อผิดพลาดของคุณ - person Michael; 16.11.2015
comment
@Michael ฉันเข้าใจประเด็นของคุณ แต่ในกรณีทดสอบที่มีข้อมูลมากเกินไปนั้นไม่ดีเนื่องจากการมีข้อมูลไม่เพียงพอ การค้นหาว่าจำนวนที่เหมาะสมคืออะไรเป็นส่วนที่ยุ่งยาก :) ct:fail สามารถรับสตริงรูปแบบและ args ได้ แต่ฉันตัดสินใจว่ามี แท็กบวกชื่อกรณีทดสอบในรายงานข้อผิดพลาดก็เพียงพอแล้วในกรณีนี้ มันไม่เคยสูญเสียข้อมูลในระบบภายใต้การทดสอบ เพียงแต่กรณีทดสอบจะทิ้งข้อมูลที่ไม่จำเป็นออกไป (มันจะสร้าง error:function_clause สิ่งที่ฉันไม่คิดว่าไม่ใช่แนวปฏิบัติที่ดี ฉันจะแยกฟังก์ชันเพื่อจับมันและสร้างข้อความแสดงข้อผิดพลาดที่เหมาะสม) . - person Jonas Fagundes; 16.11.2015