{"id":16218125,"url":"https://github.com/quasilyte/ktest","last_synced_at":"2026-01-20T18:56:39.057Z","repository":{"id":47777182,"uuid":"349142817","full_name":"quasilyte/ktest","owner":"quasilyte","description":"Test and benchmark KPHP code","archived":false,"fork":false,"pushed_at":"2021-08-24T10:29:29.000Z","size":719,"stargazers_count":3,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-13T22:53:24.010Z","etag":null,"topics":["benchmarking","kphp","php","testing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/quasilyte.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-03-18T16:25:45.000Z","updated_at":"2022-11-14T19:03:10.000Z","dependencies_parsed_at":"2022-08-24T13:10:19.289Z","dependency_job_id":null,"html_url":"https://github.com/quasilyte/ktest","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quasilyte%2Fktest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quasilyte%2Fktest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quasilyte%2Fktest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/quasilyte%2Fktest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/quasilyte","download_url":"https://codeload.github.com/quasilyte/ktest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247733045,"owners_count":20986984,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["benchmarking","kphp","php","testing"],"created_at":"2024-10-10T11:48:36.012Z","updated_at":"2026-01-20T18:56:39.042Z","avatar_url":"https://github.com/quasilyte.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](docs/logo_small.png)\n\n## Overview\n\n`ktest` is a tool that makes [kphp](https://github.com/VKCOM/kphp/) programs easier to test.\n\n* `ktest phpunit` can run [PHPUnit](https://github.com/sebastianbergmann/phpunit) tests using KPHP\n* `ktest bench` run benchmarks using KPHP\n* `ktest bench-php` run benchmarks using PHP\n* `ktest bench-vs-php` run benchmarks using both KPHP and PHP, compare the results\n* `ktest benchstat` compute and compare statistics about benchmark results (see [benchstat](https://godoc.org/golang.org/x/perf/cmd/benchstat))\n* `ktest env` print ktest-related env variables\n\n## Example - phpunit\n\nImagine that we have an ordinary `PHPUnit` test:\n\n```php\n\u003c?php\n\nuse PHPUnit\\Framework\\TestCase;\nuse ExampleLib\\Strings;\n\nclass StringsTest extends TestCase {\n    public function testContains() {\n        $this-\u003eassertSame(Strings::contains('foo', 'bar'), false);\n        $this-\u003eassertTrue(Strings::contains('foo', 'foo'));\n    }\n\n    public function testHasPrefix() {\n        $this-\u003eassertSame(Strings::hasPrefix('Hello World', 'Hello'), true);\n        $this-\u003eassertFalse(Strings::hasPrefix('Hello World', 'ello'));\n    }\n}\n```\n\nIt comes without a surprise that you can run it with `phpunit` tool:\n\n```bash\n$ ./vendor/bin/phpunit tests\n\n......                                                              6 / 6 (100%)\n\nTime: 70 ms, Memory: 4.00 MB\n\nOK (6 tests, 14 assertions)\n```\n\nWhen you're using `phpunit`, tests are executed as PHP, not KPHP.\n\n`ktest` makes it possible to run your phpunit-compatible tests with KPHP:\n\n```bash\n$ ktest phpunit tests\n\n.... 4 / 6 (66%) OK\n.. 6 / 6 (100%) OK\n\nTime: 10.74657386s\n\nOK (6 tests, 14 assertions)\n```\n\n\u003e Note that running KPHP tests is slower: a separate binary is compiled per every Test class.\n\nAll you need is `ktest` utility and installed [kphpunit](https://github.com/quasilyte/kphpunit) package:\n\n```bash\n$ composer require --dev quasilyte/kphpunit\n```\n\nNow let's do something more exciting.\n\nTake a look at this `Integers::getFirst` method:\n\n```php\n\u003c?php\n\nnamespace Foo\\Bar;\n\nclass Integers {\n    /** @param int[] $xs */\n    public static function getFirst(array $xs) {\n        return $xs[0];\n    }\n}\n```\n\nIt's intended to return the first int array item, or `null`, if index 0 is unset.\n\nWe can write a test for this method:\n\n```php\n\u003c?php\n\nuse PHPUnit\\Framework\\TestCase;\nuse Foo\\Bar\\Integers;\n\nclass IntegersTest extends TestCase {\n    public function testGetFirst() {\n        $this-\u003eassertSame(Integers::getFirst([]), null);\n        $this-\u003eassertSame(Integers::getFirst([1]), 1);\n    }\n}\n```\n\nAll tests are passing:\n\n```\n.                                                                   1 / 1 (100%)\n\nTime: 36 ms, Memory: 4.00 MB\n\nOK (1 test, 2 assertions)\n```\n\nBut if you try to run it with `ktest`, you'll see how that code would behave in KPHP:\n\n```\nF 1 / 1 (100%) FAIL\n\nTime: 4.59874429s\n\nThere was 1 failure:\n\n1) IntegersTest::testGetFirst\nFailed asserting that null is identical to 0.\n\nIntegersTest.php:8\n\nFAILURES!\nTests: 1, Assertions: 1, Failures: 1.\n```\n\nAccessing unset array index can yield a \"zero value\" instead of null.\n\nRunning with `ktest` makes it easier to ensure that your code behaves identically in both PHP and KPHP.\n\n## Example - bench\n\nThere are 2 main ways to do benchmarking with `bench` subcommand:\n\n1. Run different benchmarks and see how they relate\n2. Run benchmarks by the same name and compare samples with [benchstat](https://pkg.go.dev/golang.org/x/perf/cmd/benchstat?utm_source=godoc)\n\nLet's assume that you have a function that concatenates 3 strings. You can write a benchmark for it:\n\n```php\n\u003c?php\n\n// file \"BenchmarkConcat3.php\"\n\nclass BenchmarkConcat3 {\n    private static $strings = [\n        'foo',\n        'bar',\n        'baz',\n    ];\n\n    public function benchmarkConcat() {\n        return self::$strings[0] . self::$strings[1] . self::$strings[2];\n    }\n}\n```\n\nThis benchmark can be executed with a `bench` subcommand:\n\n```bash\n$ ktest bench BenchmarkConcat3.php\nclass BenchmarkConcat3\nBenchmarkConcat3::benchmarkConcat\t106500\t372.0 ns/op\nok BenchmarkConcat3 147.153797ms\n```\n\nSuppose that somebody proposed to re-write this function with `ob_start()` claiming that it would make things faster.\n\nFirst, we need to collect samples of the current implementation. We need at least 5 rounds, but usually the more - the better (don't get too crazy though, 10 is good enough in most cases).\n\n```bash\n$ ktest bench -count 5 Concat3Benchmark.php | tee old.txt\n```\n\nNow we have old implementation results, it's time to roll the a implementation:\n\n```php\n\u003c?php\n\nclass Concat3Benchmark {\n    private static $strings = [\n        'foo',\n        'bar',\n        'baz',\n    ];\n\n    public function benchmarkConcat() {\n        ob_start();\n        echo self::$strings[0];\n        echo self::$strings[1];\n        echo self::$strings[2];\n        return ob_get_clean();\n    }\n}\n```\n\nNow we need to collect the new implementation results:\n\n```bash\n$ ktest bench -count 5 Concat3Benchmark.php | tee new.txt\n```\n\nWhen you have 2 sets of samples, it's possible to compare them with benchstat:\n\n```\n$ ktest benchstat old.txt new.txt\nname    old time/op  new time/op  delta\nConcat   372ns ± 2%   546ns ± 6%  +46.91%  (p=0.008 n=5+5)\n```\n\nAs we can see, the new implementation is, in fact, almost 2 times slower!\n\n## TODO\n\n* Mocks\n\n## Limitations\n\n* Assert functions can't be used for objects (class instances)\n* No custom comparators for assert functions\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquasilyte%2Fktest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fquasilyte%2Fktest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquasilyte%2Fktest/lists"}