Тестовые примеры из белого ящика со 100% покрытием кода

#unit-testing #testing #code-coverage #white-box-testing

#модульное тестирование #тестирование #покрытие кода #тестирование в белом ящике

Вопрос:

Заранее приношу извинения, поскольку я новичок в тестировании программного обеспечения. Но у меня есть то, что выглядит как простой код для создания тестовых примеров из белого ящика со 100% покрытием кода:

 01 public class ShapeAreas {
02
03 public double oneParameter(String shape, float x1)
04 {
05     float area;
06     if (shape.equals("A"))
07         return x1 * x1 * Math.XI;
08     else if (shape.equals("B"))
09         return x1 * x1;
10     else 
11         return -1.0;
12 }
13 
14 public double twoParameter(String shape, float x1, float x2)
15 {
16     float area;
17     if (shape.equals("N"))
18         return x1 * x2;
19     else if (shape.equals("M"))
20         return 0.5 * x1 * x2;
21     else
22         return -1.0;
23 }
24 }
  

Мне нужна помощь в том, как должны выглядеть мои входные данные в этом коде, чтобы достичь 100% покрытия кода с наименьшим количеством тестовых примеров.

Я ценю любую помощь, которую я могу получить в этом, спасибо!

Комментарии:

1. Есть какие-либо отзывы по предоставленному ответу @jaggsharry? Это то, что вы искали?

2. Не могли бы вы уточнить, какой тип покрытия вас интересует? Покрытие инструкции, покрытие ветви, MCDC, покрытие пути? Это упражнение из курса, или ваша реальная цель — научиться создавать высококачественные наборы тестов?

3. @DirkHerrmann заявление о покрытии, моя цель — научиться создавать высококачественные наборы тестов. Спасибо!

4. @Dan_Maff большое спасибо за ответ. У меня есть дополнительный вопрос, в типичном сценарии, какими будут входные данные и ожидаемые результаты для этого тестового примера для любого метода? Еще раз спасибо!

5. Как упоминалось в ответе, вам в основном придется создать 3 тестовых примера для каждого метода с одним из заданных shape значений параметра для каждого. float Параметры не имеют значения с точки зрения покрытия.

Ответ №1:

Я позволил себе добавить номера строк в ваш код, чтобы иметь возможность дать лучшее объяснение. В комментариях вы упомянули, что вас интересует покрытие оператора. Инструкции в ваших примерах кода находятся в строках 07, 09 и 11, а также в 18, 20 и 22. И, конечно, if инструкции сами по себе также являются инструкциями (отсюда и название), но они будут выполняться в любом случае при каждом выполнении соответствующей функции.

За одно выполнение функции oneParameter будет выполнен ровно один из условных операторов: либо в строке 07, либо в строке 09, либо в строке 11. Это из-за эксклюзивного характера if-else if-else инструкции. Аналогично, при одном вызове функции twoParameter будет выполнена либо инструкция в строке 18, либо в строке 20, либо в строке 22.

Следовательно, чтобы охватить все инструкции, вам нужно вызвать каждую функцию три раза. Аргумент, который управляет фактической принятой ветвью, в обоих случаях является shape аргументом. Это означает, что значение других аргументов не имеет отношения к тому, какая инструкция будет выполнена. Тривиальный набор вызовов может быть:

 oneParameter("A", 0.0);
oneParameter("B", 0.0);
oneParameter("any other", 0.0);
twoParameter("N", 0.0, 0.0);
twoParameter("M", 0.0, 0.0);
twoParameter("any other", 0.0, 0.0);
  

Это пример минимального набора вызовов, который позволил бы достичь 100% покрытия оператора. Что может быть удивительным, так это то, что это просто вызовы — нет даже какой-либо оценки результатов. Когда мы говорим о тестовом покрытии, подразумевается, что соответствующие строки кода не просто выполняются, но что соответствующие результаты оцениваются как часть теста. Тогда это могло бы выглядеть следующим образом:

 assertEquals(0.0, oneParameter("A", 0.0));
assertEquals(0.0, oneParameter("B", 0.0));
assertEquals(-1.0, oneParameter("any other", 0.0));
assertEquals(0.0, twoParameter("N", 0.0, 0.0));
assertEquals(0.0, twoParameter("M", 0.0, 0.0));
assertEquals(-1.0, twoParameter("any other", 0.0, 0.0));
  

Несмотря на то, что теперь он имеет 100% покрытие инструкций, а также выполняет оценку результатов, он все еще далек от того, чтобы быть высококачественным набором тестов. Причина в следующем: модульное тестирование обычно выполняется с основной целью поиска ошибок в коде. Приведенный выше набор тестов, однако, не очень подходит для обнаружения каких-либо интересных ошибок. Чтобы дать вам несколько примеров возможных ошибок, которые этот набор тестов не обнаружит:

  • По ошибке вычисления в function oneParameter for "A" и "B" были заменены.
  • Функция twoParameter для "N" операции * была ошибочно заменена .
  • В функции twoParameter for "N" вместо умножения x1 на x2 выражение умножается x1 на x1 .
  • В функции twoParameter для "M" константы было установлено значение 0.05 вместо правильного 0.5 .

Это всего лишь примеры возможных ошибок. Поэтому серьезное отношение к цели поиска возможных ошибок требует думать не только о покрытии. На самом деле, могут даже быть части кода, которые вообще не подходят для тестирования с помощью модульного тестирования — хотя в вашем простом примере это не так. Примерами для таких частей кода являются недоступный код (например, ветвь инструкции switch по умолчанию, добавленная для надежности) или код, который состоит только из взаимодействий с другими компонентами, и в этом случае интеграционное тестирование является более подходящим подходом. Поэтому цель достичь 100% покрытия часто не имеет смысла, даже если вы искренне стремитесь к набору модульных тестов отличного качества.

Тем не менее, покрытие является ценной информацией для разработчиков, которые заинтересованы в том, чтобы убедиться, что они рассмотрели все соответствующие сценарии в своем коде. К сожалению, покрытие гораздо менее ценно, когда дело доходит до оценки качества набора тестов с точки зрения управления: в этом контексте оно часто сводится к простому проценту, и разработчики вынуждены создавать тесты до тех пор, пока не будет достигнут определенный процент покрытия — но часто достаточно без предоставления им достаточного обучения, времени и поощрения для проведения тестирования должным образом. Как следствие, для достижения покрытия, скажем, 80%, весь тривиальный код (средства получения, настройки и т.п.) может быть «протестирован» для увеличения покрытия. Тестирование наиболее сложных и труднотестируемых 20% кода, однако, откладывается из-за нехватки времени (хотя, скорее всего, именно в этом коде скрыты ошибки). И даже те 80%, которые были покрыты, могут быть плохо протестированы, как с минимальным набором тестов, который я показал выше.

Ответ №2:

Вы должны искать ответвления в каждом из методов, чтобы получить 100% покрытие. Это A , B X как shape параметр для oneParameter метода и N , M X для twoParameter метода.

3 тестовых примера для каждого метода дадут вам 100% покрытие.

Однако это не скажет вам, что ваш код на 100% корректен.

Попробуйте, например, null в виде формы. (Что привело бы к NullpointerException )

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

 public void testOneParameterWithShapeB() {
  double result = sut.oneParameter("B", 1.0);
  //TODO check the result
}