Тестируем и исправляем калькулятор на JavaScript

В нём много ошибок, но мы их пофиксим

Тестируем и исправляем калькулятор на JavaScript

Эта статья — продолжение истории про калькулятор на JavaScript. В предыдущих частях мы:

Теперь наш калькулятор будут смотреть инженеры по тестированию — постараются найти в нём ошибки логики, поведения, вычислений и прочие неявные моменты.

👉 В этой статье не будет автотестов, юнит-тестов, API-тестов и интеграционных тестов. Это всё нужные инструменты, но задача тестировщика, в частности, — выбрать правильный инструмент тестирования.

Если у него в работе очень простая программа (как наш калькулятор), то нет смысла городить автоматизацию и делать тесты ради тестов. Иногда можно и вручную всё проверить и получить точно такой же результат.

Базовое тестирование

Первое, что мы делаем, — проверяем, а как вообще ведут себя кнопки математических действий и делают ли они то, что нужно. Возьмём два числа — 12 и 5 — и сравним результаты всех действий с тем, что даёт калькулятор:

12 + 5 = 17

12 − 5 = 7

12 × 5 = 60

12 / 5 = 2,4

Отрицательные числа тоже отображаются и считаются правильно.

Тестируем большие числа

У компьютеров есть нюанс: любые переменные имеют ограничения по размеру числа. Например, если на переменную выделено 16 бит, то максимальное число, которое можно в нее положить, — 65 536. Число на единицу больше уже потребует 17 бит, а мы столько не выделяли.

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

Пробуем: 123 456 789 × 2 = 246 913 578 — верно

А вот необычный эксперимент:

12 345 678 901 234 567 × 1 = 12 345 678 901 234 568

Ух ты! Мы умножили большое число на единицу, а в ответе появилась ошибка. Это значит, что настолько длинные числа за раз наш калькулятор уже обработать не в состоянии.

Записываем баг:

❌ Неправильно обрабатываются 17-значные числа и те, которые больше них.

А если мы попробуем получить 17-значное число в ответе, интересно, оно тоже будет с ошибкой?

Тестируем и исправляем калькулятор на JavaScript

Да, в ответе тоже неверное число — 8 × 4 = 32, поэтому в конце должно стоять 2, а не 0. Пишем баг:

❌ Если в ответе получается 17-значное число или более — ответ точно неверный.

При этом деление на 16-значное число работает верно:

Тестируем и исправляем калькулятор на JavaScript

Тестирование математических трюков

Теперь попробуем разделить на ноль:

Тестируем и исправляем калькулятор на JavaScript

Скрипт хитро выкрутился и записал результатом деления бесконечность. Но лучше выводить сообщение, что на ноль делить нельзя.

❌ Нет сообщения при делении на ноль.

Отказоустойчивость

А что если оставить поле ввода пустым и попробовать что-то посчитать? Давайте посмотрим:

Тестируем и исправляем калькулятор на JavaScript

Скрипт преобразовал пустую строку в ноль и получил ответ, но это неправильно — при отсутствии одного из чисел калькулятор должен сообщить об этом, а не продолжать считать.

❌ Нет сообщения, если одно из чисел не введено.

Пойдём дальше и введём слово вместо числа:

Тестируем и исправляем калькулятор на JavaScript

Скрипт честно пытается перевести строку в число, у него это не получается, поэтому он выдаёт неопределённое значение.

❌ Нет проверки на то, ввели число или строку.

И напоследок проверим что будет, если мы что-то введём, но не выберем ни одно действие:

Тестируем и исправляем калькулятор на JavaScript

Тоже плохо. Надо будет обработать такую ситуацию.

❌ Нет проверки, когда не выбрали ничего из математических действий.

Так проверяем работу калькулятора со всеми действиями, а не только с умножением. В итоге у нас получится список ошибок, которые нужно исправить.

Что делаем

После тестирования у нас получился такой список ошибок:

❌ Неправильно обрабатываются 17-значные числа и те, которые больше них.

❌ Если в ответе получается 17-значное число или более — ответ точно неверный.

❌ Нет сообщения при делении на ноль.

❌ Нет сообщения, если одно из чисел не введено.

❌ Нет проверки на то, ввели число или строку.

❌ Нет проверки, когда не выбрали ничего из математических действий.

Исправим эти ошибки. Так как все вычисления начинаются при вызове функции func (), то и править всё будем тоже внутри неё.

