Spectrum — PHP фреймворк для BDD тестирования (alpha версия)

mkharitonov

Новичок
Spectrum — это PHP фреймворк, предназначенный для так называемых specification тестов (аналог RSpec и т.п.) и предоставляющий довольно богатые возможности по настройке и расширению.

Небольшой пример:
PHP:
<?php
require_once 'spectrum/application/init.php';

describe('Космический корабль', function(){
    it('Должен бороздить просторы вселенной', function(){
        $spaceship = new Spaceship();
        actual($spaceship->getLocation())->beEq('space');
    });

    it('Не должен прохлаждаться', function(){
        $spaceship = new Spaceship();
        actual($spaceship->getTask())->not->beEq('foo');
    });
});

\net\mkharitonov\spectrum\RootDescribe::run();
Подробная документация с примерами находится по адресу: http://mkharitonov.net/spectrum/

Скачать исходные коды можно с http://github.com/mkharitonov/spectrum/
Текущая версия пока еще не стабильна, будет дорабатываться и в реальных проектах к использованию пока не рекомендуется.

Предложения и пожелания можно оставлять в комментариях к данной теме или отправлять мне на [email protected].
 

mkharitonov

Новичок
Нет.
Behat — аналог Cucumber (для функционального тестирования)
Spectrum — аналог RSpec (для модульного тестирования)
 

varan

Б̈́̈̽ͮͣ̈Л̩̲̮̻̤̹͓ДͦЖ̯̙̭̥̑͆А͇̠̱͓͇̾ͨД͙͈̰̳͈͛ͅ
Можно ламмерский вопрос?
А чем это лучше чем
PHP:
if ($spaceship->getLocation() != 'space')
    $errors[] = 'Должен бороздить просторы вселенной';

if ($spaceship->getTask() == 'foo')
    $errors[] = 'Не должен прохлаждаться';
В чем выгода такого описания с помощью describe(), it() и проч?
 

Koc

Новичок
скорее даже так)

PHP:
// phpunit
$this->assertEquals($spaceship->getLocation(), 'space', 'Должен бороздить просторы вселенной');
 

mkharitonov

Новичок
Можно ламмерский вопрос?
А чем это лучше чем
PHP:
if ($spaceship->getLocation() != 'space')
    $errors[] = 'Должен бороздить просторы вселенной';

if ($spaceship->getTask() == 'foo')
    $errors[] = 'Не должен прохлаждаться';
В чем выгода такого описания с помощью describe(), it() и проч?

Вообще, Вас стоит в целом почитать о TDD и BDD, что бы таких вопросов не задавать. Но, вкратце, я поясню.

Если Вы собираетесь ограничиться написанием нескольких простых тестов, то думать о фреймворке для тестирования Вам не обязательно. В противном случае (если кол-во тестов будет исчисляться хотя бы десятками), Вам необходимо будет решить, как минимум, следующие проблемы:
— Обеспечение независимости выполнения каждого теста (иначе у Вас какой-либо тест из сотни испортит значение переменной и Вы будете тратить драгоценное время на то, что бы найти причину того, почему тест не работает (в худшем случае, Вы этого даже не заметите и тест не будет проверять то, что должен));
— Устранение дублирований в коде (что бы протестировать какой-либо класс, надо написать код для создания и инициализации экземпляра этого класса, настройки окружения, других связанных с ним классов и т.п., а если Вы хотите обеспечить независимость выполнения тестов, то для каждого теста Вам надо будет заново выполнять этот код, т.е. выносить его в общие функции и т.п.);
— Стандартизирование стиля написание тесов (что бы тесты мог сопровождать и дописывать кто-либо кроме Вас, не превращая постепенно все это в кашу, которая в итоге будет проверять не понятно что);
— Плюс при отладке конкретного теста Вам потребуется выполнять много раз только его (не запуская тучу других тестов), а в Вашем примере сделать это кроме, как закомментировать лишние тесты, нельзя (чем грозит изменение остального кода тестов я объяснять не буду).

Решив хотя бы эти проблемы, Вы и получите свой фреймворк для тестирования.

Вообще суть BDD в том, что бы описать как должна работать программа в виде списка требований (возможно составленных вместе с заказчиком) и к каждому такому требованию «прикрепить» тестовый код, который будет проверять правильность реализации данного требования. Для этого в BDD и используются слова describe и it. Например, заказчик говорит, что корзина заказов на сайте интернет-магазина должна работать следующим образом:
— Если пользователь зарегистрирован, то должет добавляться товар в корзину;
— Если товар является телефоном, то должно выдаваться сообщение о том, что можно так же приобрести подключение у сотового оператора;
— Если пользователь не зарегистрирован, то должно выдаваться сообщение с просьбой зарегистрироваться;
— Так же должна заноситься запись в лог.

