Введение в JavaScript Модульное тестирование

Вы, наверное, знаете, что тестирование это хорошо, но первое препятствие преодолеть, когда пытаются писать тесты для клиентского кода является отсутствие каких-либо фактических единиц; JavaScript код написан для каждой страницы сайта или каждый модульприложения и тесно перемешаны с серверными логика и связанные с HTML.

В худшем случае, код полностью смешивается с HTML, как встроенных событий handlers.

Это, вероятно, случай, когда ни одна библиотека JavaScript для некоторой абстракцией DOM используется; написания встроенных обработчиков событий намного проще, чем при использовании DOM API, чтобы связать эти события.Все больше и больше разработчиков собирание библиотеки, такие как JQuery для обработки DOM абстракции, что позволяет им переместить эти встроенные события в различных скриптов, либо на той же странице или даже в отдельный файл JavaScript.Однако, поставив код в отдельные файлы не означает, что он готов быть проверены как unit.

Что такое блок в любом случае?В лучшем случае, это чистая функция, которую вы можете иметь дело с в некотором роде — функция, которая всегда дает тот же результат для данного входа.Это делает модульное тестирование довольно легко, но большую часть времени вам придется иметь дело с побочными эффектами, которые в данном случае означает DOM манипуляции.Он по-прежнему полезно выяснить, какие единицы мы можем структурировать наш код в и строить модульные тесты accordingly.

Строительство единицы Tests

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

Процесс извлечения кода и ввода его в другой форме, без изменения его текущего поведения, называется рефакторинг.Рефакторинг является отличным способом повышения норм проектирования программы, а также потому, что любое изменение может фактически изменить поведение программы, безопаснее делать, когда юнит-тестов в place.

Это курица или яйцо означает, что для добавления тестов в существующий код, вы должны взять на себя риск ломать вещи.Так что, пока у вас есть твердое покрытие с юнит-тестов, нужно продолжать тестирование вручную, чтобы свести к минимуму, что risk.

Этого должно быть достаточно теория на данный момент.Давайте посмотрим на практическом примере, тестирование некоторый код JavaScript, который в настоящее смешивается с и подключен к странице.Код выглядит для связи с title атрибуты, используя эти названия, чтобы увидеть, когда что-то было опубликовано, так как относительная величина времени, как “5 дней назад”:

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Mangled date examples</title>
	<script>
	function prettyDate(time){
		var date = new Date(time || ""),
			diff = ((new Date().getTime() - date.getTime()) / 1000),
			day_diff = Math.floor(diff / 86400);

		if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
			return;
		}

		return day_diff == 0 && (
				diff < 60 && "just now" ||
				diff < 120 && "1 minute ago" ||
				diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
				diff < 7200 && "1 hour ago" ||
				diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
			day_diff == 1 && "Yesterday" ||
			day_diff < 7 && day_diff + " days ago" ||
			day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
	}
	window.onload = function(){
		var links = document.getElementsByTagName("a");
		for (var i = 0; i < links.length; i++) {
			if (links[i].title) {
				var date = prettyDate(links[i].title);
				if (date) {
					links[i].innerHTML = date;
				}
			}
		}
	};
	</script>
</head>
<body>

<ul>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a>
		by <a href="/john/">John Resig</a>
	</small>
</li>
<!-- more list items -->
</ul>

</body>
</html>

Если вы запускали, что, например, вы увидите проблема: ни одна из дат получить заменить.Код работает, однако.Он перебирает все якоря на страницу и проверяет title собственность на каждого.Если есть, то он передает его в prettyDate функции.Если prettyDate возвращает результат, он обновляет innerHTMLв связи с result.

Сделать Вещи Testable

Проблема заключается в том, что для любой даты старше 31 дней, prettyDate просто возвращает неопределенное (неявно, с одной return заявление), в результате чего текст якоря, как есть.Таким образом, чтобы видеть то, что должно было случиться, мы можем жестко “текущие” Дата:

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Mangled date examples</title>
	<script>
	function prettyDate(now, time){
		var date = new Date(time || ""),
			diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
			day_diff = Math.floor(diff / 86400);

		if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
			return;
		}

		return day_diff == 0 && (
				diff < 60 && "just now" ||
				diff < 120 && "1 minute ago" ||
				diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
				diff < 7200 && "1 hour ago" ||
				diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
			day_diff == 1 && "Yesterday" ||
			day_diff < 7 && day_diff + " days ago" ||
			day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
	}
	window.onload = function(){
		var links = document.getElementsByTagName("a");
		for (var i = 0; i < links.length; i++) {
			if (links[i].title) {
				var date = prettyDate("2008-01-28T22:25:00Z", links[i].title);
				if (date) {
					links[i].innerHTML = date;
				}
			}
		}
	};
	</script>
</head>
<body>

<ul>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a>
		by <a href="/john/">John Resig</a>
	</small>
</li>
<!-- more list items -->
</ul>

</body>
</html>

Сейчас, ссылки должны сказать “2 часа назад”, “Вчера” и так далее.Вот что-то, но до сих пор не фактические проверяемые устройства.Таким образом, без изменения кода далее, все, что мы можем сделать, это попытаться проверить полученную изменения DOM.Даже если бы это сработало, любое небольшое изменение в разметке, скорее всего, сломать теста, в результате чего очень плохо соотношение затрат и выгод для теста, как that.

Рефакторинга, Стадия 0

Вместо этого, давайте рефакторинга кода вполне достаточно, чтобы иметь нечто, что мы можем блока test.

