Тестируем и исправляем калькулятор на 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
Генератор статей для Кода
Генератор статей для Кода

Почти настоящие статьи на цепях Маркова

medium
Пинг-понг против компьютера на JavaScript

Лёгкая версия

easy
Автоматическое оглавление на странице
Автоматическое оглавление на странице

Поручите это машине.

easy
Универсальная аналитическая машина

Она может предсказать что угодно, если правильно её настроить.

medium
Я сделяль: главная страница Яндекса
Я сделяль: главная страница Яндекса

Верстаем чистую страницу поисковика.

medium
Пишем код: как поменять местами значения переменных
Пишем код: как поменять местами значения переменных

Что делать, если третью переменную использовать нельзя.

medium
Как добавить кнопки «Поделиться» на страницу
Как добавить кнопки «Поделиться» на страницу

Простой рецепт от Яндекса.

easy
Прогаем в Экселе: автомобиль в кредит или по подписке?
Прогаем в Экселе: автомобиль в кредит или по подписке?

Программерский подход, подручные средства.

easy
$ is not defined в jQuery: что это значит и что делать
medium
Пишем собственный лапшесниматель
Пишем собственный лапшесниматель

Инструмент для защиты ума от пропаганды, контрпропаганды, инфобизнеса и лжепсихологов.

easy
medium