Skip to main content

Юнит-тесты и регулярные выражения

Posted in

Есть такие задачи, где выгода от юнит-тестов очевидна даже самому упорному тестоотрицателю :)

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

Вики разметка предполагается довольно стандартная, не отличающаяся от большинства вики-систем. Тем не менее, написать и отладить такой парсер без юнит-тестов -- это большой гемор. А с юнит-тестами -- сплошное удовольствие :)

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

Вот как выглядит этот код:


/**
 * @author Yura Zhloba
 */
package com.flashdevs.yzh.wikiParser
{
public class Lexeme
{
	// properties
	private var match : String;
	private var openTag : String;
	private var closeTag : String;

	// constructor
	public function Lexeme(match : String, openTag : String, closeTag : String)
	{
		this.match = match;
		this.openTag = openTag;
		this.closeTag = closeTag;
	}

	// methods
	public function parse(text : String) : String
	{
		var ans : String = '';

		var regExp : RegExp = new RegExp(match, 'gms');
		var res : Object = regExp.exec(text);
		var next : int = 0;
		while(res != null)
		{
			ans += text.substring(next, res.index);
			ans += openTag;
			ans += (res.length > 1) ? res[1] : res[0];
			ans += closeTag;
			next = res.index + res[0].length;

			res = regExp.exec(text);
		}

		ans += text.substring(next);
		return ans;
	}

	public function toString() : String
	{ return 'Lexeme'; }
}
}


/**
 * @author Yura Zhloba
 *
 * parse wiki-text and generate xml-content with TLF
 */
package com.flashdevs.yzh.wikiParser
{
public class WikiParser
{
	static public const BOLD_MATCH : String = '\\*([^\\*]+?)\\*';
	static public const ITALIC_MATCH : String = '_([^\\*]+?)_';

	// properties
	private var lexemes : Array;

	// constructor
	public function WikiParser()
	{
		lexemes = [
			new Lexeme(BOLD_MATCH, '<span fontWeight="bold">', '</span>'),
			new Lexeme(ITALIC_MATCH, '<span fontStyle="italic">', '</span>')
		];
	}

	// methods
	public function parse(data : String) : String
	{
		var res : String = data;
		for each(var lexeme : Lexeme in lexemes) res = lexeme.parse(res);
		return res;
	}

	public function toString() : String
	{ return 'WikiParser'; }
}
}

А вот как выглядят юнит-тесты к нему:


/**
 * @author Yura Zhloba
 */
package
{
import com.flashdevs.yzh.wikiParser.Lexeme;
import com.flashdevs.yzh.wikiParser.WikiParser;

import org.flexunit.Assert;

public class TestWikiParser
{
	[Test]
	public function testLexeme() : void
	{
		var data : String = 'aaa bbb ccc bbb ddd';

		Assert.assertEquals('aaa <open>bbb<close> ccc <open>bbb<close> ddd',
				new Lexeme('bbb', '<open>', '<close>').parse(data));

		Assert.assertEquals('[aaa] [bbb] [ccc] [bbb] [ddd]',
				new Lexeme('\\w+', '[', ']').parse(data));
	}

	[Test]
	public function testBoldLexeme() : void
	{
		var lexeme : Lexeme = new Lexeme(WikiParser.BOLD_MATCH, '<span fontWeight="bold">', '</span>');

		Assert.assertEquals('some <span fontWeight="bold">bold</span> data',
				lexeme.parse('some *bold* data'));

		Assert.assertEquals('and <span fontWeight="bold">here several words</span> in bold',
				lexeme.parse('and *here several words* in bold'));

		Assert.assertEquals('one <span fontWeight="bold">two three</span> four ' +
							'<span fontWeight="bold"> five six </span> seven ' +
							'<span fontWeight="bold">eight</span>',
				lexeme.parse('one *two three* four * five six * seven *eight*'));
	}

	[Test]
	public function testBoldAndItalicLexemes() : void
	{
		var boldLexeme : Lexeme = new Lexeme(WikiParser.BOLD_MATCH, '<span fontWeight="bold">', '</span>');
		var italicLexeme : Lexeme = new Lexeme(WikiParser.ITALIC_MATCH, '<span fontStyle="italic">', '</span>');

		var data : String = 'here *bold text* and _some italic text_ and *bold and _italic inside_ it*';
		var res1 : String = italicLexeme.parse(boldLexeme.parse(data));
		var res2 : String = boldLexeme.parse(italicLexeme.parse(data));

		Assert.assertEquals(res1, res2);

		Assert.assertEquals('here <span fontWeight="bold">bold text</span> ' +
							'and <span fontStyle="italic">some italic text</span> ' +
							'and <span fontWeight="bold">bold ' +
							'and <span fontStyle="italic">italic inside</span> it</span>', res1);
	}

	[Test]
	public function testWikiParser() : void
	{
		var data : String = 'here *bold text* and _some italic text_ and *bold and _italic inside_ it*';
		var parser : WikiParser = new WikiParser();

		Assert.assertEquals('here <span fontWeight="bold">bold text</span> ' +
							'and <span fontStyle="italic">some italic text</span> ' +
							'and <span fontWeight="bold">bold ' +
							'and <span fontStyle="italic">italic inside</span> it</span>',
				parser.parse(data));
	}
}
}

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

Начинаем с простых вариантов, добиваемся правильности работы, а потом усложняем, не боясь, что все сломается.

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

PROFIT :)

No votes yet