Нам необходимо внести два изменения, чтобы это произошло: передать текущую дату в prettyDate функции в качестве аргумента, вместо того, он просто использует new Date, и извлечь функцию в отдельный файл, так что мы можем включитьКод на отдельной странице на единицу tests.

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Refactored date examples</title>
	<script src="prettydate.js"></script>
	<script>
	window.onload = function() {
		var links = document.getElementsByTagName("a");
		for ( var i = 0; i < links.length; i++ ) {
			if (links[i].title) {
				var date = prettyDate("2008-01-28T22:25:00Z", links[i].title);
				if (date) {
					links[i].innerHTML = date;
				}
			}
		}
	};
	</script>
</head>
<body>

<ul>
<li class="entry" id="post57">
	<p>blah blah blah…</p>
	<small class="extra">
		Posted <a href="/2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a>
		by <a href="/john/">John Resig</a>
	</small>
</li>
<!-- more list items -->
</ul>

</body>
</html>

 

Вот содержимое prettydate.js:

function prettyDate(now, time){
	var date = new Date(time || ""),
		diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
		day_diff = Math.floor(diff / 86400);

	if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
		return;
	}

	return day_diff == 0 && (
			diff < 60 && "just now" ||
			diff < 120 && "1 minute ago" ||
			diff < 3600 && Math.floor( diff / 60 ) + " minutes ago" ||
			diff < 7200 && "1 hour ago" ||
			diff < 86400 && Math.floor( diff / 3600 ) + " hours ago") ||
		day_diff == 1 && "Yesterday" ||
		day_diff < 7 && day_diff + " days ago" ||
		day_diff < 31 && Math.ceil( day_diff / 7 ) + " weeks ago";
}

Теперь у нас есть кое-что, чтобы проверить, давайте напишем некоторые фактические испытания блока:

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Refactored date examples</title>
	<script src="prettydate.js"></script>
	<script>
	function test(then, expected) {
		results.total++;
		var result = prettyDate("2008-01-28T22:25:00Z", then);
		if (result !== expected) {
			results.bad++;
			console.log("Expected " + expected + ", but was " + result);
		}
	}
	var results = {
		total: 0,
		bad: 0
	};
	test("2008/01/28 22:24:30", "just now");
	test("2008/01/28 22:23:30", "1 minute ago");
	test("2008/01/28 21:23:30", "1 hour ago");
	test("2008/01/27 22:23:30", "Yesterday");
	test("2008/01/26 22:23:30", "2 days ago");
	test("2007/01/26 22:23:30", undefined);
	console.log("Of " + results.total + " tests, " + results.bad + " failed, "
		+ (results.total - results.bad) + " passed.");
	</script>
</head>
<body>

</body>
</html>

Это создаст специальную среду тестирования, используя только консоль для вывода.Он не имеет зависимостей для DOM на всех, так что вы можете точно так же запустить его не в среде браузера JavaScript, таких как Node.js или Rhino, путем извлечения кода в script теги собственной file.

Если тест провален, он будет выводить ожидаемые и фактические результаты в этом тесте.В конце концов, он выведет Сводка теста с общим, не удалось, и прошел ряд tests.

Если все тесты прошли, как они должны здесь, вы увидите следующее в консоли:

Из 6 тестов, 0 не удалось, 6 passed.

Чтобы понять, что не удалось утверждение выглядит, мы можем что-то изменить, чтобы разбить его:

Ожидаемые 2 дня назад, но было 2 дня ago.

Из 6 тестов, 1 не удалось, 5 passed.

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

Тест QUnit наличие Suite

Выбор рамки в основном дело вкуса.Для остальной части этой статьи мы будем использовать QUnit (произносится как "Q-единица"), потому что его стиль описания тестов близка к нашей специальной тест framework.

<!DOCTYPE html>
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<title>Refactored date examples</title>

	<link rel="stylesheet" href="qunit.css" />
	<script src="qunit.js"></script>
	<script src="prettydate.js"></script>

	<script>
	test("prettydate basics", function() {
		var now = "2008/01/28 22:25:00";
		equal(prettyDate(now, "2008/01/28 22:24:30"), "just now");
		equal(prettyDate(now, "2008/01/28 22:23:30"), "1 minute ago");
		equal(prettyDate(now, "2008/01/28 21:23:30"), "1 hour ago");
		equal(prettyDate(now, "2008/01/27 22:23:30"), "Yesterday");
		equal(prettyDate(now, "2008/01/26 22:23:30"), "2 days ago");
		equal(prettyDate(now, "2007/01/26 22:23:30"), undefined);
	});
	</script>
</head>
<body>
	<div id="qunit"></div>
</body>
</html>

Три секции стоит присмотреться здесь.Наряду с обычным HTML-шаблона, мы имеем три включаемых файлов: два файла для QUnit (qunit.css и qunit.js) и предыдущего prettydate.js

.

Затем, есть еще один блок сценария с реальными тестами.test метод вызывается один раз, передавая строку в качестве первого аргумента (название теста) и переходя функцию в качестве второго аргумента (который будет работать фактический код для данного теста).Этот код затем определяет now переменную, которая получает повторно ниже, а затем вызывает equal метод несколько раз с разными аргументами.equal метод является одним из нескольких утверждений, что QUnit предоставляет.Первый аргумент является результатом вызова до prettyDate, с now переменной в качестве первого аргумента и date строки в качестве второго.Второй аргумент equal ожидаемый результат.Если два аргумента в equal одни и те же значения, то утверждение будет проходить, в противном случае, она будет fail.

Наконец, в организме элемент некоторого QUnit-разметку.Эти элементы являются необязательными.Если они присутствуют, QUnit будет использовать их для вывода тестового results.

Результат таков:

Если вы хотите прочитать полностью статью, посетите сайт наших спонсоров

Comments are closed.