├── .gitignore ├── example.php ├── LICENSE ├── README.md └── testes.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | a = array("First", true); 7 | } 8 | 9 | function test_count() { 10 | $this->assert_count($this->a, 2); // Pass 11 | $this->assert_count($this->a, 20); // Fail 12 | } 13 | 14 | function test_first_element() { 15 | $this->assert_equals($this->a[0], "First"); // Pass 16 | $this->assert_equals($this->a[0], "Second"); // Fail 17 | } 18 | 19 | function test_second_element() { 20 | $this->assert_true($this->a[1]); // Pass 21 | $this->assert_false($this->a[1]); // Fail 22 | } 23 | 24 | function after() { 25 | unset($this->a); 26 | } 27 | } 28 | 29 | Testes::run(); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2012 Kunal Anand http://kunalanand.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Testes 2 | 3 | *Make your code test-tacular.* 4 | 5 | **Testes** is a simple PHP testing library. It is designed for programmers who want to add test coverage using an intuitive API. 6 | 7 | ## Features 8 | 9 | - Around a dozen built-in assertions 10 | - Support for `before` and `after` methods (setup and cleanup) 11 | - Single file dependency (`testes.php`) 12 | - Tested with PHP 5.3 and above 13 | 14 | ## Example 15 | 16 | Copy/paste the following in file called `example.php`: 17 | 18 | ```php 19 | a = array("First", true); 26 | } 27 | 28 | function test_count() { 29 | $this->assert_count($this->a, 2); // Pass 30 | $this->assert_count($this->a, 20); // Fail 31 | } 32 | 33 | function test_first_element() { 34 | $this->assert_equals($this->a[0], "First"); // Pass 35 | $this->assert_equals($this->a[0], "Second"); // Fail 36 | } 37 | 38 | function test_second_element() { 39 | $this->assert_true($this->a[1]); // Pass 40 | $this->assert_false($this->a[1]); // Fail 41 | } 42 | 43 | function after() { 44 | unset($this->a); 45 | } 46 | } 47 | 48 | Testes::run(); 49 | ``` 50 | 51 | Executing `php example.php` from the command line will yield the following output: 52 | 53 | .X.X.X 54 | 55 | TestArrays::test_count -- <2> does not match count of 20 56 | /Users/kunal/Desktop/Testes/example.php:11 57 | 58 | TestArrays::test_first_element -- (string) expected but was First (string) 59 | /Users/kunal/Desktop/Testes/example.php:16 60 | 61 | TestArrays::test_second_element -- expected but was true (boolean) 62 | /Users/kunal/Desktop/Testes/example.php:21 63 | 64 | 6 assertions. 3 failures. 65 | 66 | 67 | ## Using Testes 68 | 69 | **Testes** is easy to use: 70 | 71 | 1. Make sure your test class extends `Testes`. This will allow your class to get access to the built-in assertions. 72 | 73 | 2. Make sure that each of your test methods begin with the phrase `test` (case-insensitive). 74 | 75 | 3. When invoked, `Testes::run()` will build a list of classes that extend `Testes`. It will then automagically call `test` methods. `before`/`after` methods will wrap each test call (see below). 76 | 77 | 4. `Testes` will keep track of every assertion, including the originating file, class, method, and line number. At the end of test execution, you will be presented with a detailed log of successes and/or failures. 78 | 79 | 80 | ## Before & After 81 | 82 | **Testes** allows programmers to create setup/cleanup methods that execute before and after each test. These methods are appropriately named `before` and `after`. You are not required to implement these methods in your test classes. 83 | 84 | The pair provides convenient functionality: 85 | 86 | - Easy to create/destroy common objects (reusability) 87 | - Allows for code consistency - copying and pasting setup blocks is bad 88 | - Can prevent test pollution - i.e. destructive functions against an object that will be tested later 89 | 90 | ## Assertions API 91 | 92 | **Testes** includes the following assertions: 93 | 94 | Function | Pseudo-English 95 | ------------ | -------------- 96 | assert_count($actual, $count) | $actual (array/object) has a count of $expected 97 | assert_equals($actual, $expected) | $actual equals $expected 98 | assert_false($actual) | $actual is false (requires boolean type) 99 | assert_instance_of($actual, $string_or_object) | $actual is an instance of $string_or_object 100 | assert_not_null($actual) | $actual is not null 101 | assert_null($actual) | $actual is null 102 | assert_string_contains($string, $expected) | $string contains $expected (case-sensitive) 103 | assert_string_ends_with($string, $expected) | $string ends with $expected (case-sensitive) 104 | assert_string_matches($string, $regex) | $string matches $regex 105 | assert_string_starts_with($string, $expected) | $string starts with $expected (case-sensitive) 106 | assert_true($actual) | $actual is true 107 | 108 | 109 | ## Installation 110 | 111 | Include `testes.php` and start adding test coverage. That's it. 112 | 113 | ## Credits & License 114 | 115 | **Testes** was made by [Kunal Anand](http://kunalanand.com) and released under the MIT License. 116 | 117 | Special thanks to [Francisco Brito](http://nullisnull.blogspot.com) and [Jason Mooberry](http://jasonmooberry.com/) for creative input and rhetorical emulsification. 118 | 119 | All forks and stars are dedicated to the [Minazo Remembrance Foundation](http://knowyourmeme.com/memes/lolrus). -------------------------------------------------------------------------------- /testes.php: -------------------------------------------------------------------------------- 1 | assert(function($actual, $count) { 24 | if (count($actual) != $count) { 25 | $actual_count = count($actual); 26 | TestesException::new_exception("<{$actual_count}> does not match count of $count"); 27 | } 28 | }, func_get_args()); 29 | } 30 | 31 | function assert_equals($actual, $expected) { 32 | $this->assert(function($actual, $expected) { 33 | if ($actual !== $expected) { 34 | $actual_type = gettype($actual); 35 | $expected_type = gettype($expected); 36 | TestesException::new_exception("<$expected> ({$expected_type}) expected but was $actual ({$actual_type})"); 37 | } 38 | }, func_get_args()); 39 | } 40 | 41 | function assert_false($actual) { 42 | $this->assert(function($actual) { 43 | if ($actual !== false) { 44 | $actual_type = gettype($actual); 45 | $actual = Testes::prep_boolean($actual); 46 | TestesException::new_exception(" expected but was $actual ({$actual_type})"); 47 | } 48 | }, func_get_args()); 49 | } 50 | 51 | function assert_instance_of($actual, $string_or_object) { 52 | $this->assert(function($actual, $string_or_object) { 53 | if (!($actual instanceof $string_or_object)) { 54 | $actual_type = gettype($actual); 55 | TestesException::new_exception("<{$string_or_object}> type expected but was {$actual_type}"); 56 | } 57 | }, func_get_args()); 58 | } 59 | 60 | function assert_not_null($actual) { 61 | $this->assert(function($actual) { 62 | if (is_null($actual)) { 63 | $actual_type = gettype($actual); 64 | TestesException::new_exception("<$actual> {$actual_type} is null"); 65 | } 66 | }, func_get_args()); 67 | } 68 | 69 | function assert_null($actual) { 70 | $this->assert(function($actual) { 71 | if (!(is_null($actual))) { 72 | $actual_type = gettype($actual); 73 | TestesException::new_exception("<$actual> {$actual_type} is not null"); 74 | } 75 | }, func_get_args()); 76 | } 77 | 78 | function assert_string_contains($string, $expected) { 79 | $this->assert(function($actual) { 80 | if (strpos($string, $expected) === false) { 81 | TestesException::new_exception("<$string> does not contain $expected"); 82 | } 83 | }, func_get_args()); 84 | } 85 | 86 | function assert_string_ends_with($string, $expected) { 87 | $this->assert(function($string, $expected) { 88 | if ((strlen($expected) != 0) && (substr($string, -strlen($expected)) !== $expected)) { 89 | TestesException::new_exception("<$string> does not end with: $expected"); 90 | } 91 | }, func_get_args()); 92 | } 93 | 94 | function assert_string_matches($string, $regex) { 95 | $this->assert(function($string, $regex) { 96 | if (!preg_match($regex, $string)) { 97 | TestesException::new_exception("<$string> does not match expression: $regex"); 98 | } 99 | }, func_get_args()); 100 | } 101 | 102 | function assert_string_starts_with($string, $expected) { 103 | $this->assert(function($string, $expected) { 104 | if (substr($string, 0, strlen($expected)) !== $expected) { 105 | TestesException::new_exception("<$string> does not start with $actual"); 106 | } 107 | }, func_get_args()); 108 | } 109 | 110 | function assert_true($actual) { 111 | $this->assert(function($actual) { 112 | if ($actual !== true) { 113 | $actual_type = gettype($actual); 114 | $actual = Testes::prep_boolean($actual); 115 | TestesException::new_exception(" expected but was $actual ({$actual_type})"); 116 | } 117 | }, func_get_args()); 118 | } 119 | 120 | static function prep_boolean($a) { 121 | if (is_bool($a)) { 122 | if ($a === false) { 123 | return "false"; 124 | } 125 | else if ($a === true) { 126 | return "true"; 127 | } 128 | } 129 | return $a; 130 | } 131 | 132 | static function run() { 133 | $test_classes = array(); 134 | $testes_methods = get_class_methods("Testes"); 135 | 136 | foreach (get_declared_classes() as $c) { 137 | if (is_subclass_of($c, "Testes")) { 138 | $test_classes[] = $c; 139 | } 140 | } 141 | 142 | foreach ($test_classes as $c) { 143 | $c = new $c; 144 | $methods = array_diff(get_class_methods($c), $testes_methods); 145 | 146 | foreach ($methods as $m) { 147 | $m = strtolower($m); 148 | if (is_callable(array($c, "before"))) { 149 | $c->before(); 150 | } 151 | if ((substr($m, 0, 4) === "test") && is_callable(array($c, $m))) { 152 | $c->$m(); 153 | } 154 | if (is_callable(array($c, "after"))) { 155 | $c->after(); 156 | } 157 | } 158 | } 159 | 160 | echo "\n\n"; 161 | 162 | if (count(self::$exceptions) > 0) { 163 | foreach(self::$exceptions as $e) { 164 | echo "$e\n\n"; 165 | } 166 | } 167 | 168 | echo self::$assertion_count . " assertions. "; 169 | echo count(self::$exceptions) . " failures.\n"; 170 | } 171 | } 172 | 173 | class TestesException extends Exception { 174 | function __construct($message, $file, $class, $method, $line) { 175 | parent::__construct($message, 0); 176 | $this->file = $file; 177 | $this->class = $class; 178 | $this->method = $method; 179 | $this->line = $line; 180 | } 181 | 182 | function __toString() { 183 | return "{$this->class}::{$this->method} -- {$this->message}\n{$this->file}:{$this->line}"; 184 | } 185 | 186 | static function new_exception($message) { 187 | $trace = debug_backtrace(); 188 | $file = $trace[4]['file']; 189 | $class = $trace[5]['class']; 190 | $method = $trace[5]['function']; 191 | $line = $trace[4]['line']; 192 | throw new TestesException($message, $file, $class, $method, $line); 193 | } 194 | } --------------------------------------------------------------------------------