{"id":33999862,"url":"https://github.com/rawsrc/exacodis","last_synced_at":"2026-05-29T08:01:37.411Z","repository":{"id":62533519,"uuid":"403517904","full_name":"rawsrc/exacodis","owner":"rawsrc","description":"EXACODIS a very minimalist test engine for PHP","archived":false,"fork":false,"pushed_at":"2023-06-27T06:34:10.000Z","size":1088,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-14T21:18:31.268Z","etag":null,"topics":["php-testing","unit-test"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rawsrc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-09-06T06:58:03.000Z","updated_at":"2023-04-17T20:50:44.000Z","dependencies_parsed_at":"2022-11-02T15:00:37.354Z","dependency_job_id":null,"html_url":"https://github.com/rawsrc/exacodis","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/rawsrc/exacodis","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rawsrc%2Fexacodis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rawsrc%2Fexacodis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rawsrc%2Fexacodis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rawsrc%2Fexacodis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rawsrc","download_url":"https://codeload.github.com/rawsrc/exacodis/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rawsrc%2Fexacodis/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33642318,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["php-testing","unit-test"],"created_at":"2025-12-13T09:08:24.429Z","updated_at":"2026-05-29T08:01:37.403Z","avatar_url":"https://github.com/rawsrc.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# **Exacodis**\n\n`2022-08-15` `PHP 8.0+` `v.1.2.3`\n\n## **A PHP TESTING ENGINE**\n\n`Exacodis` is a very minimalist testing engine for PHP (very lightweight PHP testing framework). \nThis engine is really far from others tools as it is very simple to use. No complex architecture, \nnot even a huge testing engine, just the basic (and a little more) to help you to validate \nyour PHP code.\n\nJust 3 classes : \n1. One to pilot the tests (called `Pilot`),\n2. One to encapsulate and execute the test code (called `Runner`)\n3. One to produce the report (called `Report`)\n\nAnd many helpers to check the returned values against the expected types and/or values.\nThe helpers are not exhaustive, you'll be able to create easily yours.\n\n**INSTALLATION**\n```bash\ncomposer require rawsrc/exacodis\n```\n\n**IMPORTANT**\n\nPlease note that your source code for tests must be perfectly clean: you can't\noverride a test run nor a result nor a resource.\u003cbr\u003e\nIf you do, then the code will fail with an `Exception` until you fix it. \n\n**CHANGELOG**\n1. Add the possibility to test any protected/private method from a class\n2. Add the possibility to test any protected/private static method from a class\n3. Does not break the compatibility with the previous version\n\n**HOW TO USE**\n\nI will directly use `Exacodis` to test itself as a learning support. \n\nAs `Exacodis` is a very lightweight engine, it's your responsibility to set up \na test environment. Clearly, it's as simple as : \n\n```php\n\u003c?php\n\ndeclare(strict_types=1);\n\ninclude 'Pilot.php';\n\nuse Exacodis\\Pilot;\n```\nFor projects with many classes, you must tell PHP how to load your classes either \nby including them or using an autoloader. \n\nThat's enough to start to test your code.\n\n*CONCEPT*\n\n- The `Pilot` class does absolutely everything. You do not have to use the two \nother classes; `Runner` and `Report`.\n- All the test code must be encapsulated in a `Closure` that **MUST** return a value.\n- The helpers you can create are used for the `assert` part of the engine. \nYou are free to create as many assertions as you want. There are already many \nhelpers included in the standard library.\n\n*LET'S START*\n```php\n// create new project\n$pilot = new Pilot('Exacodis - A PHP minimalist test framework');\n$pilot-\u003einjectStandardHelpers();\n```\n- RESOURCES\n\nTo use everything you need to test your code, the engine is able to store and \nretrieve any resource you want (objects, arrays, scalar values, even unit tests...).\nEach resource must have a unique name, and you can't override it by error.\n```php\n//region resources\n$pilot-\u003eaddResource('year', 2021);\n$pilot-\u003eaddResource('years', [2020, 2021]);\n$pilot-\u003eaddResource('is_leap', false);\n$pilot-\u003eaddResource('current_month', 'september');\n\n$pilot-\u003eaddResource('dummy_array_data', [\n    $pilot-\u003egetResource('year'), \n    $pilot-\u003egetResource('is_leap'), \n    $pilot-\u003egetResource('current_month')\n]);\n//endregion\n```\n- TEST\n\nAs written, a test is a simple snippet of code encapsulated in a `Closure` that\nreturns a value :\n```php\n$pilot-\u003erun(\n    id: '001',\n    description: 'Resource extractor (integer) - assertIsInt assertEqual assertNotEqual assertIn assertInStrict',\n    test: fn() =\u003e $pilot-\u003egetResource('year')\n);\n```\n- ASSERTIONS\n\nAssertions use the standard helpers and of course yours.\n```php\n$pilot-\u003eassertIsInt();\n$pilot-\u003eassertEqual(2021);\n$pilot-\u003eassertNotEqual(2000);\n$pilot-\u003eassertIn(['2021']);\n$pilot-\u003eassertInStrict([2021]);\n```\nYou must know that assertions (`-\u003eassertXXX`) always apply to the latest run.\nIf you want to change the current runner, then you can ask for it:\n```php\n$pilot-\u003esetCurrentRunnerTo('001');\n```\nThen the assertions will apply to this one (see below: NESTED RUNS)\n\n- DYNAMIC ASSERTION\n\nHere's the way to write a dynamic test:\n```php\n$pilot-\u003eassert(\n    test: fn() =\u003e count($pilot-\u003egetRunner('select_001')-\u003egetResult()) === 2,\n    test_name: 'Count the records',\n    expected: 2,    \n);\n```\n\n- COMPLEX TEST CODE\n\nYou can write your test code as raw code, especially for complex test code.\n```php\n// manual test\n$stats = $pilot-\u003egetStats();\nunset($stats['milliseconds'], $stats['hms']);\n// we encapsulate the result in a closure to use it for testing purpose\n$pilot-\u003erun(\n    id: '007',\n    description: 'check the count',\n    test: fn() =\u003e $stats;\n);\n// then we lead our assertions\n$pilot-\u003eassertIsArray();\n$pilot-\u003eassertEqual([\n    'nb_runs' =\u003e 6,\n    'passed_runs' =\u003e 5,\n    'failed_runs' =\u003e 1,\n    'passed_runs_percent' =\u003e round(5/6*100, 2),\n    'failed_runs_percent' =\u003e 100-round(5/6*100, 2),\n    'nb_assertions' =\u003e 18,\n    'passed_assertions' =\u003e 17,\n    'failed_assertions' =\u003e 1,\n    'passed_assertions_percent' =\u003e round(17/18*100, 2),\n    'failed_assertions_percent' =\u003e 100-round(17/18*100, 2)\n]);\n```\n- TESTING PROTECTED/PRIVATE AND/OR STATIC METHODS IN CLASSES\n\nTo be able to test any `protected` or `private` (`static` or not) method, \nyou must use `$pilot-\u003erunClassMethod(...)` instead of `$pilot-\u003erun(...)`.\nThe signature of the method is:\n```php\npublic function runClassMethod(\n    int|string|null $id,\n    object|string $class,\n    string $description = '',\n    ?string $method = null,\n    array $params = [],\n)\n```\nPlease note:\n- if the class has a complex constructor with required arguments, then you must\nprovide a clean object instance for `$class`.\n- in other cases, `$class` can be a string like `Foo` or even `Foo::method`. \nThis work for classes without constructor or with one that have no required parameters.\n- The array `$params` must have all the required parameters for the invocation \nof the method. It's also compatible with named parameters.\n\nEverything else is similar to the method `$pilot-\u003erun()`.\n\nLet's have an example from the php test file:\nHere all tests are equivalent:\n```php\n$foo = new Foo();\n$pilot-\u003erunClassMethod(\n    id: '008',\n    description: 'private method unit test using directly an instance of Foo',\n    class: $foo, // instance\n    method: 'abc',\n);\n$pilot-\u003eassertIsString();\n$pilot-\u003eassertEqual('abc');\n\n$pilot-\u003erunClassMethod(\n    id: '009',\n    description: 'private method unit test using string notation for the class Foo',\n    class: 'Foo', // class name \n    method: 'abc',\n);\n$pilot-\u003eassertIsString();\n$pilot-\u003eassertEqual('abc');\n\n$pilot-\u003erunClassMethod(\n    id: '010',\n    description: 'private method unit test using short string notation for the class Foo and the method abc',\n    class: 'Foo::abc', // short notation\n);\n$pilot-\u003eassertIsString();\n$pilot-\u003eassertEqual('abc');\n```\nHave a look at the call of a private method with two parameters and another call\nto a `private static function()`: \n```php\n$pilot-\u003erunClassMethod(\n    id: '012',\n    description: 'private method unit test with two parameters',\n    class: 'Foo',\n    method: 'hij',\n    params: ['p' =\u003e 25, 'q' =\u003e 50]  // or [25, 50] \n);\n$pilot-\u003eassertIsInt();\n$pilot-\u003eassertEqual(250);\n\n$pilot-\u003erunClassMethod(\n    id: '018',\n    description: 'private static method unit test',\n    class: 'Foo::tuv',\n);\n$pilot-\u003eassertIsString();\n$pilot-\u003eassertEqual('tuv');\n```\nThe named parameters must follow the same order as defined in the function prototype.\n\n- REPORT\n\nThe engine compute internally the data and, you can ask for a HTML report, as\nsimply as:\n```php\n$pilot-\u003ecreateReport();\n```\nYou will find in the repository two reports: one totally passed and another one failed.\nYou'll exactly see how the engine follows the runs and what kind of data is kept. \n\n- HELPERS\n\nYou can create your own helpers to validate any result using à simple `Closure`.\nHave a look at:\n```php\n//region equal\n$equal = function(mixed $to): void {\n    /** @var Pilot $this */\n    if ($this-\u003ecurrent_runner-\u003egetResult() === $to) {\n        $this-\u003eaddSuccess('equal');\n    } else {\n        $this-\u003eaddFailure(expected: 'Equal to: '.print_r($to, true));\n    }\n};\n$helpers['assertEqual'] = $equal;\n//endregion\n```\nThis assertion is one of the standard library and is injected right after the \nstart of a new project.\nIt is also possible to define a helper on the fly using \n`$pilot-\u003eaddHelper($name, $closure);`.\n\n- NESTED RUNS\n\nFor really complex tests, you can also define nested runs.\n```php\n$pilot-\u003erun(\n    id: 'abc',\n    description: 'complex nested tests',\n    test: function() use ($pilot) {\n        // nested run\n        $pilot-\u003erun(\n            id: 'def',\n            description: 'nested run',\n            test: function() {\n                // ...\n                return $foo;\n            }\n        )\n        // careful: this applies to the (latest) run which is here 'def'\n        $pilot-\u003eassertIsArray();\n        \n        return []; // a run MUST always return a value\n    }\n);\n// careful, if you continue the assertions here, by default they will apply to the\n// (latest) run which is still 'def'; you must change the current runner to work with the previous one\n$pilot-\u003esetCurrentRunnerTo('abc');\n$pilot-\u003eassertIsArray(); // now it applies to the run 'abc'\n```\nThis is the only tricky point of `Exacodis`. This keeps the code more readable as there's \nno need to have tons of parameters for each function call.   \n\nEnjoy!\n\n**rawsrc**","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frawsrc%2Fexacodis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frawsrc%2Fexacodis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frawsrc%2Fexacodis/lists"}