Вот Вы и описываете эти требования в виде describe/it/context:
PHP:
describe('Корзина заказов', function(){
	beforeEach(function(){
		// Тут размещаем код создания экземпляра корзины, пользователя, 
		// настройки окружения и т.п. И весь этот код будет выполняться 
		// заново перед каждым it(), обеспечивая независимость каждого теста.
	});

	describe('Пользователь зарегистрирован', function(){
		describe('Добавление товара в корзину', function(){
			describe('Товар является мобильным телефоном', function(){
				it('Должен добавиться товар в корзину', function(){});
				it('Должно выдаваться сообщение о том, что можно приобрести подключение', function(){});
			});

			describe('Остальные типы товаров', function(){
				it('Должен добавиться товар в корзину', function(){});
				it('Не должно выдаваться никаких сообщений', function(){});
			});
		});

		// А с помощью контекстов можно это описать еще короче, избавившись 
		// от необходимости сопровождать два (а в реальности, скорей всего, далеко
		// не два) идентичных теста в разном окружении
		describe('Добавление товара в корзину', function(){
			it('Должен добавиться товар в корзину', function(){});
			context('Товар является мобильным телефоном', function(){
				it('Должно выдаваться сообщение о том, что можно приобрести подключение', function(){});
			});

			context('Остальные типы товаров', function(){
				it('Не должно выдаваться никаких сообщений', function(){});
			});
		});
	});

	describe('Пользователь не зарегистрирован', function(){
		describe('Добавление товара в корзину', function(){
			it('Должно выдаваться сообщение с просьбой зарегистрироваться', function(){});
			it('Не должно добавиться товаров в корзину', function(){});
			it('Должна заноситься запись в лог', function(){});
		});
	});
});
Надеюсь понятно, что в реальности кол-во подобных тестов будет больше и сложность сопровождение будет возрастать с каждым лишнем дублированием.
 

mkharitonov

Новичок
скорее даже так)

PHP:
// phpunit
$this->assertEquals($spaceship->getLocation(), 'space', 'Должен бороздить просторы вселенной');
PHPUnit, как минимум, не позволяет записывать названия тестов на русском языке (если Вы собитаетесь следовать правилу один тест — один метод и не именовать тестовые методы test1, test2 и т.п.). Плюс Вы ограничены одномерным размещением тестовых методов (в противном случае, Вам придется либо именовать методы как testName_Subname_Subname_Should, что снижает читабельность при больших объемах, либо разбивать тестовый класс на подклассы, что снижает скорость написания тестов).
 

fixxxer

К.О.
Партнер клуба
Так те же яйца вид сбоку, не? Что тут, что там подход со стороны юзкейсов.

Ну или я не понимаю, какие проблемы. Методы по русски нельзя, да, это серьезная проблема лол
 

mkharitonov

Новичок
Так те же яйца вид сбоку, не? Что тут, что там подход со стороны юзкейсов.
Ну или я не понимаю, какие проблемы. Методы по русски нельзя, да, это серьезная проблема лол
Как минимум, более удобная организация фикстур (их намного проще применить к группе методов) и возможность выполнить одни и те же тесты в разных фикстурах без лишних телодвижений, а так же более естественная организация тестов в BDD стиле.

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

fixxxer

К.О.
Партнер клуба
Да что ты обижаешься то. Ничего личного же. Да и тут не институт благородных девиц =)

Может и правда твой вариант удобнее - на примере абстрактных сферических коней неясно.

Меня наоборот вот смущает что будет стопицот вложенных describe-context, и может получиться каша из вложенных структур. На первый взгляд (на первый!) это очень похоже на антипаттерн, когда в один тест пихается всё подряд.

А вот пример тестирования чего-то реального, вида "было-стало" (слева PhpUnit_Story_TestCase, справа Spectrum) был бы очень показателен и нагляден.
 
  • Like
Реакции: atv

mkharitonov

Новичок
Да что ты обижаешься то. Ничего личного же. Да и тут не институт благородных девиц =)
Это не означает, что не надо проявлять элементарного уважения в чужому труду (а фразы, вроде, «Зачем ты это сделал?! Кому это нужно?!» (сказанные, к тому же, без особого ознакомления с данным трудом) хоть немного, но задевают).

Меня наоборот вот смущает что будет стопицот вложенных describe-context, и может получиться каша из вложенных структур. На первый взгляд (на первый!) это очень похоже на антипаттерн, когда в один тест пихается всё подряд.
А какая альтернитива? И в обоих случает кол-во сущностей будет одинаковым, только в одном случае это будут только вложенные describe/it (которые можно, между прочим, и на отдельные файлы разбить), а в другом либо подряд идущие методы с именами вида testName_Subname_Should (с ограничениями по применению к ним фикстур), либо несколько разных классов (которые, опять же, не понятно как организовывать), либо смесь из того и другого.

