Skip to content
Browse files

initial

  • Loading branch information...
0 parents commit 111d08289a16493068c9d62b7b6c6c58daffc758 Davert committed
Showing with 11,080 additions and 0 deletions.
  1. +17 −0 autoload.php
  2. +17 −0 codecept
  3. +19 −0 codeception.yml
  4. +12 −0 config/codeception.yml
  5. +147 −0 readme.md
  6. +67 −0 src/Codeception/AbstractGuy.php
  7. +147 −0 src/Codeception/Codecept.php
  8. +33 −0 src/Codeception/Command/Base.php
  9. +149 −0 src/Codeception/Command/Bootstrap.php
  10. +82 −0 src/Codeception/Command/Build.php
  11. +58 −0 src/Codeception/Command/GenerateScenarios.php
  12. +43 −0 src/Codeception/Command/Install.php
  13. +78 −0 src/Codeception/Command/Run.php
  14. +7 −0 src/Codeception/Exception/Configuration.php
  15. +14 −0 src/Codeception/Exception/Module.php
  16. +13 −0 src/Codeception/Exception/ModuleConfig.php
  17. +7 −0 src/Codeception/Exception/TestRuntime.php
  18. +91 −0 src/Codeception/Module.php
  19. +32 −0 src/Codeception/Module/Cli.php
  20. +65 −0 src/Codeception/Module/Db.php
  21. +34 −0 src/Codeception/Module/Filesystem.php
  22. +14 −0 src/Codeception/Module/Framework.php
  23. +76 −0 src/Codeception/Module/PhpBrowser.php
  24. +273 −0 src/Codeception/Module/Unit.php
  25. +67 −0 src/Codeception/Output.php
  26. +37 −0 src/Codeception/ResultPrinter.php
  27. +164 −0 src/Codeception/ResultPrinter/HTML.php
  28. +48 −0 src/Codeception/ResultPrinter/Report.php
  29. +116 −0 src/Codeception/ResultPrinter/UI.php
  30. +13 −0 src/Codeception/ResultPrinter/template/scenario.html.dist
  31. +2 −0 src/Codeception/ResultPrinter/template/scenario_header.html.dist
  32. +141 −0 src/Codeception/ResultPrinter/template/scenarios.html.dist
  33. +4 −0 src/Codeception/ResultPrinter/template/step.html.dist
  34. +105 −0 src/Codeception/Runner.php
  35. +91 −0 src/Codeception/Scenario.php
  36. +104 −0 src/Codeception/Step.php
  37. +10 −0 src/Codeception/Step/Action.php
  38. +10 −0 src/Codeception/Step/Assertion.php
  39. +14 −0 src/Codeception/Step/Comment.php
  40. +10 −0 src/Codeception/Step/Condition.php
  41. +137 −0 src/Codeception/SuiteManager.php
  42. +172 −0 src/Codeception/TestCase.php
  43. +138 −0 src/Codeception/Util/Mink.php
  44. +13 −0 src/Codeception/Util/Stub/Builder.php
  45. +8 −0 src/Codeception/Util/Stub/Stub.php
  46. +64 −0 src/Codeception/Util/Stub/builders/phpunit/AbstractStub.php
  47. +14 −0 src/Codeception/Util/Stub/builders/phpunit/EmptyStub.php
  48. +60 −0 src/Codeception/Util/Stub/builders/phpunit/Stub.php
  49. +1 −0 tests/_data/dump.sql
  50. +98 −0 tests/_log/codeception-2011-11-28.log
  51. +1,278 −0 tests/_log/codeception-2011-11-30.log
  52. +17 −0 tests/acceptance.suite.yml
  53. +3 −0 tests/acceptance/SampleSpec.php
  54. +39 −0 tests/acceptance/WebGuy.php
  55. +2 −0 tests/acceptance/_bootstrap.php
  56. +9 −0 tests/functional.suite.yml
  57. +3 −0 tests/functional/SampleSpec.php
  58. +22 −0 tests/functional/TestGuy.php
  59. +2 −0 tests/functional/_bootstrap.php
  60. +8 −0 tests/helpers/CodeHelper.php
  61. +8 −0 tests/helpers/TestHelper.php
  62. +8 −0 tests/helpers/WebHelper.php
  63. +6 −0 tests/unit.suite.yml
  64. +34 −0 tests/unit/CodeGuy.php
  65. +10 −0 tests/unit/SampleSpec.php
  66. +10 −0 tests/unit/Scenario/runSpec.php
  67. +2 −0 tests/unit/_bootstrap.php
  68. +36 −0 vendor/Monolog/Formatter/FormatterInterface.php
  69. +40 −0 vendor/Monolog/Formatter/JsonFormatter.php
  70. +105 −0 vendor/Monolog/Formatter/LineFormatter.php
  71. +87 −0 vendor/Monolog/Formatter/WildfireFormatter.php
  72. +169 −0 vendor/Monolog/Handler/AbstractHandler.php
  73. +70 −0 vendor/Monolog/Handler/AbstractProcessingHandler.php
  74. +67 −0 vendor/Monolog/Handler/BufferHandler.php
  75. +95 −0 vendor/Monolog/Handler/FingersCrossedHandler.php
  76. +161 −0 vendor/Monolog/Handler/FirePHPHandler.php
  77. +74 −0 vendor/Monolog/Handler/GroupHandler.php
  78. +77 −0 vendor/Monolog/Handler/HandlerInterface.php
  79. +54 −0 vendor/Monolog/Handler/MailHandler.php
  80. +49 −0 vendor/Monolog/Handler/NativeMailerHandler.php
  81. +37 −0 vendor/Monolog/Handler/NullHandler.php
  82. +109 −0 vendor/Monolog/Handler/RotatingFileHandler.php
  83. +71 −0 vendor/Monolog/Handler/StreamHandler.php
  84. +55 −0 vendor/Monolog/Handler/SwiftMailerHandler.php
  85. +108 −0 vendor/Monolog/Handler/SyslogHandler.php
  86. +120 −0 vendor/Monolog/Handler/TestHandler.php
  87. +394 −0 vendor/Monolog/Logger.php
  88. +58 −0 vendor/Monolog/Processor/IntrospectionProcessor.php
  89. +40 −0 vendor/Monolog/Processor/MemoryPeakUsageProcessor.php
  90. +50 −0 vendor/Monolog/Processor/MemoryProcessor.php
  91. +40 −0 vendor/Monolog/Processor/MemoryUsageProcessor.php
  92. +60 −0 vendor/Monolog/Processor/WebProcessor.php
  93. +888 −0 vendor/Symfony/Component/Console/Application.php
  94. +591 −0 vendor/Symfony/Component/Console/Command/Command.php
  95. +81 −0 vendor/Symfony/Component/Console/Command/HelpCommand.php
  96. +67 −0 vendor/Symfony/Component/Console/Command/ListCommand.php
  97. +190 −0 vendor/Symfony/Component/Console/Formatter/OutputFormatter.php
  98. +83 −0 vendor/Symfony/Component/Console/Formatter/OutputFormatterInterface.php
  99. +209 −0 vendor/Symfony/Component/Console/Formatter/OutputFormatterStyle.php
  100. +72 −0 vendor/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php
  101. +137 −0 vendor/Symfony/Component/Console/Helper/DialogHelper.php
  102. +97 −0 vendor/Symfony/Component/Console/Helper/FormatterHelper.php
  103. +42 −0 vendor/Symfony/Component/Console/Helper/Helper.php
  104. +49 −0 vendor/Symfony/Component/Console/Helper/HelperInterface.php
  105. +102 −0 vendor/Symfony/Component/Console/Helper/HelperSet.php
  106. +307 −0 vendor/Symfony/Component/Console/Input/ArgvInput.php
  107. +190 −0 vendor/Symfony/Component/Console/Input/ArrayInput.php
  108. +211 −0 vendor/Symfony/Component/Console/Input/Input.php
  109. +132 −0 vendor/Symfony/Component/Console/Input/InputArgument.php
  110. +524 −0 vendor/Symfony/Component/Console/Input/InputDefinition.php
  111. +103 −0 vendor/Symfony/Component/Console/Input/InputInterface.php
  112. +182 −0 vendor/Symfony/Component/Console/Input/InputOption.php
