{"id":15107771,"url":"https://github.com/vkcom/ktest","last_synced_at":"2025-09-27T06:32:08.665Z","repository":{"id":40324514,"uuid":"397184926","full_name":"VKCOM/ktest","owner":"VKCOM","description":"Test and benchmark KPHP code","archived":true,"fork":false,"pushed_at":"2022-12-05T13:00:05.000Z","size":1314,"stargazers_count":9,"open_issues_count":2,"forks_count":2,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-09-25T21:41:26.232Z","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/VKCOM.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-08-17T09:11:05.000Z","updated_at":"2024-04-23T13:22:58.000Z","dependencies_parsed_at":"2023-01-23T02:15:53.992Z","dependency_job_id":null,"html_url":"https://github.com/VKCOM/ktest","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VKCOM%2Fktest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VKCOM%2Fktest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VKCOM%2Fktest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VKCOM%2Fktest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VKCOM","download_url":"https://codeload.github.com/VKCOM/ktest/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219871956,"owners_count":16554471,"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-09-25T21:41:34.638Z","updated_at":"2025-09-27T06:32:03.363Z","avatar_url":"https://github.com/VKCOM.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](docs/readme_header.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 compare` run given script with PHP and KPHP, check that output is identical\n* `ktest bench` run benchmarks using KPHP\n* `ktest bench-ab` run two selected benchmarks using KPHP and compare their results\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## Installation\n\n```bash\n$ composer require --dev vkcom/ktest-script\n```\n\nIf composer is not an option or you want to install `ktest` binary globally, consider these options:\n\n* Download the `ktest` binary from the [latest release](https://github.com/VKCOM/ktest/releases)\n* or build `ktest` from sources\n\nYou may need to set `KPHP_ROOT` environment variable to point to your [KPHP repository](https://github.com/VKCOM/kphp/) folder:\n\n```bash\n$ git clone https://github.com/VKCOM/kphp.git\n$ export KPHP_ROOT=$(pwd)/kphp\n```\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$ ./vendor/bin/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/VKCOM/kphpunit) package:\n\n```bash\n$ composer require --dev vkcom/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$ ./vendor/bin/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$ ./vendor/bin/ktest bench -count 5 BenchmarkConcat3.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 BenchmarkConcat3 {\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$ ./vendor/bin/ktest bench -count 5 BenchmarkConcat3.php | tee new.txt\n```\n\nWhen you have 2 sets of samples, it's possible to compare them with benchstat:\n\n```\n$ ./vendor/bin/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%2Fvkcom%2Fktest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvkcom%2Fktest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvkcom%2Fktest/lists"}