Проверяем, что ввели число, а не слова или другие символы

Для перевода строку в число в JavaScript используют функцию Number(). Если строку можно перевести в число — она сработает без ошибок, а если не получится — вернёт значение NaN. Это значит, что числа не получилось.Чтобы проверить, прошло всё нормально или нет, мы будем использовать функцию isNaN() — она сравнит значение переменной с NaN, и вернёт true, если в переменной лежит NaN. А чтобы не путать числа и строки для сравнения, переименуем переменные в самом начале скрипта и сделаем их принудительно строками:

// получаем первое и второе число
var num1_str = String(document.getElementById("num1").value);
var num2_str = String(document.getElementById("num2").value);

// переводим строки в числа
let num1 = Number(num1_str)
let num2 = Number(num2_str)

// проверяем, получилось ли число из первой строки или нет
if (isNaN(num1)) {
	// если не получилось — пишем сообщение
	document.getElementById("result").innerHTML = 'Калькулятор не может распознать первое число. Проверьте его, пожалуйста';
	// и выходим из функции
	return;
}

// проверяем, получилось ли число из второй строки или нет
if (isNaN(num2)) {
	// если не получилось — пишем сообщение
	document.getElementById("result").innerHTML = 'Калькулятор не может распознать второе число. Проверьте его, пожалуйста';
	// и выходим из функции
	return;
} 
Тестируем и исправляем калькулятор на JavaScript

Проверяем, что нет пустых значений

JavaScript когда переводит строку в число, то пустую строку он считает как 0. Нам такой вариант не подходит, поэтому сравним её с пустой сторокой. Если она пустая — выдаём сообщение и ничего не считаем.

Ещё надо дополнительно добавить проверку на пробелы — JavaScript строку из пробелов тоже переводит как ноль, а нам это не нужно:

// получаем первое и второе число
var num1_str = String(document.getElementById("num1").value);
var num2_str = String(document.getElementById("num2").value);

// проверяем, не пустая ли первая строка
if ((num1_str.length == 0) || (num1_str.indexOf(' ') != -1)) {
	// если пустая — пишем сообщение
	document.getElementById("result").innerHTML = 'Вы не ввели первое число или добавили пробел в поле ввода';
	// и выходим из функции
	return;
}

// проверяем, не пустая ли вторая строка
if ((num2_str.length == 0) || (num2_str.indexOf(' ') != -1)) {
	// если пустая — пишем сообщение
	document.getElementById("result").innerHTML = 'Вы не ввели второе число или добавили пробел в поле ввода';
	// и выходим из функции
	return;
}
Тестируем и исправляем калькулятор на JavaScript

Обрабатываем деление на ноль

Простая проверка — добавляем сравнение второго числа с нулём:

// проверяем второе число при делении
if ((num2 == 0) && (op == '/')) {
	// если не получилось — пишем сообщение
	document.getElementById("result").innerHTML = 'На ноль делить нельзя';
	// и выходим из функции
	return;
}
Тестируем и исправляем калькулятор на JavaScript

Обрабатываем длинные числа

Даже если мы ограничим каждое поле ввода числами по 16 знаков вместо 17, то при перемножении они дадут нам в ответе 32 знака — а это тоже превышает наш предел точности. Чтобы гарантированно получить в ответе число не больше 16 разрядов перед запятой, нам нужно, чтобы оба числа были не больше 99 999 999 — в нём 8 разрядов, а при перемножении мы получим максимум 16, как раз то, что нужно.

Чтобы это сделать, добавим проверку на размер числа:

// проверяем размер чисел
if ((num1 > 99999999) || (num2 > 99999999)) {
	// если не помещается одно из них в диапазон — пишем сообщение
	document.getElementById("result").innerHTML = 'Калькулятор может работать с числами не больше 99 999 999';
	// и выходим из функции
	return;
}
Тестируем и исправляем калькулятор на JavaScript

Если не выбрано математическое действие

С этим всё просто — добавляем в case действие по умолчанию, которое выполнится, если никакие из вариантов не подойдут:

// смотрим, что было в переменной с действием, и действуем исходя из этого
switch (op) {
  case '+':
    result = num1 + num2;
    break;
  case '-':
    result = num1 - num2;
    break;
  case '*':
    result = num1 * num2;
    break;
  case '/':
    result = num1 / num2;
    break;
  default: result = 'Выберите действие'
}
Тестируем и исправляем калькулятор на JavaScript