Может и правда твой вариант удобнее - на примере абстрактных сферических коней неясно.

А вот пример тестирования чего-то реального, вида "было-стало" (слева PhpUnit_Story_TestCase, справа Spectrum) был бы очень показателен и нагляден.
К сожалению, сейчас у меня нет достаточно времени на написание более реалистичных примеров и различных сравнений, поэтому я и не хочу затрагивать это в данной теме. К тому же, мне казалось, что большинству здесь и так известно, что BDD является дальнейшим развитием TDD, а значит и проводить различные сравнения в данном случае особого смысла не имеет.
 

fixxxer

К.О.
Партнер клуба
фразы, вроде, «Зачем ты это сделал?! Кому это нужно?!»
Вовсе нет, фразы вроде "А чем это лучше, чем foo?".

BDD является дальнейшим развитием TDD
1) "А ФП лучше ООП." :D Разные вещи ведь, на разном акценты.

2) http://www.phpunit.de/manual/3.6/en/behaviour-driven-development.html

А какая альтернитива?
Да вот же она.

Я не придираюсь, мне правда интересно, чем jquery style удобнее. По опыту работы с JS-фреймворками, все эти вложенные функции в подобном стиле образуют дикую нечитаемую кашу. А ведь тест - это еще и документация.
 

whirlwind

TDD infected, paranoid
Чего-то я не понимаю, а почему тесты на phpunit? Сделайте самотестированием и всем сразу станет понятно как это работает на практике. Сферические корабли в вакууме из одного класса мало кому понятны.

Вам придется либо именовать методы как testName_Subname_Subname_Should, что снижает читабельность при больших объемах, либо разбивать тестовый класс на подклассы, что снижает скорость написания тестов).
Откройте для себя параметризованные тесты.
 

mkharitonov

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

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

Откройте для себя параметризованные тесты.
PHPUnit поддерживает параметризованные тесты? Да и как параметризованные тесты помогут избавиться от указанных мной необходимостей?

Я не придираюсь, мне правда интересно, чем jquery style удобнее. По опыту работы с JS-фреймворками, все эти вложенные функции в подобном стиле образуют дикую нечитаемую кашу. А ведь тест — это еще и документация.
Мне не слишком понятно, как из каши тестов, написанных на BDD фреймворке, можно сделать что-то более аппетитное, если переписать эти тесты на TDD фреймворке. Да и какая разница, если в обеих случаях тесты являются функциями?
В phpunit это будет запись вида (название метода специально взял из Zend Framework'а)
PHP:
public function testGetControllerDirectoryByModuleNameReturnsNullOnBadModule(){}
А в spectrum'е это будет:
PHP:
it('Get controller directory by module name returns null on bad module', function(){});
Только в случае с PHPUnit надо будет потанцевать с бубном, если для нескольких методов потребуется создать отдельную фикстуру (мне часто такое требовалось).

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

Но если уж действительно без наглядного примера сложно понять, то можете привести примеры тестов на PHPUnit (желательно нетривиальных — тех, где приходилось создавать несколько тестовых классов, использовать параметризованные тесы, выносить фикстуры во вспомогательные функции, использовать scenario стиль и т.п.) — я перепишу их на spectrum'е и Вы сами сделаете для себя вывод.
 

tz-lom

Продвинутый новичок
Но если уж действительно без наглядного примера сложно понять, то можете привести примеры тестов на PHPUnit (желательно нетривиальных — тех, где приходилось создавать несколько тестовых классов, использовать параметризованные тесы, выносить фикстуры во вспомогательные функции, использовать scenario стиль и т.п.) — я перепишу их на spectrum'е и Вы сами сделаете для себя вывод.
https://github.com/tz-lom/TrustedForms/blob/master/selenium/tests/TestValidators.php
 

atv

Новичок
Только в случае с PHPUnit надо будет потанцевать с бубном, если для нескольких методов потребуется создать отдельную фикстуру
Если Вы об этом? http://www.phpunit.de/manual/3.6/en/fixtures.html, то зачем выносить фикстуру из метода, если для каждого она своя? Когда всё рядом, то и нагляднее получается. Ну и потом, PHPUnit это ООП-шный фрэймворк со всеми вытекающими, т.е. его легко расширять, переопределять и т.д.
 

whirlwind

TDD infected, paranoid
Прежде чем браться за улучшение какой то определенной технологии, было бы неплохо хорошенько узнать плюсы и минусы предшественников. BDD вполне себе средство для разработки функциональных тестов: взяли use case и задекларировали его на BDD. Но какое влияние функциональные тесты оказывают на дизайн приложения? К тому же есть более серьезные проблемы, которые не решаются ни в рамках TDD ни рамках BDD.
 
Сверху