Sorry, we could not display the entire diff because it was too big.
17 autoload.php
@@ -0,0 +1,17 @@
+<?php
+
+require_once 'vendor/UniversalClassLoader.php';
+
+$loader = new UniversalClassLoader();
+$loader->registerNamespaces(array(
+ 'Codeception' => __DIR__ . '/src',
+ 'Monolog' => __DIR__ . '/vendor',
+ 'Symfony\Component' => __DIR__ . '/vendor',
+));
+
+$loader->register();
+$loader->registerNamespaceFallbacks(array(__DIR__.'/vendor/Mink/vendor'));
+
+include_once 'PHPUnit/Autoload.php';
+include_once 'mink/autoload.php';
+// require_once __DIR__.'/src/BaseTestGuy.php';
17 codecept
@@ -0,0 +1,17 @@
+#!/usr/bin/env php
+<?php
+
+require_once 'autoload.php';
+
+use Symfony\Component\Console\Application,
+ Symfony\Component\Console\Input\InputInterface,
+ Symfony\Component\Console\Input\InputDefinition,
+ Symfony\Component\Console\Input\InputOption;
+
+ $app = new Application('Codeception', Codeception\Codecept::VERSION);
+ $app->add(new Codeception\Command\Build('build'));
+ $app->add(new Codeception\Command\Run('run'));
+ $app->add(new Codeception\Command\Install('install'));
+ $app->add(new Codeception\Command\Bootstrap('bootstrap'));
+ $app->add(new Codeception\Command\GenerateScenarios('generate-scenarios'));
+ $app->run();
19 codeception.yml
@@ -0,0 +1,19 @@
+paths:
+ tests: tests
+ output: tests/_log
+ helpers: tests/helpers
+settings:
+ bootstrap: _bootstrap.php
+ suite_class: \PHPUnit_Framework_TestSuite
+ colors: true
+ silent: false
+ memory_limit: 1024M
+ log: true
+ log_max_files: 10
+modules:
+ config:
+ Db:
+ dsn: ''
+ user: ''
+ password: ''
+ dump: tests/_data/dump.sql
12 config/codeception.yml
@@ -0,0 +1,12 @@
+paths:
+ tests: tests
+ output: tests/_log
+ helpers: tests/helpers
+ modules: tests/modules
+settings:
+ bootstrap: _bootstrap.php
+ suite_class: \PHPUnit_Framework_TestSuite
+ colors: true
+ silent: false
+ memory_limit: 1024M
+ silent: false
147 readme.md
@@ -0,0 +1,147 @@
+# TestGuy Standalone
+## Functional Testing Framework
+
+TestGuy is a functional testing framework powered by PHPUnit.
+Designed make tests easy to write, read, and debug.
+
+## Principles
+TestGuy library allows to write test scenarios in PHP with DSL designed to look like native English.
+Imagine your tester describes her actions and you write them as a functional tests!
+Tester can perform some actions and see the results. Whenever she doesn't see expected value the test fails.
+This is how testers act. And this is the same how TestGuy is acting.
+
+TestGuy knows a little about internals of your application. When error occur it won't tell you which module triggered this error.
+Still it makes you confident that your app is still running correctly and users can perform the same scenarios the TestGuy does.
+
+Cover your application with functional tests and let them stay simple to read, simple to write, and simple to debug.
+Use TestGuy!
+
+## In a Glance
+This is the sample TestGuy test. User is accessing the site to create a new wiki page about the movie.
+He gets to 'new' page, submits form, and sees the page he just created. Also he performs additional checks, if the slug is generated and if the database record is saved.
+
+``` php
+<?php
+
+$I = new TestGuy($scenario);
+$I->wantTo('create wiki page');
+$I->amOnPage('/');
+$I->click('Pages');
+$I->click('New');
+$I->see('New Page');
+$I->submitForm('#pageForm', array('page' => array(
+ 'title' => 'Tree of Life Movie Review',
+ 'body' => 'Next time don\'t let Hollywood create arthouse =) '
+)));
+$I->see('page created'); // notice generated
+$I->see('Tree of Life Movie Review','h1'); // head of page of is our title
+$I->seeInCurrentAddress('pages/tree-of-life-mobie-review'); // slug is generated
+$I->seeInDatabase('pages', array('title' => 'Tree of Life Movie Review')); // data is stored in database
+
+```
+
+## About
+
+TestGuy uses PHPUnit (http://http://www.phpunit.de/) as backend for testing framework. If you are familiar with PHPUnit you can your TestGuy installation with it's features.
+Also TestGuy uses Mink (http://mink.behat.org/) a powerful library that provides interface for browser emulators.
+TestGuy was developed as symfony1 plugin and now it's migrated to standalone version. You can test any project with it!
+
+## Install
+
+If you need stable standalone version download [https://github.com/downloads/DavertMik/TestGuy_Standalone/testguy.phar](phar package).
+
+Put it wherever you expect to store your test suites.
+
+Install TestGuy dependencies.
+
+```
+php testguy.phar install
+```
+
+Generate empty test suite
+
+````
+php testguy.phar init
+````
+
+That will create a 'tests' directory with a sample suite inside it.
+By default suite will be configured to test web sites with Mink.
+Configuration is stored in ```tests/testguy/suites.yml````.
+
+
+Build TestGuy class
+
+````
+php testguy.phar build
+````
+
+Then your suite is ready to run first test file.
+
+````
+php testguy.phar run
+````
+
+You will see the result:
+
+````
+Starting app...
+TestGuy 0.7 running with modules: Cli, Filesystem.
+Powered by PHPUnit 3.5.5 by Sebastian Bergmann.
+
+
+# Trying to test some feature of my app (SampleSpec.php) - ok
+
+Time: 0 seconds, Memory: 8.75Mb
+
+OK (1 test, 0 assertions)
+````
+
+## Writing tests
+
+Each test belongs to suite. You can have several suites for different parts of your application.
+By default the 'app' test suite is crated and stored into ````tests/testguy/app````.
+Inside of it you will see a first test file: ````SampleSpec.php````
+
+TestGuy tests should be placed in suite directory and should be ended with 'Spec.php'.
+
+Tests should always start with this lines:
+
+``` php
+<?php
+$I = new TestGuy($scenario);
+$I->wantTo('actions you are going to perform');
+```
+
+$I - is a magical object. It stores all actions you can perform. Just type ```$I->``` in your IDE and you will see what actions you can execute.
+For instance, the Web module is connected and you can open browser on specific page and test the expected result.
+
+``` php
+<?php
+$I = new TestGuy($scenario);
+$I->wantTo('see if registration page is here');
+$I->amOnPage('/register');
+$I->see('Registration');
+```
+
+ALl methods of $I object are taken from TestGuy modules. There are not much of them, but you can write your own.
+The most powerful module is Web module, it allows you to test wep sites with headless browser.
+You can connect as many modules as you like and use all them together.
+
+The detailed information on modules and configurations you can see [https://github.com/DavertMik/TestGuy_Modules](here)
+
+## Testing Methods
+The method names in $I object are designed to be easy to understand their meaning.
+There are 3 types of methods:
+
+* Conditions: start with ```am```. They specify the starting conditions. For example: ```$I->amOnPage('/login');```
+* Assertions: start with ```see``` or ```dontSee```. They define the expected result and makes a test fail if result is not see.
+* Actions: all other actions. They change current application state. For example: ```$I->click('Signin');``` moves user to sign in page.
+
+## Sample tests
+You can look at sample tests here, in /tests/ dir.
+
+### License
+MIT
+
+(c) Michael Bodnarchuk "Davert"
+2011
67 src/Codeception/AbstractGuy.php
@@ -0,0 +1,67 @@
+<?php
+namespace Codeception;
+
+abstract class AbstractGuy {
+ public static $methods = array();
+
+ /**
+ * @var \Codeception\Scenario
+ */
+ protected $scenario;
+
+ public function __construct(\Codeception\Scenario $scenario) {
+ $this->scenario = $scenario;
+
+ foreach (\Codeception\SuiteManager::$modules as $module) {
+ $module->_cleanup();
+ }
+ }
+
+ public function wantToTest($text) {
+ $this->scenario->setFeature(strtolower("test $text"));
+ }
+
+ public function wantTo($text) {
+ $this->scenario->setFeature(strtolower($text));
+ }
+
+ public function amTestingClass($text) {
+ $this->scenario->setFeature($text);
+ }
+
+ public function amTestingMethod($method) {
+ $this->testMethod($method);
+ }
+
+ public function testMethod($signature) {
+ if (!$this->scenario->getFeature()) {
+ $this->scenario->setFeature("execute method $signature()");
+ } else {
+ $this->scenario->setFeature($this->scenario->getFeature() . " with [[$signature]]");
+ }
+ $this->scenario->when(array_merge(array('testMethod', $signature)));
+ }
+
+ public function expectTo($prediction) {
+ $this->scenario->comment(array('expect to '.$prediction));
+ }
+
+ public function amGoingTo($argumentation) {
+ $this->scenario->comment(array('am going to '.$argumentation));
+ }
+
+ public function __call($method, $args) {
+ // if (!in_array($method, array_keys(TestGuy::$methods))) throw new \RuntimeException("Action $method not defined");
+// foreach ($args as $k => $arg) {
+// if (is_object($arg)) $args[$k] = clone($arg);
+// }
+ if (0 === strpos($method,'see')) {
+ $this->scenario->then(array_merge(array($method) ,$args));
+ } elseif (0 === strpos($method,'am')) {
+ $this->scenario->given(array_merge(array($method) ,$args));
+ } else {
+ $this->scenario->when(array_merge(array($method),$args));
+ }
+ }
+
+}
147 src/Codeception/Codecept.php
@@ -0,0 +1,147 @@
+<?php
+namespace Codeception;
+
+use \Symfony\Component\Yaml\Yaml;
+use Symfony\Component\Finder\Finder;
+
+class Codecept
+{
+ const VERSION = "1.01a";
+
+ /**
+ * @var \Codeception\Runner
+ */
+ protected $runner;
+ /**
+ * @var \PHPUnit_Framework_TestResult
+ */
+ protected $result;
+
+ /**
+ * @var \Monolog\Handler\StreamHandler
+ */
+ protected $logHandler;
+
+ /**
+ * @var array
+ */
+ protected $options = array(
+ 'silent' => false,
+ 'debug' => false,
+ 'html' => false,
+ 'report' => false,
+ 'colors' => true,
+ 'log' => true
+ );
+
+ public function __construct($config = array(), $options = array()) {
+ $this->runner = new \Codeception\Runner();
+ $this->result = new \PHPUnit_Framework_TestResult;
+ $this->config = $config;
+ $this->options = $this->mergeOptions($options);
+ $this->path = $this->config['paths']['tests'];
+ $this->output = new Output((bool)$this->options['silent'], (bool)$this->options['colors']);
+ $this->logHandler = new \Monolog\Handler\RotatingFileHandler($this->config['paths']['output'].'/codeception.log', $this->config['settings']['log_max_files']);
+
+ }
+
+ private function mergeOptions($options) {
+ foreach ($options as $option => $value) {
+ if (isset($this->config['settings'][$option])) {
+ if (!$value && $this->config['settings'][$option]) $value = $this->config['settings'][$option];
+ }
+ // no colors on windows
+ if ($option == 'colors' && !$options['colors'] && strtoupper(substr(PHP_OS, 0, 3) == 'WIN')) {
+ $value = false;
+ }
+ $options[$option] = $value;
+ }
+ if ($options['report']) {
+ $options['silent'] = true;
+ }
+ $options['verbose'] = !$options['silent'];
+ if ($options['html']) $options['html'] = $this->config['paths']['output'] . '/result.html';
+ return $options;
+ }
+
+ public static function loadConfiguration()
+ {
+ $config = file_exists('codeception.yml') ? Yaml::parse('codeception.yml') : array();
+ $distConfig = file_exists('codeception.dist.yml') ? Yaml::parse('codeception.dist.yml') : array();
+ $config = array_merge($distConfig, $config);
+
+ if (!isset($config['paths'])) throw new \Codeception\Exception\Configuration('Paths not defined');
+
+ if (isset($config['paths']['helpers'])) {
+ // Helpers
+ $helpers = Finder::create()->files()->name('*Helper.php')->in($config['paths']['helpers']);
+ foreach ($helpers as $helper) include_once($helper);
+ }
+
+ if (!isset($config['suites'])) {
+ $suites = Finder::create()->files()->name('*.suite.yml')->in($config['paths']['tests']);
+ $config['suites'] = array();
+ foreach ($suites as $suite) {
+ preg_match('~\/(.*?)(\.suite|\.suite\.dist)\.yml~', $suite, $matches);
+ $config['suites'][] = $matches[1];
+ }
+ }
+ return $config;
+ }
+
+
+ public function runSuite($suite, $settings, $test = null) {
+ $class = $settings['suite_class'];
+ if (!class_exists($class)) throw new \RuntimeException("Suite class for $suite not found");
+
+ if (file_exists($testguy = sprintf('%s/%s/%s.php', $this->path, $suite, $settings['class_name']))) {
+ require_once $testguy;
+ }
+ if (!class_exists($settings['class_name'])) throw new \RuntimeException("No guys were found in $testguy. Tried to find {$settings['class_name']} but he was not there.");
+
+ \Codeception\SuiteManager::init($settings);
+
+ $testManager = new \Codeception\SuiteManager(new $class, $this->options['debug']);
+ if (isset($settings['bootstrap'])) $testManager->setBootstrtap($settings['bootstrap']);
+
+ if ($test) {
+ $testManager->loadTest($test, $this->path.'/'.$suite);
+ } else {
+ $testManager->loadTests($this->path.'/'.$suite);
+ }
+ $tests = $testManager->getCurrentSuite()->tests();
+
+ foreach ($tests as $test) {
+ if (method_exists($test, 'setOutput')) $test->setOutput($this->output);
+ if (method_exists($test, 'setLogHandler')) $test->setLogHandler($this->logHandler);
+ }
+
+ $this->runner->doRun($testManager->getCurrentSuite(), $this->result, array_merge(array('convertErrorsToExceptions' => false), $this->options));
+ return $this->result;
+ }
+
+ public static function versionString() {
+ return 'Codeception PHP Testing Framework v'.self::VERSION;
+ }
+
+ /**
+ * @return \Codeception\Runner
+ */
+ public function getRunner()
+ {
+ return $this->runner;
+ }
+
+ /**
+ * @return \PHPUnit_Framework_TestResult
+ */
+ public function getResult()
+ {
+ return $this->result;
+ }
+
+ public function getOptions() {
+ return $this->options;
+ }
+
+}
33 src/Codeception/Command/Base.php
@@ -0,0 +1,33 @@
+<?php
+namespace Codeception\Command;
+
+use \Symfony\Component\Yaml\Yaml;
+
+class Base extends \Symfony\Component\Console\Command\Command
+{
+ protected $config;
+ protected $suites = array();
+ protected $tests_path;
+
+ protected function initCodeception()
+ {
+ $this->config = \Codeception\Codecept::loadConfiguration();
+ $this->tests_path = $this->config['paths']['tests'];
+
+ if (isset($this->config['suites'])) {
+
+ $globalConf = $this->config['settings'];
+ $moduleConf = array('modules' => isset($this->config['modules']) ? $this->config['modules'] : array());
+
+
+ foreach ($this->config['suites'] as $suite) {
+
+ $suiteConf = file_exists($this->tests_path . "/$suite.suite.yml") ? Yaml::parse($this->tests_path . "/$suite.suite.yml") : array();
+ $suiteDistconf = file_exists($this->tests_path . "/$suite.suite.dist.yml") ? Yaml::parse($this->tests_path . "/$suite.suite.dist.yml") : array();
+
+ $this->suites[$suite] = array_merge($globalConf, $moduleConf, $suiteDistconf, $suiteConf);
+ }
+ }
+ }
+
+}
149 src/Codeception/Command/Bootstrap.php
@@ -0,0 +1,149 @@
+<?php
+namespace Codeception\Command;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Helper\DialogHelper;
+use Symfony\Component\Yaml\Yaml;
+
+class Bootstrap extends \Symfony\Component\Console\Command\Command
+{
+
+ public function getDescription()
+ {
+ return 'Initializes empty test suite and default configuration file';
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output)
+ {
+
+ if (file_exists('codeception.yml')) {
+ $output->writeln("<error>\nProject already initialized here\n</error>");
+ return;
+ }
+
+ $basicConfig = array(
+ 'paths' => array(
+ 'tests' => 'tests',
+ 'output' => 'tests/_log',
+ 'helpers' => 'tests/helpers',
+ 'modules' => 'tests/modules'
+ ),
+ 'settings' => array(
+ 'bootstrap' => '_bootstrap.php',
+ 'suite_class' => '\PHPUnit_Framework_TestSuite',
+ 'colors' => true,
+ 'memory_limit' => '1024M'
+ ),
+ 'modules' => array('config' => array(
+ 'Db' => array(
+ 'dsn' => '',
+ 'user' => '',
+ 'password' => '',
+ 'dump' => 'tests/_data/dump.sql'
+ )
+ )
+ )
+ );
+
+ $str = Yaml::dump($basicConfig, 4);
+ file_put_contents('codeception.yml', $str);
+
+ $output->writeln("File codeception.yml written - global configuration");
+
+ @mkdir('tests');
+ @mkdir('tests/functional');
+ @mkdir('tests/unit');
+ @mkdir('tests/acceptance');
+ @mkdir('tests/helpers');
+ @mkdir('tests/_log');
+ @mkdir('tests/_data');
+
+ $output->writeln("tests/unit created - unit tests");
+ $output->writeln("tests/functional created - functional tests");
+ $output->writeln("tests/acceptance created - acceptance tests");
+
+ file_put_contents('tests/_data/dump.sql', '/* Replace this file with actual dump of your database */');
+
+ file_put_contents('tests/unit/_bootstrap.php', "<?php\n// Here you can initialize variables that will for your tests\n");
+ file_put_contents('tests/functional/_bootstrap.php', "<?php\n// Here you can initialize variables that will for your tests\n");
+ file_put_contents('tests/acceptance/_bootstrap.php', "<?php\n// Here you can initialize variables that will for your tests\n");
+
+
+ file_put_contents('tests/helpers/CodeHelper.php', "<?php\nnamespace Codeception\\Module;\n\n// here you can define custom functions for CodeGuy \n\nclass CodeHelper extends \\Codeception\\Module\n{\n}\n");
+ file_put_contents('tests/helpers/TestHelper.php', "<?php\nnamespace Codeception\\Module;\n\n// here you can define custom functions for TestGuy \n\nclass TestHelper extends \\Codeception\\Module\n{\n}\n");
+ file_put_contents('tests/helpers/WebHelper.php', "<?php\nnamespace Codeception\\Module;\n\n// here you can define custom functions for WebGuy \n\nclass WebHelper extends \\Codeception\\Module\n{\n}\n");
+
+ file_put_contents('tests/unit/SampleSpec.php', "<?php\n\$I = new CodeGuy(\$scenario);\n\$I->wantTo('test a code specification');");
+ file_put_contents('tests/functional/SampleSpec.php', "<?php\n\$I = new TestGuy(\$scenario);\n\$I->wantTo('test an integration feature');");
+ file_put_contents('tests/acceptance/SampleSpec.php', "<?php\n\$I = new WebGuy(\$scenario);\n\$I->wantTo('test an application feature in a web browser');");
+
+ // CodeGuy
+ $conf = array(
+ 'class_name' => 'CodeGuy',
+ 'modules' => array('enabled' => array('Unit','CodeHelper')),
+
+ );
+
+ $firstline = $str = "# Codeception Test Suite Configuration\n\n";
+ $str .= "# suite for unit (internal) tests.\n";
+ $str .= Yaml::dump($conf, 2);
+
+ file_put_contents('tests/unit.suite.yml', $str);
+ $output->writeln("tests/unit.suite.yml written - unit tests suite configuration");
+
+
+ // CodeGuy
+ $suiteConfig = array(
+ 'class_name' => 'TestGuy',
+ 'modules' => array('enabled' => array('Filesystem', 'TestHelper')),
+ );
+
+ $str = $firstline;
+ $str .= "# suite for functional (integration) tests.\n";
+ $str .= "# emulate web requests and make application process them.\n";
+ $str .= "# (tip: better to use with frameworks).\n\n";
+ $str .= Yaml::dump($suiteConfig, 2);
+
+ file_put_contents('tests/functional.suite.yml', $str);
+ $output->writeln("tests/functional.suite.yml written - functional tests suite configuration");
+
+
+
+ $suiteConfig = array(
+ 'class_name' => 'WebGuy',
+ 'modules' => array(
+ 'enabled' => array('PhpBrowser','WebHelper'),
+ 'config' => array(
+ 'PhpBrowser' => array(
+ 'start' => 'http://localhost/myapp/',
+ 'output' => 'tests/_log'
+ ),
+ )
+ ),
+ );
+
+ $str = $firstline;
+ $str .= "# suite for acceptance tests.\n";
+ $str .= "# perform tests in browser using the Selenium-like tools.\n";
+ $str .= "# powered by Mink (http://mink.behat.org).\n";
+ $str .= "# (tip: that's what your customer will see).\n";
+ $str .= "# (tip: test your ajax and javascript by one of Mink drivers).\n\n";
+
+ $str .= Yaml::dump($suiteConfig, 5);
+ file_put_contents('tests/acceptance.suite.yml', $str);
+ $output->writeln("tests/acceptance.suite.yml written - acceptance tests suite configuration");
+
+ $output->writeln("<info>\nBootstrap is done. Check out /tests directory</info>");
+ $output->writeln("<comment>To complete initialization run 'build' command</comment>");
+
+
+
+ }
+
+
+}
82 src/Codeception/Command/Build.php
@@ -0,0 +1,82 @@
+<?php
+namespace Codeception\Command;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Yaml\Yaml;
+use \Symfony\Component\Console\Helper\DialogHelper;
+
+class Build extends Base
+{
+
+ protected $template = <<<EOF
+<?php
+// This class was automatically generated by build task
+// You can change it manually, but it will be overwritten on next build
+
+/**
+%s
+**/
+
+
+class %s extends %s
+{
+
+
+}
+
+
+EOF;
+
+ public function getDescription() {
+ return 'Generates base classes for all suites';
+ }
+
+ protected function configure()
+ {
+ $this->setDefinition(array(
+ new \Symfony\Component\Console\Input\InputOption('silent', '', InputOption::VALUE_NONE, 'Don\'t ask for rebuild')
+ ));
+ parent::configure();
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $this->initCodeception();
+ foreach ($this->suites as $suite => $settings) {
+
+ \Codeception\SuiteManager::init($settings);
+
+ $phpdoc = array();
+ $methodCounter = 0;
+
+ foreach (\Codeception\SuiteManager::$modules as $modulename => $module) {
+ $class = new \ReflectionClass($modulename);
+ $methods = $class->getMethods();
+ $phpdoc[] = '';
+ $phpdoc[] = 'Methods from ' . $modulename;
+ foreach ($methods as $method) {
+ if (strpos($method->name, '_') === 0) continue;
+ if (!$method->isPublic()) continue;
+ $params = array();
+ foreach ($method->getParameters() as $param) {
+ if ($param->isOptional()) continue;
+ $params[] = '$' . $param->name;
+ }
+
+ $params = implode(', ', $params);
+ $phpdoc[] = '@method void ' . $method->name . '(' . $params . ')';
+ $methodCounter++;
+ }
+ }
+ $contents = sprintf($this->template, implode("\r\n * ", $phpdoc), $settings['class_name'], '\Codeception\AbstractGuy');
+
+ file_put_contents($file = $this->tests_path.'/'.$suite.'/'.$settings['class_name'].'.php', $contents);
+ $output->writeln("$file generated sucessfully. $methodCounter methods added");
+ }
+ }
+}
58 src/Codeception/Command/GenerateScenarios.php
@@ -0,0 +1,58 @@
+<?php
+namespace Codeception\Command;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+
+class GenerateScenarios extends Base {
+
+ public function getDescription() {
+ return 'Generates text representation for all scenarios';
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $this->initCodeception();
+ @mkdir($path = $this->config['paths']['output'].'/scenarios');
+ foreach ($this->suites as $suite => $settings) {
+ $class = $settings['suite_class'];
+ if (!class_exists($class)) continue;
+
+ $output->writeln('Suite '.$suite.' started...');
+
+ \Codeception\SuiteManager::init($settings);
+
+ $testManager = new \Codeception\SuiteManager(new $class, false);
+ if (isset($settings['bootstrap'])) $testManager->setBootstrtap($settings['bootstrap']);
+ $testManager->loadTests($this->tests_path.'/'.$suite);
+ $tests = $testManager->getCurrentSuite()->tests();
+
+ @mkdir($path.'/'.$suite);
+
+ foreach ($tests as $test) {
+ $test->loadScenario();
+ $features = $test->getScenarioText();
+ $name = $this->underscore($test->getName());
+
+ $output->writeln("* $name generated");
+ file_put_contents($path.'/'.$suite.'/'.$name.'.txt', $features);
+
+ }
+
+ }
+ }
+
+ private function underscore($name)
+ {
+ $name = preg_replace('/([A-Z]+)([A-Z][a-z])/','\\1_\\2', $name);
+ $name = preg_replace('/([a-z\d])([A-Z])/','\\1_\\2', $name);
+ return $name;
+
+ }
+
+}
43 src/Codeception/Command/Install.php
@@ -0,0 +1,43 @@
+<?php
+namespace Codeception\Command;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use \Symfony\Component\Console\Helper\DialogHelper;
+
+class Install extends \Symfony\Component\Console\Command\Command {
+
+ public function getDescription() {
+ return 'Installs all required components: PHPUnit, Mink, Symfony Components';
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output) {
+
+ $dialog = new DialogHelper();
+ $confirmed = $dialog->askConfirmation($output,
+ "This will install all TestGuy dependencies through PEAR installer.\n"
+ . "PHPUnit, Symfony Components, and Mink will be installed.\n"
+ . "Do you want to proceed? (Y/n)");
+ if (!$confirmed) return;
+
+ $output->writeln('Intalling PHPUnit...');
+ $output->write(shell_exec('pear config-set auto_discover 1'));
+ $output->write(shell_exec('pear install pear.phpunit.de/PHPUnit'));
+
+ $output->writeln("Installing Symfony Components...");
+ $output->write(shell_exec("pear channel-discover pear.symfony.com"));
+ $output->write(shell_exec('pear install symfony2/Finder'));
+
+ $output->writeln("Installing Mink...");
+ $output->write(shell_exec("pear channel-discover pear.behat.org"));
+ $output->write(shell_exec("pear install behat/mink"));
+
+ $output->writeln("Installaction complete. Init your new TestGuy suite calling the 'init' command");
+ }
+
+
+}
78 src/Codeception/Command/Run.php
@@ -0,0 +1,78 @@
+<?php
+namespace Codeception\Command;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Input\InputDefinition;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+
+class Run extends Base
+{
+
+ protected function configure()
+ {
+ $this->setDefinition(array(
+
+ new \Symfony\Component\Console\Input\InputArgument('suite', InputArgument::OPTIONAL, 'suite to be tested'),
+ new \Symfony\Component\Console\Input\InputArgument('test', InputArgument::OPTIONAL, 'test to be run'),
+
+ new \Symfony\Component\Console\Input\InputOption('report', '', InputOption::VALUE_NONE, 'Show output in compact style'),
+ new \Symfony\Component\Console\Input\InputOption('html', '', InputOption::VALUE_NONE, 'Generate html with results'),
+ new \Symfony\Component\Console\Input\InputOption('colors', '', InputOption::VALUE_NONE, 'Use colors in output'),
+ new \Symfony\Component\Console\Input\InputOption('silent', '', InputOption::VALUE_NONE, 'Use colors in output'),
+ new \Symfony\Component\Console\Input\InputOption('debug', '', InputOption::VALUE_NONE, 'Show debug and scenario output')
+ ));
+ parent::configure();
+ }
+
+ public function getDescription()
+ {
+ return 'Runs the test suites';
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output)
+ {
+ $this->initCodeception();
+
+ ini_set('memory_limit', isset($this->config['settings']['memory_limit']) ? $this->config['settings']['memory_limit'] : '1024M');
+
+ $options = $input->getOptions();
+ if ($input->getArgument('test')) $options['debug'] = true;
+
+ $suite = $input->getArgument('suite');
+ $test = $input->getArgument('test');
+
+ if ($suite) $this->suites = array($suite => $this->suites[$suite]);
+
+ $codecept = new \Codeception\Codecept($this->config, $options);
+ $options = $codecept->getOptions();
+
+ $output->writeln(\Codeception\Codecept::versionString() . "\nPowered by " . \PHPUnit_Runner_Version::getVersionString());
+
+
+
+ if ($suite and $test) {
+ $output->writeln("Running <comment>{$test}Spec.php</comment>:");
+ $codecept->runSuite($suite, $this->suites[$suite], $test);
+ }
+
+ if (!$test) {
+ foreach ($this->suites as $suite => $settings) {
+ if (!$options['silent']) {
+ $guy = isset($settings['class_name']) ? $settings['class_name'] : 'NullGuy';
+ $output->write("\n\n# <comment>$guy's $suite tests</comment> started");
+ }
+ $codecept->runSuite($suite, $settings, $test);
+ }
+ }
+
+ $codecept->getRunner()->getPrinter()->printResult($codecept->getResult());
+
+
+ if ($codecept->getResult()->failures() > 0) exit(1);
+
+ }
+}
7 src/Codeception/Exception/Configuration.php
@@ -0,0 +1,7 @@
+<?php
+namespace Codeception\Exception;
+
+class Configuration extends \Exception {
+
+
+}
14 src/Codeception/Exception/Module.php
@@ -0,0 +1,14 @@
+<?php
+namespace Codeception\Exception;
+
+class Module extends \Exception {
+
+ protected $module;
+
+ public function __construct($module, $message) {
+ $module = str_replace('\Codeception\Module\\','',$module);
+ parent::__construct($message);
+ $this->message = '(Exception in '.$this->module.') ' . $this->message;
+ }
+
+}
13 src/Codeception/Exception/ModuleConfig.php
@@ -0,0 +1,13 @@
+<?php
+namespace Codeception\Exception;
+
+class ModuleConfig extends \Exception {
+
+ public function __construct($module, $message) {
+ $module = str_replace('\Codeception\Module\\','',$module);
+ parent::__construct($message);
+ $this->message = $module." module is not configured!\n ". $this->message;
+ }
+
+
+}
7 src/Codeception/Exception/TestRuntime.php
@@ -0,0 +1,7 @@
+<?php
+namespace Codeception\Exception;
+
+class TestRuntime extends \RuntimeException
+{
+
+}
91 src/Codeception/Module.php
@@ -0,0 +1,91 @@
+<?php
+namespace Codeception;
+
+abstract class Module {
+
+ protected $debugStack = array();
+
+ protected $storage = array();
+
+ protected $config = array();
+
+ protected $requiredFields = array();
+
+ public function _setConfig($config)
+ {
+ $this->config = $config;
+ $fields = array_keys($this->config);
+ if (array_intersect($this->requiredFields, $fields) != $this->requiredFields)
+ throw new \Codeception\Exception\ModuleConfig(get_class($this),"
+ Options: ".implode(', ', $this->requiredFields)." are required\n
+ Update cunfiguration and set all required fields\n\n
+ ");
+ }
+
+ // HOOK: used after configuration is loaded
+ public function _initialize() {}
+
+ // HOOK: on every TestGuy class initialization
+ public function _cleanup()
+ {
+ }
+
+ // HOOK: before every step
+ public function _beforeStep(\Codeception\Step $step) {
+ }
+
+ // HOOK: after every step
+ public function _afterStep(\Codeception\Step $step) {
+ }
+
+ // HOOK: before scenario
+ public function _before(\Codeception\TestCase $test) {
+ }
+
+ // HOOK: after scenario
+ public function _after(\Codeception\TestCase $test) {
+ }
+
+ // HOOK: on fail
+ public function _failed(\Codeception\TestCase $test, $fail) {
+ }
+
+
+ protected function debug($message) {
+ $this->debugStack[] = $message;
+ }
+
+ protected function debugSection($title, $message) {
+ $this->debug("[$title] $message");
+ }
+
+ public function _clearDebugOutput() {
+ $this->debugStack = array();
+ }
+
+ public function _getDebugOutput() {
+ $debugStack = $this->debugStack;
+ $this->_clearDebugOutput();
+ return $debugStack;
+ }
+
+ protected function assert($arguments, $not = false) {
+ $not = $not ? 'Not' : '';
+ $method = ucfirst(array_shift($arguments));
+ if (($method === 'True') && $not) {
+ $method = 'False';
+ $not = '';
+ }
+ if (($method === 'False') && $not) {
+ $method = 'True';
+ $not = '';
+ }
+
+ call_user_func_array(array('\PHPUnit_Framework_Assert', 'assert'.$not.$method), $arguments);
+ }
+
+ protected function assertNot($arguments) {
+ $this->assert($arguments, true);
+ }
+
+}
32 src/Codeception/Module/Cli.php
@@ -0,0 +1,32 @@
+<?php
+namespace Codeception\Module;
+
+class Cli extends \Codeception\Module
+{
+ protected $output = '';
+
+ public function _cleanup()
+ {
+ $this->output = '';
+ }
+
+ public function amInPath($dir) {
+ chdir($dir);
+ }
+
+ public function runShellCommmand($command) {
+ $this->output = shell_exec("$command");
+ $this->debug($this->output);
+ }
+
+ public function seeInShellOutput($text) {
+
+ \PHPUnit_Framework_Assert::assertContains($text, $this->output);
+ }
+
+ public function dontSeeInShellOutput($text) {
+ $this->debug($this->output);
+ \PHPUnit_Framework_Assert::assertNotContains($text, $this->output);
+ }
+
+}
65 src/Codeception/Module/Db.php
@@ -0,0 +1,65 @@
+<?php
+namespace Codeception\Module;
+
+class Db extends \Codeception\Module {
+
+ protected $sql;
+ protected $dbh;
+
+ protected $requiredFields = array('dsn', 'user', 'password');
+
+ public function _initialize() {
+
+ if (!file_exists($this->config['dump'])) {
+ throw new \Codeception\Exception\ModuleConfig(__CLASS__, "
+ File with dump deesn't exist.\n
+ Please, check path for sql file: ".$this->config['dump']);
+ }
+
+ // not necessary to specify dump
+ if (isset($this->config['dump'])) {
+ $sql = file_get_contents($this->config['dump']);
+ $this->sql = $sql;
+
+ try {
+ $dbh = new \PDO($this->config['dsn'], $this->config['user'], $this->config['password']);
+ $this->dbh = $dbh;
+ } catch (\PDOException $e) {
+ throw new \Codeception\Exception\Module(__CLASS__, $e->getMessage());
+ }
+ }
+ }
+
+ public function _before() {
+
+ $dbh = $this->dbh;
+ if (!$dbh) {
+ throw new \Codeception\Exception\ModuleConfig(__CLASS__, "No connection to database. Remove this module from config if you don't need database repopulation");
+ }
+ try {
+ $res = $dbh->query('show tables')->fetchAll();
+ foreach ($res as $row) {
+ $dbh->exec('drop table '.$row[0]);
+ }
+ $this->queries = explode(';', $this->sql);
+
+ foreach ($this->queries as $query) {
+ $dbh->exec($query);
+ }
+
+ } catch (\PDOException $e) {
+ throw new \Codeception\Exception\Module(__CLASS__, $e->getMessage());
+ }
+ }
+
+ public function seeInDatabase($table, $criteria) {
+
+ }
+
+
+ public function dontSeeInDatabase($table, $criteria) {
+
+ }
+
+
+}
34 src/Codeception/Module/Filesystem.php
@@ -0,0 +1,34 @@
+<?php
+namespace Codeception\Module;
+
+class Filesystem extends \Codeception\Module {
+
+
+ protected $file = null;
+
+ public function openFile($filename) {
+ $this->file = file_get_contents($filename);
+ }
+
+ public function seeInFile($text) {
+ PHPUnit_Framework_Assert::assertContains($text, $this->file,"text $text in currently opened file");
+ }
+
+ public function dontSeeInFile($text) {
+ PHPUnit_Framework_Assert::assertNotContains($text, $this->file,"text $text in currently opened file");
+ }
+
+ public function seeFileFound($filename, $path = '') {
+ $path = getcwd().'/'.$path;
+ $this->debug($path);
+
+ $files = \Symfony\Component\Finder\Finder::create()->files()->name($filename)->in($path);
+ foreach ($files as $file) {
+ // PHPUnit_Framework_Assert::assertTrue(file_exists($file),'found and opened file');
+ $this->openFile($file);
+ return;
+ }
+ PHPUnit_Framework_Assert::fail("$filename in $path");
+ }
+
+}
14 src/Codeception/Module/Framework.php
@@ -0,0 +1,14 @@
+<?php
+namespace Codeception\Module;
+
+/**
+ * An abstract class for any PHP Framework.
+ * Integrate your favorite PHP Framework to run integration tests
+ */
+
+class Framework extends \Codeception\Module
+{
+ public function _initialize() {
+ // initialize framework
+ }
+}
76 src/Codeception/Module/PhpBrowser.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Codeception\Module;
+
+class PhpBrowser extends \Codeception\Util\Mink {
+
+ protected $requiredFields = array('start', 'output');
+
+ public function _cleanup() {
+ $zendOptions = array('httpversion' => '1.1', 'useragent' => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0a2) Gecko/20110613 Firefox/6.0a2');
+ if (isset($this->config['zend'])) array_merge($this->config['zend'], $zendOptions);
+ $mink = new \Behat\Mink\Mink(array('primary' => new \Behat\Mink\Session(new \Behat\Mink\Driver\GoutteDriver(new \Goutte\Client($zendOptions)))));
+ $this->session = $mink->getSession('primary');
+ }
+
+ public function submitForm($selector, $params) {
+
+ $form = $this->session->getPage()->find('css',$selector);
+ $fields = $this->session->getPage()->findAll('css', $selector.' input');
+ $url = '';
+ foreach ($fields as $field) {
+ if ($field->getAttribute('type') == 'checkbox') continue;
+ if ($field->getAttribute('type') == 'radio') continue;
+ $url .= sprintf('%s=%s',$field->getAttribute('name'), $field->getAttribute('value')).'&';
+ }
+
+ $fields = $this->session->getPage()->findAll('css', $selector.' textarea');
+ foreach ($fields as $field) {
+ $url .= sprintf('%s=%s',$field->getAttribute('name'), $field->getText()).'&';
+ }
+
+ $fields = $this->session->getPage()->findAll('css', $selector.' select');
+ foreach ($fields as $field) {
+ $url .= sprintf('%s=%s',$field->getAttribute('name'), $field->getValue()).'&';
+ }
+
+ $url .= '&'.http_build_query($params);
+ parse_str($url, $params);
+ $url = $form->getAttribute('action');
+ $method = $form->getAttribute('method');
+
+
+ $this->call($url, $method, $params);
+ }
+
+ public function sendAjaxPostRequest($uri, $params) {
+ $this->session->setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ $this->call($uri, 'post', $params);
+ $this->debug($this->session->getPage()->getContent());
+ }
+
+
+ public function sendAjaxGetRequest($uri, $params) {
+ $this->session->setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ $this->call($uri, 'get', $params);
+ $this->debug($this->session->getPage()->getContent());
+ }
+
+
+ protected function call($uri, $method = 'get', $params = array())
+ {
+ $browser = $this->session->getDriver()->getClient();
+// $uri = $browser->getAbsoluteUri($uri);
+
+ $this->debug('Request ('.$method.'): '.$uri.' '. json_encode($params));
+ $browser->request($method, $uri, $params);
+
+ $this->debug('Response code: '.$this->session->getStatusCode());
+ }
+
+ public function _failed(TestGuy_TestCase $test, $fail) {
+ file_put_contents($this->config['log'].'/'.$test->getFileName().'.page.debug.html', $this->session->getPage()->getContent());
+
+ }
+
+}
273 src/Codeception/Module/Unit.php
@@ -0,0 +1,273 @@
+<?php
+namespace Codeception\Module;
+
+class Unit extends \Codeception\Module
+{
+
+ protected $stubs = array();
+
+ protected $last_result;
+
+ /**
+ * @var \Codeception\TestCase
+ */
+ protected $test;
+
+ protected $testedClass;
+ protected $testedMethod;
+
+ protected $testedStatic;
+
+ public function _initialize() {
+ \Codeception\Util\Stub\Builder::loadClasses(); // loading stub classes
+ }
+
+ public function _before(\PHPUnit_Framework_TestCase $test) {
+ $this->test = $test;
+ $this->stubs = array();
+ set_error_handler(function ($errno, $errstr, $errfile, $errline) { throw new \Codeception\Exception\TestRuntime($errstr, $errno); });
+ }
+
+ public function __after() {
+ restore_error_handler();
+ }
+
+ public function _failed() {
+ if (count($this->stubs)) {
+ $this->debug("Stubs were used:");
+ foreach ($this->stubs as $stub) {
+ if (isset($stub->__mocked)) $this->debug($stub->__mocked);
+ $this->debug(json_encode($stub));
+ }
+ }
+ }
+
+ public function testMethod($signature) {
+ if (strpos($signature, '.')) {
+ // this is class method
+ list($class, $method) = explode('.', $signature);
+ $this->testedClass = $class;
+ $this->testedMethod = $method;
+ $this->testedStatic = false;
+ } elseif (strpos($signature, '::')) {
+ // we test static method
+ list($class, $method) = explode('::', $signature);
+ $this->testedClass = $class;
+ $this->testedMethod = $method;
+ $this->testedStatic = true;
+ }
+ $this->debug('Class: '.$class);
+ $this->debug('Method: '.$method);
+ if ($this->testedStatic) $this->debug('Static');
+ }
+
+ public function haveFakeClass($instance) {
+ $this->stubs[] = $instance;
+ $stubid = count($this->stubs)-1;
+ if (isset($instance->__mocked)) $this->debug('Stub_'.$stubid.' '.$instance->__mocked);
+ }
+
+ public function haveStub($instance) {
+ $this->haveFakeClass($instance);
+ }
+
+ public function executeTestedMethod() {
+ $args = func_get_args();
+ if ($this->testedStatic) {
+ $res = call_user_func_array(array($this->testedClass, $this->testedMethod), $args);
+ $this->debug("Static method {$this->testedClass}::{$this->testedMethod} executed");
+ $this->debug('With parameters: '.json_encode($args));
+ } else {
+ $obj = array_shift($args);
+ if (isset($obj->__mocked)) $this->debug('Received STUB');
+ $this->createMocks();
+ if (!$obj) throw new \InvalidArgumentException("Object for tested method is expected");
+ $res = call_user_func_array(array($obj, $this->testedMethod), $args);
+ $this->debug("method {$this->testedMethod} executed");
+ }
+ $this->debug('Result: '.json_encode($res));
+ $this->last_result = $res;
+ }
+
+ public function executeTestedMethodOn($object) {
+ $this->executeTestedMethod($object);
+ }
+
+ /**
+ * Very magical function that generates Mock methods for expected assertions
+ * Allows declare seeMethodInvoked, seeMethodNotInvoked, etc AFTER the 'execute' command
+ *
+ */
+ protected function createMocks()
+ {
+ $scenario = $this->test->getScenario();
+ $scenario->getCurrentStep();
+ $steps = $scenario->getSteps();
+ for ($i = $scenario->getCurrentStep(); $i < count ($steps); $i ++) {
+ $step = $steps[$i];
+ if (strpos($action = $step->getAction(),'seeMethod') === 0) {
+ $arguments = $step->getArguments(false);
+ $mock = array_shift($arguments);
+ $function = array_shift($arguments);
+ $params = array_shift($arguments);
+
+ $invoke = false;
+
+ switch ($action) {
+ case 'seeMethodInvoked':
+ case 'seeMethodInvokedAtLeastOnce':
+ if (!$mock) throw new \InvalidArgumentException("Stub class not defined");
+ $invoke = new \PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce();
+ break;
+ case 'seeMethodInvokedOnce':
+ if (!$mock) throw new \InvalidArgumentException("Stub class not defined");
+ $invoke = new \PHPUnit_Framework_MockObject_Matcher_InvokedCount(1);
+ break;
+ case 'seeMethodNotInvoked':
+ if (!$mock) throw new \InvalidArgumentException("Stub class not defined");
+ $invoke = new \PHPUnit_Framework_MockObject_Matcher_InvokedCount(0);
+ break;
+ case 'seeMethodInvokedMultipleTimes':
+ if (!$mock) throw new \InvalidArgumentException("Stub class not defined");
+ $times = $params;
+ if (!is_int($times)) throw new \InvalidArgumentException("Invoked times count should be an integer");
+ $params = array_shift($arguments);
+ $invoke = new \PHPUnit_Framework_MockObject_Matcher_InvokedCount($times);
+ break;
+ default:
+ }
+
+ if ($invoke) {
+ $mockMethod = $mock->expects($invoke)->method($function);
+ $this->debug(get_class($invoke) .' attached');
+ if ($params) {
+ call_user_func_array(array($mockMethod, 'with'), $params);
+ $this->debug('with '.json_encode($params));
+ }
+ }
+
+
+ }
+ if ($steps[$i]->getAction() == 'execute') break;
+ }
+ }
+
+ /**
+ *
+ * @magic
+ * @see createMocks
+ * @param $mock
+ * @param $method
+ * @param array $params
+ */
+ public function seeMethodInvoked($mock, $method, array $params = array()) {
+ $this->verifyMock($mock);
+ }
+
+ /**
+ *
+ * @magic
+ * @see createMocks
+ * @param $mock
+ * @param $method
+ * @param array $params
+ */
+ public function seeMethodInvokedOnce($mock, $method, array $params = array()) {
+ $this->verifyMock($mock);
+ }
+
+ /**
+ *
+ * @magic
+ * @see createMocks
+ * @param $mock
+ * @param $method
+ * @param array $params
+ */
+ public function seeMethodNotInvoked($mock, $method, array $params = array()) {
+ $this->verifyMock($mock);
+ }
+
+ /**
+ *
+ * @magic
+ * @see createMocks
+ * @param $mock
+ * @param $method
+ * @param $times
+ * @param array $params
+ */
+ public function seeMethodInvokedMultipleTimes($mock, $method, $times, array $params = array()) {
+ $this->verifyMock($mock);
+ }
+
+ protected function verifyMock($mock)
+ {
+ foreach ($this->stubs as $stubid => $stub) {
+ if (spl_object_hash($stub) == spl_object_hash($mock)) {
+ if (!$mock->__phpunit_hasMatchers()) {
+ throw new \Exception("Probably Internal Error. There is no matchers for current mock");
+ }
+ if (isset($stub->__mocked)) {
+ $this->debug('Stub_'.$stubid.': '.$stub->__mocked);
+ }
+
+ \PHPUnit_Framework_Assert::assertTrue(true); // hook to increment assertions counter
+ try {
+ $mock->__phpunit_verify();
+ } catch (\PHPUnit_Framework_ExpectationFailedException $e) {
+ \PHPUnit_Framework_Assert::fail("\n".$e->getMessage()); // hook to increment assertions counter
+ throw $e;
+ }
+ $mock->__phpunit_cleanup();
+ return;
+ }
+ }
+ throw new \Exception("Mock is not registered by 'haveStub' or 'haveFakeClass' methods");
+ }
+
+ public function seeResultEquals($value) {
+ $this->assert(array('Equals', $value, $this->last_result));
+ }
+
+ public function seeResultContains($value) {
+ \PHPUnit_Framework_Assert::assertContains($value, $this->last_result);
+ }
+
+ public function dontSeeResultContains($value) {
+ \PHPUnit_Framework_Assert::assertNotContains($value, $this->last_result);
+ }
+
+ public function seeResultNotEquals($value) {
+ \PHPUnit_Framework_Assert::assertNotEquals($value, $this->last_result);
+ }
+
+ public function seeEmptyResult() {
+ \PHPUnit_Framework_Assert::assertEmpty($this->last_result);
+ }
+
+ public function seeResultIs($type) {
+ if (in_array($type, array('int','bool','string','array','float','null','resource','scalar'))) {
+ return \PHPUnit_Framework_Assert::assertInternalType($type, $this->last_result);
+ }
+ return \PHPUnit_Framework_Assert::assertInstanceOf($type, $this->last_result);
+ }
+
+ public function seePropertyEquals($object, $property, $value) {
+ $current = $this->retrieveProperty($object, $property);
+ $this->debug('Property value is: ' . $current);
+ \PHPUnit_Framework_Assert::assertEquals($value, $current);
+ }
+
+ protected function retrieveProperty($object, $property)
+ {
+ if (isset($object->__mocked)) $this->debug('Received STUB');
+ $reflectionClass = new \ReflectionClass($object);
+ $reflectionProperty = $reflectionClass->getProperty($property);
+ $reflectionProperty->setAccessible(true);
+ return $reflectionProperty->getValue($object);
+ }
+
+
+
+}
67 src/Codeception/Output.php
@@ -0,0 +1,67 @@
+<?php
+namespace Codeception;
+
+class Output {
+
+ protected $silent = false;
+ protected $colors = true;
+
+ function __construct($silent = false, $colors = true) {
+ $this->silent = $silent;
+ $this->colors = $colors;
+ }
+
+ public function put($message) {
+ if ($this->silent) return;
+ $message = $this->colors ? $this->colorize($message) : $this->naturalize($message);
+ $message = $this->clean($message);
+ $this->write($message);
+
+
+ }
+
+ private function write($text)
+ {
+ if ($this->silent) return;
+ ob_get_flush();
+ print $text;
+ ob_start();
+ }
+
+ protected function naturalize($message)
+ {
+ $message = str_replace(array('[[',']]','(%','%)','((','))','(!','!)'), array('','','','','','','',''), $message);
+ return $message;
+ }
+
+ protected function colorize($message) {
+ // magent colors
+ $message = str_replace(array('[[',']]'), array("\033[35;1m","\033[0m"), $message);
+ $message = str_replace(array('(%','%)'), array("\033[45;37m","\033[0m"), $message);
+ // grey
+ $message = str_replace(array('((','))'), array("\033[37;1m","\033[0m"), $message);
+ $message = str_replace(array('(!','!)'), array("\033[41;37m","\033[0m"), $message);
+ return $message;
+ }
+
+ protected function clean($message)
+ {
+ // clear json serialization
+ $message = str_replace('\/','/', $message);
+ return $message;
+ }
+
+ public function writeln($message) {
+
+ $this->put("\n# $message");
+
+ }
+
+ public function debug($message) {
+ if ($this->silent) return;
+ if (is_array($message)) $message = implode("\n=> ", $message);
+ $this->colors ? $this->write("\033[36m\n=> ".$message."\033[0m") : $this->write("=> ".$message) ;
+ }
+
+
+}
37 src/Codeception/ResultPrinter.php
@@ -0,0 +1,37 @@
+<?php
+namespace Codeception;
+
+class ResultPrinter extends \PHPUnit_Util_TestDox_ResultPrinter
+{
+
+ protected $testTypeOfInterest = '\Codeception\TestCase';
+
+ /**
+ * A test ended.
+ *
+ * @param \PHPUnit_Framework_Test $test
+ * @param float $time
+ */
+ public function endTest(\PHPUnit_Framework_Test $test, $time)
+ {
+
+ if ($this->testStatus == \PHPUnit_Runner_BaseTestRunner::STATUS_PASSED) {
+ $this->successful++;
+ $success = TRUE;
+ $steps = $test->getScenario()->getSteps();
+ } else {
+ $success = FALSE;
+ $steps = $test->getTrace();
+ }
+
+ $this->onTest(
+ $test->getScenario()->getFeature(),
+ $success,
+ $steps,
+ $time
+ );
+ }
+
+
+
+}
164 src/Codeception/ResultPrinter/HTML.php
@@ -0,0 +1,164 @@
+<?php
+namespace Codeception\ResultPrinter;
+
+class HTML extends \Codeception\ResultPrinter
+{
+ /**
+ * @var boolean
+ */
+ protected $printsHTML = TRUE;
+
+ /**
+ * @var integer
+ */
+ protected $id = 0;
+
+ /**
+ * @var string
+ */
+ protected $scenarios = '';
+
+ /**
+ * @var string
+ */
+ protected $templatePath;
+
+ /**
+ * @var int
+ */
+ protected $timeTaken = 0;
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $out
+ * @throws InvalidArgumentException
+ */
+ public function __construct($out = NULL)
+ {
+ parent::__construct($out);
+
+ $this->templatePath = sprintf(
+ '%s%stemplate%s',
+
+ dirname(__FILE__),
+ DIRECTORY_SEPARATOR,
+ DIRECTORY_SEPARATOR
+ );
+ }
+
+ /**
+ * Handler for 'start class' event.
+ *
+ * @param string $name
+ */
+ protected function startClass($name)
+ {
+ }
+
+ /**
+ * Handler for 'on test' event.
+ *
+ * @param string $name
+ * @param boolean $success
+ * @param array $steps
+ */
+ protected function onTest($name, $success = TRUE, array $steps = array(), $time = 0)
+ {
+ $this->timeTaken += $time;
+ if ($this->testStatus == PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE) {
+ $scenarioStatus = 'scenarioFailed';
+ }
+
+ else if ($this->testStatus == PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED) {
+ $scenarioStatus = 'scenarioSkipped';
+ }
+
+ else if ($this->testStatus == PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE) {
+ $scenarioStatus = 'scenarioIncomplete';
+ }
+
+ else {
+ $scenarioStatus = 'scenarioSuccess';
+ }
+
+ $stepsBuffer = '';
+
+ foreach ($steps as $step) {
+ $currentStepName = $step->getName();
+
+ $stepText = $currentStepName;
+
+ $stepTemplate = new Text_Template(
+ $this->templatePath . 'step.html'
+ );
+
+ $stepTemplate->setVar(
+ array(
+ 'action' => $step->getHumanizedAction() . ' ' . $step->getArguments(TRUE),
+ )
+ );
+
+ $stepsBuffer .= $stepTemplate->render();
+ }
+
+ $scenarioTemplate = new Text_Template(
+ $this->templatePath . 'scenario.html'
+ );
+
+ $scenarioTemplate->setVar(
+ array(
+ 'id' => ++$this->id,
+ 'name' => $name,
+ 'scenarioStatus' => $scenarioStatus,
+ 'steps' => $stepsBuffer,
+ 'time' => round($time, 2)
+ )
+ );
+
+ $this->scenarios .= $scenarioTemplate->render();
+ }
+
+ /**
+ * Handler for 'end run' event.
+ *
+ */
+ protected function endRun()
+ {
+
+ $scenarioHeaderTemplate = new Text_Template(
+ $this->templatePath . 'scenario_header.html'
+ );
+
+ $status = !$this->failed ? '<span style="color: green">OK</span>' : '<span style="color: red">FAILED</span>';
+
+ $scenarioHeaderTemplate->setVar(
+ array(
+ 'name' => 'TestGuy Results',
+ 'status' => $status,
+ 'time' => round($this->timeTaken,1)
+ )
+ );
+
+ $header = $scenarioHeaderTemplate->render();
+
+ $scenariosTemplate = new Text_Template(
+ $this->templatePath . 'scenarios.html'
+ );
+
+ $scenariosTemplate->setVar(
+ array(
+ 'header' => $header,
+ 'scenarios' => $this->scenarios,
+ 'successfulScenarios' => $this->successful,
+ 'failedScenarios' => $this->failed,
+ 'skippedScenarios' => $this->skipped,
+ 'incompleteScenarios' => $this->incomplete
+ )
+ );
+
+ $this->write($scenariosTemplate->render());
+ }
+
+
+}
48 src/Codeception/ResultPrinter/Report.php
@@ -0,0 +1,48 @@
+<?php
+namespace Codeception\ResultPrinter;
+
+class Report extends \Codeception\ResultPrinter
+{
+
+ /**
+ * Handler for 'on test' event.
+ *
+ * @param string $name
+ * @param boolean $success
+ * @param array $steps
+ */
+ protected function onTest($name, $success = TRUE, array $steps = array(), $time = 0)
+ {
+ if ($this->testStatus == \PHPUnit_Runner_BaseTestRunner::STATUS_FAILURE) {
+ $status = "\033[41;37mFAIL\033[0m";
+ }
+
+ else if ($this->testStatus == \PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED) {
+ $status = 'Skipped';
+ }
+
+ else if ($this->testStatus == \PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE) {
+ $status = 'Incomplete';
+ }
+
+ else {
+ $status = 'Ok';
+ }
+
+ if (strlen($name) > 75) $name = substr($name, 0, 70);
+ $line = $name . str_repeat('.', 75 - strlen($name));
+ $line .= $status;
+
+
+ $this->write($line."\n");
+
+
+ }
+
+ protected function endRun()
+ {
+ $this->write("\nTestGuy Results\n");
+ $this->write(sprintf("Sucessful: %s. Failed: %s. Incomplete: %s. Skipped: %s", $this->successful, $this->failed, $this->skipped, $this->incomplete)."\n");
+ }
+
+}
116 src/Codeception/ResultPrinter/UI.php
@@ -0,0 +1,116 @@
+<?php
+namespace Codeception\ResultPrinter;
+
+class UI extends \PHPUnit_TextUI_ResultPrinter {
+
+ protected $output;
+ protected $traceLength = 5;
+
+ function __construct($out = NULL, $verbose = FALSE, $colors = FALSE, $debug = FALSE) {
+ parent::__construct($out, $verbose, $colors, $debug);
+ $this->output = new \Codeception\Output(!$verbose, $colors);
+ }
+
+ protected function printDefectHeader(\PHPUnit_Framework_TestFailure $defect, $count)
+ {
+ $failedTest = $defect->failedTest();
+
+ $feature = strtolower($failedTest->getScenario()->getFeature());
+ $this->output->put("\n$count) ((Couldn't $feature)) ({$failedTest->getFilename()})\n");
+
+ }
+
+ public function startTest(\PHPUnit_Framework_Test $test)
+ {
+ if (!($test instanceof \Codeception\TestCase)) return parent::startTest($test);
+ }
+