Тестирование ввода пользователя, и обработка ошибок
- Декомпозируем проверку ввода
- Добавляем бросание исключений
- Перехватываем исключения
- Тестируем обработку ввода
- Как быть с матрицей
Декомпозируем проверку ввода
Допустим я хочу запросить у пользователя размер матрицы и ввести ее элементы. Сделать это можно так:
static void Main(string[] args)
{
// заведем переменную под размер матрицы
int matrixSize;
while (true) // используем бесконечный цикл чтобы давать пользователю вводить данные пока ответ не станет корректным
{
var input = Console.ReadLine(); // запрашиваем ввод у пользователя
// сначала проверяем ввели ли число
if (!int.TryParse(inputLine, out matrixSize))
{
Console.WriteLine("Вы ввели не число");
continue; // если не число, то вызываем continue чтобы снова отправить юзера на запрос нового значения
}
// теперь проверяем размерность
if (!(matrixSize > 0 && matrixSize < 10))
{
Console.WriteLine("Число должно быть в переделах от 0 до 10");
continue; // если не в пределах вызываем continue чтобы снова отправить юзера на запрос нового значения
}
// если мы попали сюда, значит все ок и можно выйти из цикла
break;
}
Console.ReadKey();
}
как теперь сделать этот код тестируемым?
Для этого нам надо вынести проверки в отдельную функцию. Однако есть проблема, внутри проверок у нас есть ключевое слово continue, которое строго привязано к циклу и может начать нам мешать. Как с этим бороться пока не понятно.
Давайте сначала сделаем функцию из кода проверки
// функция нам должна вернуть обработанное значение, то бишь int
public static int processSizeInput(String inputLine)
{
int matrixSize; // звели переменную
if (!int.TryParse(inputLine, out matrixSize))
{
Console.WriteLine("Вы ввели не число");
continue; // <<< ЭТО БОЛЬШЕ НЕ РАБОТАЕТ
}
// теперь проверяем размерность
if (!(matrixSize > 0 && matrixSize < 10))
{
Console.WriteLine("Число должно быть в переделах от 0 до 10");
continue; // <<< ЭТО БОЛЬШЕ НЕ РАБОТАЕТ
}
return matrixSize;
}
вот эти два continue нам теперь портят всю картинку, такой код даже не скомпилируется.
Добавляем бросание исключений
Нам надо как-то сообщить из функцию что что-то пошло не так. Но как это сделать?
Для этого как нельзя лучше подойдет возможность бросать исключения.
Делается это очень просто, правим код
public static int processSizeInput(String inputLine)
{
int matrixSize;
if (!int.TryParse(inputLine, out matrixSize))
{
/* убираем это
Console.WriteLine("Вы ввели не число");
continue;
и заменяем на:
*/
throw new FormatException("Вы ввели не число");
}
// теперь проверяем размерность
if (!(matrixSize > 0 && matrixSize < 10))
{
/* тут тоже убираем
Console.WriteLine("Число должно быть в переделах от 0 до 10");
continue;
и заменяем:
*/
throw new FormatException("Число должно быть в переделах от 0 до 10");
}
return matrixSize;
}
мы теперь убили двух зайцев, во первых у нас больше нет continue и код успешно скомпилируется, во вторых мы очистили код от вывода значения на Console, что является элементом взаимодействия с пользователем и не должно присутствовать в логике.
Теперь нам надо эту функцию подключить в main. Тут немного хитро.
Перехватываем исключения
Так как мы генерируем исключения надо их перехватывать. Для этого воспользуемся блоком try. Получится как-то так
static void Main(string[] args)
{
int matrixSize;
while (true) // << это не трогаем
{
var input = Console.ReadLine(); // << и это не трогаем
/* А ТУТ ПОШЛА МАГИЯ ~~~ */
try // это слово означает что внутри следующего блока мы будем пытаться поймать ошибку, типа ловушку поставили
{
// тут вызываем нашу функцию которая проверит ввод на корректность
// и может выкинуть ошибку если отправленный input не корректен
// зато если все ввели правильно то в matrixSize окажется размер матрицы
matrixSize = processSizeInput(input);
// это очень важная строчка, если мы дошли до сюда,
// значит ввод был корректен и можно выйти из бесконечного цикла
break;
}
catch (FormatException ex) // ловим ошибку типа FormatException (см. выше мы там делали как раз new FormatException
{
// если мы попали сюда, значит произошла ошибка FormatException
// у ошибки ex, есть свойство сообщение
// это как раз то что мы писали внутри processSizeInput
// то есть если там было throw new FormatException("Вы ввели не число");
// то ex.Message содержит "Вы ввели не число"
// вот мы его и выводим
Console.WriteLine(ex.Message);
// следующую строчку в принцие можно не писать,
// но так нагляднее, типа явно отправляем пользователя на второй круг
continue;
}
}
Console.ReadKey();
}
Тестируем обработку ввода
теперь чтобы можно было протестировать этот код делаем класс публичным:
namespace ConsoleApp3
{
public class Program // добавляем слово public
{
public static int processSizeInput(String inputLine)
{
/* ... */
}
}
// ...
}
и тыкаем правой кнопкой как обычно
далее ок
ну и дальше пишем три теста (так как у нас три варианта исхода для функции)
namespace ConsoleApp3.Tests
{
[TestClass()]
public class ProgramTests
{
[TestMethod()]
public void ProcessSizeInputNotANumberTest()
{
// тут хитрый синтаксис, Assert.ThrowsException -- это метод для проверки что произойдет исключение
// <FormatException> -- это дженерик, который будет проверять что выпадет исключение именно типа FormatException
// () => { /* ... */ } -- это так называемое лямбда выражение, упрощённая форма записи для функции
Assert.ThrowsException<FormatException>(() =>
{
// и внутри этой лямбда-функции мы вызываем метод, который выкинет исключение
Program.processSizeInput("не число");
});
}
}
}
если хочется еще проверить и сообщение, то делаем так
[TestMethod()]
public void ProcessSizeInputNotANumberTest()
{
String message = Assert.ThrowsException<FormatException>(() =>
{
Program.processSizeInput("не число");
}).Message;
Assert.AreEqual("Вы ввели не число", message);
}
второй тест пишем по аналогии, то есть когда число выходит за рамки:
[TestClass()]
public class ProgramTests
{
[TestMethod()]
public void ProcessSizeInputNotANumberTest() { /* ... */ }
[TestMethod()]
public void ProcessSizeInputOutOfLimitsTest()
{
String message = Assert.ThrowsException<FormatException>(() =>
{
Program.processSizeInput("12");
}).Message;
Assert.AreEqual("Число должно быть в переделах от 0 до 10", message);
}
}
ну и третий тест на корректный ввод
[TestMethod()]
public void ProcessSizeInputTest()
{
Assert.AreEqual(5, Program.processSizeInput("5"));
}
Как быть с матрицей
если вы дополнительно запрашиваете элементы матрицы то просто делаете дополнительную функцию а цикл while пихаете внутрь двойного цикла как-то так:
public static int processSizeInput(String inputLine) { /* ... */ }
// добавил новую функцию
public static int processCellInput(String inputLine)
{
if (!int.TryParse(inputLine, out int a))
{
throw new FormatException("Вы ввели не число");
}
return a;
}
static void Main(string[] args)
{
/* ЭТО НЕ ТРОГАЕМ */
int matrixSize;
while (true)
{
var input = Console.ReadLine();
try
{
matrixSize = processSizeInput(input);
break;
}
catch (FormatException ex)
{
Console.WriteLine(ex.Message);
continue;
}
}
/* КОНЕЦ ЭТО НЕ ТРОГАЕМ */
// создаем двумерный массив
var data = new int[matrixSize, matrixSize];
// затем двумерный цикл
for (int i = 0; i < matrixSize; ++i)
{
for (int j = 0; j < matrixSize; ++j)
{
// сообщенице
Console.WriteLine($"Введите [{i}, {j}] элемент");
// а тут все тоже самое что и с запросом размерности, только фиксируем результат в data[i, j]
while(true)
{
var input = Console.ReadLine();
try
{
data[i, j] = processCellInput(input);
break;
}
catch (FormatException ex)
{
Console.WriteLine(ex.Message);
continue;
}
}
}
}
Console.ReadKey();
}
вот такие дела =)