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

Тестируем и исправляем калькулятор на 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-значное число в ответе, интересно, оно тоже будет с ошибкой?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Что делаем

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

❌ Неправильно обрабатываются 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 когда переводит строку в число, то пустую строку он считает как 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;
}

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

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

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

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

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

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

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

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

С этим всё просто — добавляем в 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 = 'Выберите действие'
}

В итоге

✅ Калькулятор не работает с числами больше 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

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

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

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

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

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

easy
Крутой веб-проект: симуляция нормального распределения на JavaScript

Физика, статистика и математика в одном мощном проекте

medium
Что означает ошибка ReferenceError: Can't find variable

Ошибка в JavaScript: что она значит и что делать

easy
Простейший математический фокус

Можно использовать для пикапа или на пьяных вечеринках

easy
Отладка Python-кода с помощью pdb

В сложных программах это лучше, чем print()

hard
Автоматизируем новости

И другие нелинейные процессы принятия решений.

easy
Учим компьютер складывать числа любой длины

Человек умеет складывать в столбик, а компьютер — нет

medium
medium
[anycomment]
Exit mobile version