├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── bin └── omikron ├── composer.json ├── docs └── omikron.png ├── examples └── topic-calculus.php ├── src ├── assertion.php ├── input.php ├── omikron.php └── output.php └── tests ├── topic-exceptions.php └── topic-integration.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | - hhvm 7 | 8 | before_script: 9 | - composer install 10 | 11 | script: 12 | - bin/omikron tests 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ======================================= 3 | 4 | Copyright © 2015 Toon Daelman 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Omikron Test Framework 2 | ============================= 3 | 4 | [![Travis CI](https://api.travis-ci.org/turanct/omikron.svg?branch=master)](https://travis-ci.org/turanct/omikron) 5 | 6 | A simple, functional programming inspired, test framework. 7 | 8 | ![Ouroboros, the greek dragon eating its own tail](docs/omikron.png) 9 | 10 | This project was started as a reaction to [this issue](https://github.com/mathiasverraes/lambdalicious/issues/22) and was initially [a gist](https://gist.github.com/turanct/129a6ed97ec3543ebafd). It's currently *not meant to be used in production*, but there's nobody stopping you if you want to. 11 | 12 | 13 | Usage 14 | ----------------------------- 15 | 16 | Installing it is easy, just require `turanct/omikron` as a development dependency in your `composer.json` file, and configure a `bin-dir`. The omikron executable will be available in your bin directory when you've run `composer install`. 17 | 18 | ```json 19 | { 20 | "require-dev": { 21 | "turanct/omikron": "dev-master" 22 | }, 23 | "config": { 24 | "bin-dir": "bin" 25 | } 26 | } 27 | ``` 28 | 29 | Omikron has a concept of topics, topics are distinct parts of your code under test. These topics have different features, and to describe those features, there are assertions. 30 | 31 | ```php 32 | failing test 38 | }), 39 | it("adds three numbers", function() { return 40 | expect(1 + 1 + 1, toBe(3)); 41 | }) 42 | ), 43 | describe("subtraction", 44 | it("subtracts two numbers", function() { return 45 | expect(3 - 2, toBe(1)); 46 | }) 47 | ) 48 | ); 49 | ``` 50 | 51 | This is an example of a topic. It's in a file named `topics/topic-calculus.php`. Every topic file must have a name starting with `topic-`. You can split topics over multiple files if you want to. The topic `calculus` has two features, `addition` and `subtraction`, and both of those features have some assertions. 52 | 53 | To run the tests for this topic, we'll just run `bin/omikron topics` (as `topics` is the directory with my topics). The output will be something like this: 54 | 55 | ```sh 56 | $ bin/omikron topics 57 | topics: 1 58 | features: 2 59 | assertions: 3 60 | 61 | FAILED: calculus: addition adds two numbers 62 | Expected 2 to be 3 63 | ``` 64 | 65 | 66 | Tests 67 | ----------------------------- 68 | 69 | Omikron has unit tests, located in the `tests` directory. These tests are written using Omikron itself, so you can just run the Omikron executable on the tests dir! 70 | 71 | ```sh 72 | $ bin/omikron tests 73 | ``` 74 | 75 | 76 | Contributing 77 | ----------------------------- 78 | 79 | Feel free to fork and send pull requests! 80 | 81 | -------------------------------------------------------------------------------- /bin/omikron: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getMessage() . "\n"; 29 | } 30 | 31 | return true; 32 | } 33 | 34 | function diff($actual, $expected) { 35 | // workaround for bug in differ https://github.com/sebastianbergmann/diff/issues/25 36 | $actual = is_scalar($actual) ? (string) $actual : $actual; 37 | $expected = is_scalar($expected) ? (string) $expected : $expected; 38 | return PHP_EOL. 39 | (new Differ("--- Actual\n+++ Expected\n")) 40 | ->diff($actual, $expected); 41 | } 42 | 43 | function toBe($expected) { 44 | return function($actual) use($expected) { 45 | return $actual === $expected 46 | ? true 47 | : "Expected values to be equal, instead got:".diff($actual, $expected) 48 | ; 49 | }; 50 | } 51 | 52 | function notToBe($expected) { 53 | return function($actual) use($expected) { 54 | return $actual !== $expected 55 | ? true 56 | : "Expected values not to be equal, instead got:".diff($actual, $expected) 57 | ; 58 | }; 59 | } 60 | 61 | function toBeTrue() { 62 | return function($actual) { 63 | return $actual === true 64 | ? true 65 | : "Expected value to be true" 66 | ; 67 | }; 68 | } 69 | 70 | function toBeFalse() { 71 | return function($actual) { 72 | return $actual === false 73 | ? true 74 | : "Expected value to be false" 75 | ; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /src/input.php: -------------------------------------------------------------------------------- 1 | $topic, 7 | 'failedAssertions' => 8 | array_reduce( 9 | $features, 10 | function ($failedAssertions, $feature) { 11 | return array_merge( 12 | $failedAssertions, 13 | array_map( 14 | function ($assertion) use ($feature) { 15 | return array($assertion, $feature); 16 | }, 17 | $feature->failedAssertions 18 | ) 19 | ); 20 | }, 21 | array() 22 | ), 23 | 'numberOfFeatures' => count($features), 24 | 'numberOfAssertions' => 25 | array_reduce( 26 | $features, 27 | function ($number, $feature) { 28 | return $number + $feature->numberOfAssertions; 29 | }, 30 | 0 31 | ), 32 | ]; 33 | } 34 | 35 | function describe($feature, ...$assertions) 36 | { 37 | return (object)[ 38 | 'name' => $feature, 39 | 'failedAssertions' => 40 | array_filter( 41 | $assertions, 42 | function ($assertion) { return !$assertion->assert; } 43 | ), 44 | 'numberOfAssertions' => count($assertions) 45 | ]; 46 | } 47 | 48 | function it($doesThis, callable $correctly) 49 | { 50 | try { 51 | $outcome = $correctly(); 52 | } catch (Exception $e) { 53 | $outcome = PHP_EOL . 'Exception: ' . $e->getMessage(); 54 | $outcome .= ' in ' . $e->getFile() . ' on line ' . $e->getLine() . PHP_EOL; 55 | $outcome .= PHP_EOL . 'Call stack:' . PHP_EOL . $e->getTraceAsString(); 56 | } 57 | 58 | return (object)[ 59 | 'assert' => true === $outcome, 60 | 'description' => (string)$doesThis . (true !== $outcome ? PHP_EOL . $outcome : '') 61 | ]; 62 | } 63 | 64 | function testResults(array $topics) 65 | { 66 | return (object)[ 67 | 'numberOfTopics' => 68 | count(array_unique(array_map( 69 | function ($topic) { return $topic->name; }, 70 | $topics 71 | ))), 72 | 'numberOfFeatures' => 73 | array_reduce( 74 | $topics, 75 | function ($number, $topic) { 76 | return $number + $topic->numberOfFeatures; 77 | }, 78 | 0 79 | ), 80 | 'numberOfAssertions' => 81 | array_reduce( 82 | $topics, 83 | function ($number, $topic) { 84 | return $number + $topic->numberOfAssertions; 85 | }, 86 | 0 87 | ), 88 | 'failedAssertions' => 89 | array_reduce( 90 | $topics, 91 | function ($failedAssertions, $topic) { 92 | $assertions = $topic->failedAssertions; 93 | 94 | $assertions = array_map( 95 | function ($assertion) use ($topic) { 96 | $assertion[] = $topic; 97 | return $assertion; 98 | }, 99 | $assertions 100 | ); 101 | 102 | return array_merge($failedAssertions, $assertions); 103 | }, 104 | array() 105 | ), 106 | ]; 107 | } 108 | -------------------------------------------------------------------------------- /src/output.php: -------------------------------------------------------------------------------- 1 | failedAssertions) ? 1 : 0; 7 | } 8 | 9 | function renderOutput($testResults) 10 | { 11 | $output = ''; 12 | 13 | $output .= 'topics: ' . $testResults->numberOfTopics . "\n"; 14 | $output .= 'features: ' . $testResults->numberOfFeatures . "\n"; 15 | $output .= 'assertions: ' . $testResults->numberOfAssertions . "\n"; 16 | $output .= "\n"; 17 | 18 | $output .= implode( 19 | "\n", 20 | array_map( 21 | function ($assertion) { 22 | return failedOutput($assertion[2], $assertion[1], 23 | $assertion[0]); 24 | }, 25 | $testResults->failedAssertions 26 | ) 27 | ); 28 | $output .= "\n"; 29 | 30 | return $output; 31 | } 32 | 33 | function failedOutput($topic, $feature, $assertion) 34 | { 35 | return 'FAILED: ' 36 | . $topic->name 37 | . ': ' . $feature->name 38 | . ' ' . $assertion->description 39 | ; 40 | } 41 | -------------------------------------------------------------------------------- /tests/topic-exceptions.php: -------------------------------------------------------------------------------- 1 |