В итоге

✅ Калькулятор не работает с числами больше 16 знаков до запятой и предупреждает об этом пользователя

✅ В ответе всегда число, в котором не больше 16 знаков до запятой

✅ Есть проверка деления на ноль

✅ Есть сообщение, если одно из чисел не введено.

✅ Есть проверка на то, ввели число или строку.

✅ Есть проверка, когда не выбрали ничего из математических действий.

Это всё?

О нет, этот калькулятор можно гонять ещё и в хвост и в гриву:

  • Протестировать десятичные дроби и операции с ними.
  • Вставлять в поля ввода изображения и файлы.
  • Устраивать переполнение буфера браузера.
  • Совершать 10 миллионов вычислений в секунду.
  • Запускать одновременно 10 миллионов калькуляторов.
  • Запустить калькулятор в 1911 году.
  • Засунуть в него комплексные числа.
  • Засунуть в него самое большое простое число (и разделить).
  • Засунуть в него кота.

Это (и многое другое) — и есть работа тестировщика. Круто, да?

Приходите учиться на тестировщиков
в «Практикум» →

И ни одна кошка не пострадает.

Готовый код
<!DOCTYPE html>
<html lang="ru">
<head>
	<meta charset="utf-8">
	<title>Размеры шрифтов</title>

	<style type="text/css">
		/*задаём общие параметры для всей страницы: шрифт и отступы*/
		body {
		  text-align: center;
		  margin: 10;
		  font-family: Verdana, Arial, sans-serif;
		  font-size: 16px;
		}
		/* настраиваем внешний вид полей ввода*/
		input {
		  display: inline-block;
		  margin: 20px auto;
		  border: 2px solid #eee;
		  padding: 10px 20px;
		  font-family: Verdana, Arial, sans-serif;
		  font-size: 16px;
		}
		/* внешний вид кнопок */
		button{
		  font-family: Verdana, Arial, sans-serif;
		  font-size: 16px;
		  margin: 10px;
		  padding: 10px;
		}
		/* стиль подсветки выбранной операции */
		.light{
			background-color: yellow;
		}
	</style>

