Как протестировать обработку ошибок в обычном тесте?

Я начинаю использовать 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)

Я предполагаю, что общий тест имеет это в каком-то месте, и мое отсутствие надлежащих знаний о структуре заставляет меня принимать такое подробное решение.

Обновление: Вдохновленный ответом Майкла, я создал следующую функцию:

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

Это превращает ваше исключение в неисключение и наоборот, возможно, настолько лаконично, насколько вы можете ожидать.

Вы всегда можете использовать макрос или функцию, чтобы скрыть многословие.

Изменить (re: комментарий):

Если вы используете полную конструкцию 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 функционален, вы будете знать, какая функция не удалась, где и почему... эту информацию и замените ее на «not_failing_as_expected». Я понимаю ваше стремление сделать это в некоторой степени, но я думаю, что это действительно неправильный путь. Я добавлю это к ответу, чтобы было ясно, на самом деле я не могу сделать это в комментарии. - person Michael; 16.11.2015
comment
@JonasFagundes Вы, конечно, можете адаптировать выбранное решение вспомогательной функции, чтобы включить это, если вы не против добавить много запутанной многословности к своей ошибке. - person Michael; 16.11.2015
comment
@Майкл, я понимаю вашу точку зрения, но в тестовом примере иметь слишком много информации плохо, так как не иметь достаточно информации, найти правильную сумму - это сложная часть :) ct:fail может получить строку формата и аргументы, но я решил, что имея в этом случае было достаточно тега плюс имя тестового примера в отчете об ошибке. Он никогда не терял информацию в тестируемой системе, только тестовый пример отбрасывает ненужную информацию (он генерирует error:function_clause, что я не считаю хорошей практикой, я разветвлю функцию, чтобы поймать ее и сделать правильное сообщение об ошибке) . - person Jonas Fagundes; 16.11.2015