2009-11-21 2 views
1

Я пришел к чему-то перекрестку. Недавно я написал приложение на 10 000 строк без TDD (ошибка, которую я знаю). Я определенно столкнулся с очень большим количеством ошибок, но теперь я хочу модифицировать проект. Вот проблема, с которой я столкнулся. Давайте пример функции, которая делает деление:Как работает TDD с исключениями и параметризацией?

public int divide (int var1, int var2){ 
if (var1 == 0 || var2 == 0) 
    throw new RuntimeException("One of the parameters is zero"); 
return var1/var2; 
} 

В этой ситуации я выбрасываю ошибку времени выполнения, так что я могу не и по крайней мере узнать, что мой код сломался где-то. Вопрос в 2 раза. Во-первых, я правильно использую исключения здесь? Во-вторых, как написать тест для работы с этим исключением? Очевидно, я хочу, чтобы он прошел тест, но в этом случае он собирается выбросить исключение. Не слишком уверен, как это можно было бы с этим справиться. Есть ли другой способ, с которым обычно обрабатывается TDD?

Благодаря

+1

IllegalArgumentException (или эквивалент, если не Java) следует использовать вместо обычного RuntimeException. –

+4

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

ответ

6

Чтобы ответить на ваш первый вопрос:

Если это вполне вероятно, знаменатель аргумент divide будет 0, то вы не должны использовать обработку в ловушку ошибки исключения. Исключения являются дорогостоящими и не должны использоваться для управления потоком программы. Поэтому вы все равно должны проверять, но возвращать код ошибки (или использовать тип с нулевым значением как возвращаемое значение), и ваш код вызова должен проверять это и обрабатывать его соответствующим образом.

public int? divide (int var1, int var2) 
{ 
    if (var2 == 0) 
    { 
     return null; // Calling method must check for this 
    } 
    return var1/var2; 
} 

Если нули действительно исключение - например, не должно быть никакого способа, которым они могут быть переданы, - тогда сделайте так же, как и вы сейчас.

Чтобы ответить на ваш второй вопрос:

В своих методах испытаний, которые проверяют код ошибки вам нужно обработчик исключений:

try 
{ 
    divide (1, 0); 
    // If it gets here the test failed 
} 
catch (RuntimeException ex) 
{ 
    // If it gets here the test passed 
} 
+0

Хмм хороший ответ, спасибо. Как относится к вопросу 1, всегда лучше ли заставить вызывающую функцию проверять ввод перед отправкой? Может быть, это было бы необходимо, несмотря ни на что. Хмм ... –

+0

@ Джон - лучше поставить все свои проверки в одном и том же месте, таким образом, если ваши требования изменятся или вы теперь сможете обрабатывать новые входы, вам нужно только изменить код в одном месте, а не везде, функция низкого уровня называется. – ChrisF

+0

@ Крис Хм хорошо. Я считаю, что это трудная работа, мне нужно провести некоторое исследование о том, как это сделать. Однако большой момент. Спасибо за помощь –

8

Во-первых, ваш первый аргумент (числитель), равным нулю, вероятно, не должен» t вызывают исключение. Ответ должен быть равен нулю. Выбрасывайте исключение только тогда, когда пользователь пытается делить на ноль.

Во-вторых, есть два способа (используя JUnit), чтобы проверить, выбрасываются ли исключения, когда они должны быть. Первый «классический» метод:

@Test 
public void testForExpectedExceptionWithTryCatch() 
     throws Exception { 
    try { 
     divide (1, 0); 
     fail("division by zero should throw an exception!"); 
    } catch (RuntimeException expected) { 
     // this is exactly what you expect so 
     // just ignore it and let the test pass 
    } 
} 

новый метод в JUnit 4 использует аннотации, чтобы сократить объем кода, который нужно написать:

@Test(expected = RuntimeException.class) 
public void testForExpectedExceptionWithAnnotation() 
     throws Exception { 
    divide (1, 0); 
} 

Здесь, потому что мы добавили (expected = RuntimeException.class) к аннотация, тест не удастся, если вызов divideне введите RuntimeException.

+0

Второй пример (JUnit4) является кратким и отлично работает. Спасибо. – 030

0

Я не отвечаю на ваш главный вопрос.

Я бы предложил использовать ArgumentException вместо RuntimeException.

EDIT: Я предполагаю, что .net :)

+0

Я бы даже зашел так далеко, как предлагаю DivideByZeroException :) – Mathias

0

Ваш вопрос зависит от языка, так что мой ответ не может применяться, но NUnit в .NET (и я считаю, JUnit тоже) есть конкретные обозначения для тестирования исключений ,В NUnit, ваш тест будет выглядеть следующим образом:

[Test] 
[ExpectedException(typeof(RuntimeException))] 
public void DivideByZeroShouldThrow() 
{ 
    divide(1,0); 
} 

тест потерпит неудачу, если правильный тип исключения не выброшен во время выполнения.
Подход try/catch работает также и имеет свои преимущества (вы можете точно определить, где вы ожидаете возникновения исключения), но это может оказаться довольно утомительным для записи.

0

На первый вопрос отвечает ChrisF и Bill the Lizard. Я просто хочу добавить альтернативу тесту исключения, с C++ 11 вы можете использовать лямбда непосредственно в своем тесте.

Assert::ExpectException<std::invalid_argument>([] { return divide(1,0); }, "Division by zero should throw an exception."); 

Это эквивалентно:

try 
{ 
divide(1,0); 
Assert::Fail("Division by zero should throw an exception."); 
} 
catch(std::invalid_argument) 
{ 
    //test passed 
} 
Смежные вопросы