</head>
<body>

	<!-- заголовок -->
	<h1>Калькулятор</h1>
	<!-- поле ввода первого числа -->
	<input id="num1" />

	<!-- блок с кнопками -->
	<div id="operator_btns">
	  <button id="plus" onclick="sel_ligth('plus')">+</button>
	  <button id="minus" onclick="sel_ligth('minus')">-</button>
	  <button id="times" onclick="sel_ligth('times')">x</button>
	  <button id="divide" onclick="sel_ligth('divide')">:</button>
	</div>

	<!-- поле ввода второго числа -->
	<input id="num2" />
	<br>

	<!-- кнопка для расчётов -->
	<button onclick="func()">Посчитать</button>

	<!-- здесь будет результат -->
	<p id="result"></p>

	<!-- наш скрипт -->
	<script>
	  // переменная, в которой хранится выбранное математическое действие
	  var op; 

	  // функция, которая подсветит выбранное математическое действие
	  function sel_ligth(sel_id) {
	  	// убираем класс подсветки со всех кнопок
	  	document.getElementById("plus").classList.remove("light");
	  	document.getElementById("minus").classList.remove("light");
	  	document.getElementById("times").classList.remove("light");
	  	document.getElementById("divide").classList.remove("light");

	  	// и добавляем его только к нажатой
	  	document.getElementById(sel_id).classList.add("light");

	  	// в зависимости от нажатой клавиши меняем значение переменной op
	  	switch (sel_id) {
	  	  case "plus":
	  	    op = "+"
	  	    break;
	  	  case 'minus':
	  	    op = '-'
	  	    break;
	  	  case 'times':
	  	    op = "*"
	  	    break;
	  	  case 'divide':
	  	    op = "/"
	  	    break;
	  	}
	  }

	  // добавляем обработчик нажатия на клавиши ко второму полю ввода
      document.getElementById("num2").addEventListener('keydown', function(e) {
		if (e.keyCode === 13) {
		  func();
		}
	  });
	  
	  // функция расчёта
	  function func() {
	  	// переменная для результата
	    var result;
	    // получаем первое и второе число
	    var num1_str = String(document.getElementById("num1").value);
	    var num2_str = String(document.getElementById("num2").value);

	    // проверяем, не пустая ли первая строка
	    if ((num1_str.length == 0) || (num1_str.indexOf(' ') != -1)) {
	    	// если пустая — пишем сообщение
	    	document.getElementById("result").innerHTML = 'Вы не ввели первое число или добавили пробел в поле ввода';
	    	// и выходим из функции
	    	return;
	    }

	    // проверяем, не пустая ли вторая строка
	    if ((num2_str.length == 0) || (num2_str.indexOf(' ') != -1)) {
	    	// если пустая — пишем сообщение
	    	document.getElementById("result").innerHTML = 'Вы не ввели второе число или добавили пробел в поле ввода';
	    	// и выходим из функции
	    	return;
	    }

	    // переводим строки в числа
	    let num1 = Number(num1_str)
	    let num2 = Number(num2_str)


	    // проверяем, получилось ли число из первой строки или нет
	    if (isNaN(num1)) {
	    	// если не получилось — пишем сообщение
	    	document.getElementById("result").innerHTML = 'Калькулятор не может распознать первое число. Проверьте его, пожалуйста';
	    	// и выходим из функции
	    	return;
	    }

	    // проверяем, получилось ли число из второй строки или нет
	    if (isNaN(num2)) {
	    	// если не получилось — пишем сообщение
	    	document.getElementById("result").innerHTML = 'Калькулятор не может распознать второе число. Проверьте его, пожалуйста';
	    	// и выходим из функции
	    	return;
	    } 

	    // проверяем размер чисел
	    if ((num1 > 99999999) || (num2 > 99999999)) {
	    	// если не помещается одно из них в диапазон — пишем сообщение
	    	document.getElementById("result").innerHTML = 'Калькулятор может работать с числами не больше 99 999 999';
	    	// и выходим из функции
	    	return;
	    }

	     // проверяем второе число при делении
	    if ((num2 == 0) && (op == '/')) {
	    	// если не получилось — пишем сообщение
	    	document.getElementById("result").innerHTML = 'На ноль делить нельзя';
	    	// и выходим из функции
	    	return;
	    }

	    // смотрим, что было в переменной с действием, и действуем исходя из этого
	    switch (op) {
	      case '+':
	        result = num1 + num2;
	        break;
	      case '-':
	        result = num1 - num2;
	        break;
	      case '*':
	        result = num1 * num2;
	        break;
	      case '/':
	        result = num1 / num2;
	        break;
	      default: result = 'Выберите действие'
	    }

	    // отправляем результат на страницу
	    document.getElementById("result").innerHTML = result;
	  }
	</script>

</body>
</html>

Посмотреть работу калькулятора на странице проекта.

Что дальше

Кажется, что теперь в самом коде есть что улучшить. В следующей серии займёмся рефакторингом. Не переключайтесь.

Вам может быть интересно
Что означает ошибка IndentationError: expected an indented block
Что означает ошибка IndentationError: expected an indented block

Самая популярная ошибка у начинающих программистов на Python.

easy
Проверяем текст в Главреде
Проверяем текст в Главреде

Добавляем новые возможности через API.

medium
Красивые ссылки… с анимацией!
Красивые ссылки… с анимацией!

Невероятные… Фантастические… Ни капельки не бесящие.

easy
Прокачиваем игру на Python c уворачиванием от предметов
Прокачиваем игру на Python c уворачиванием от предметов

Добавляем главный экран, подсчёт очков и перезапуск

hard
Что означает ошибка TypeError: can only concatenate str (not «int») to str
Что означает ошибка TypeError: can only concatenate str (not «int») to str

Это значит, что вы пытаетесь сложить строки с числами

easy
Рисуем лабиринты любой сложности
Рисуем лабиринты любой сложности

Вообще любой сложности, какой захотите

hard
Делаем свой кредитный калькулятор на Python
Делаем свой кредитный калькулятор на Python

Простой, но полезный

easy
Генератор лабиринтов
Генератор лабиринтов

Рисуем лабиринты любого размера.

hard
WebGL: Отбрасываем реалистичные тени прямо в браузере
WebGL: Отбрасываем реалистичные тени прямо в браузере

Разбор сложного проекта, который показывает возможности современного веба

medium
medium

Художник:

Даня Берковский

Корректор:

Ирина Михеева

Вёрстка:

Кирилл Климентьев

Соцсети:

Олег Вешкурцев