{"id":16965024,"url":"https://github.com/propensive/probably","last_synced_at":"2025-07-07T12:09:54.330Z","repository":{"id":44898867,"uuid":"108000061","full_name":"propensive/probably","owner":"propensive","description":"To probe what we can't prove, so the unprovable may become probable; testing for Scala","archived":false,"fork":false,"pushed_at":"2025-02-11T23:45:24.000Z","size":4865,"stargazers_count":55,"open_issues_count":8,"forks_count":12,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-07-04T22:48:37.626Z","etag":null,"topics":["assertion","fury","property-testing","scala","testing","testing-tool","testing-tools"],"latest_commit_sha":null,"homepage":"https://propensive.com/probably/","language":"Scala","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/propensive.png","metadata":{"files":{"readme":".github/readme.md","changelog":null,"contributing":".github/contributing.md","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}},"created_at":"2017-10-23T15:18:50.000Z","updated_at":"2025-02-11T23:45:28.000Z","dependencies_parsed_at":"2023-10-31T09:27:03.658Z","dependency_job_id":"b41c6c49-aa82-44fc-b644-9d8c94342392","html_url":"https://github.com/propensive/probably","commit_stats":{"total_commits":494,"total_committers":9,"mean_commits":"54.888888888888886","dds":"0.15384615384615385","last_synced_commit":"9e7d27785b4f77ff6616c919ad1c634815415e33"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/propensive/probably","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fprobably","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fprobably/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fprobably/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fprobably/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/propensive","download_url":"https://codeload.github.com/propensive/probably/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/propensive%2Fprobably/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264075705,"owners_count":23553512,"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":["assertion","fury","property-testing","scala","testing","testing-tool","testing-tools"],"created_at":"2024-10-13T23:44:50.839Z","updated_at":"2025-07-07T12:09:54.314Z","avatar_url":"https://github.com/propensive.png","language":"Scala","readme":"[\u003cimg alt=\"GitHub Workflow\" src=\"https://img.shields.io/github/actions/workflow/status/propensive/probably/main.yml?style=for-the-badge\" height=\"24\"\u003e](https://github.com/propensive/probably/actions)\n[\u003cimg src=\"https://img.shields.io/discord/633198088311537684?color=8899f7\u0026label=DISCORD\u0026style=for-the-badge\" height=\"24\"\u003e](https://discord.com/invite/MBUrkTgMnA)\n\u003cimg src=\"/doc/images/github.png\" valign=\"middle\"\u003e\n\n# Probably\n\n__To probe what we can't prove, so what isn't provable may be probable__\n\n__Probably__ is a testing library designed to unintrusively provide test recording and reporting capabilities to any\ncodebase, regardless of the users\u0026rsquo; choices of libraries or programming paradigms. __Probably__ can define and run\nunit tests and property tests. Its syntax is simple and unexciting, and its execution model has zero magic:\nit\u0026rsquo;s the same as for any other program.\n\n## Features\n\n- no framework, reflection or control flow to understand\n- ScalaCheck-style property testing\n- tests may be run multiple times, with results aggregated\n- automatic derivation of arbitrary instances\n- functional API where it matters; impure calls where it's safe and practical\n\n\n## Availability\n\n\n\n\n\n\n\n## Getting Started\n\n_Probably_ defines only two primary types: a mutable `Runner` for recording test results and reporting back on\nthem, and `Test` definitions, whose instances are created by the `Runner`.\n\nAlthough it is possible to construct and use different `Runner`s, the most typical usage is to use the global\nsingleton `Runner` called `test`, because for most purposes only one `Runner` will be required. Defining a test\nis simple. For example,\n```scala\nimport probably.*\n\ntest(t\"the sum of two identical integers is divisible by two\"):\n  val x: Int = 7\n  x + x\n.assert(_%2 == 0)\n```\n\nNote that the assertion takes a predicate lambda, which operates on the result from evaluating the body of the\ntest. It does not operate on the value directly. This clearly separates the process of running the test from the\ncheck which is performed upon it.\n\nWhen a test definition like this is encountered in running code, its body will be evaluated, and a predicate\n(defined as a parameter to `assert`) will be evaluated on the result of the body. The outcome of this will be\none of four possibilities:\n- the predicate returns true, and the test passes\n- it returns false, and the test fails\n- an exception is thrown while the body is being evaluated\n- an exception is thrown while the assertion is being evaluated\n\nThese different cases will be distinguished in the test report.\n\nIt is important to note that a test can be defined anywhere, such as,\n- in the `main` method of an application\n- in a `lazy val`\n- inside an asynchronous `Task`\n- in a parameterized method, testing a property of that parameter\n- in the request handler on a web server\n- in a pattern extractor (`unapply` method)\n- inside an actor\n- in the Scala REPL\n- as one branch of a case clause\n- nested inside another test\n\nRegardless of where the test is defined, the behavior is always the same: it will be evaluated, checked, and the\nresult will be recorded in the `Runner`, as a side-effect. Tests may be run more than once (in which case they\nare recorded more than once, and aggregated) or not at all if, by virtue of some runtime criterion, they are\nsimply not executed. The question of whether the test is executed is the same for \n\nThe decision to make the `Runner` mutable reflects the power of Scala's hybrid nature. The state of the `Runner`\nis write-only while the tests are being run, so many of the common concurrency problems which arise with mutable\nstate do not apply. The `Runner` has one read-only method, `report()`, which will produce a summary report of\nthe recorded test results. Reports may be produced many times, but normally `report()` is called just once, at\nthe end. This conscious and careful compromise in functional purity buys convenience: integration of tests does\nnot impose constraints on new code, or require non-local changes to existing code.\n\n### Parameterized tests\n\nAs tests may appear anywhere, they are easy to parameterize. We could, for example, rewrite the test above like\nso,\n```scala\nimport probably.*\n\ndef runTest(x: Int): Unit =\n  test(t\"the sum of three identical integers is divisible by 3\"):\n    x + x + x\n  .assert(_%3 == 0)\n\nrunTest(2)\nrunTest(50)\nrunTest(Int.MaxValue)\n```\n\nHowever, if the test were to fail, it would be useful to know what input caused it to fail. Any number of inputs\ncan be logged by including them as additional named parameters after the test name, like this:\n```scala\nimport probably.*\n\ndef runTest(x: Int): Unit =\n  test(t\"the sum of three identical integers is divisible by 3\", input = x):\n    x + x + x\n  .assert(_%3 == 0)\n```\n\nThe choice of the parameter name `input` is the user\u0026rsquo;s choice: any name that is a valid identifier may be\nchosen. The output from running the above tests will be displayed like this:\n![Screenshot of test results showing input=2147483647](doc/images/failure.png)\n\n### Property-based testing\n\nThe ability to run the same test multiple times with different parameters suggests an obvious approach to\nproperty-based testing: to run the same test over and over again with a stream of different inputs. _Probably_\nalso provides the means to generate such streams of increasingly-obscure instances for a variety of primitive\ntypes, and will derive generators on-demand for case-class and sealed-trait types for which generators exist for\neach of the parameters.\n\n```scala\nimport probably.*\n\ncase class Person(name: Text, age: Int)\n\nGenerate.stream[Person](1000).foreach { person =\u003e\n  test(t\"all persons have realistic ages\", v = person):\n    person.age\n  .assert { a =\u003e a \u003e= 0 \u0026\u0026 a \u003c 100 }\n}\n```\n\nFor a given `Seed`, the pseudorandom data generated will always be deterministic and hence repeatable.\n\n### Command-line Interface\n\n_Probably_ comes with a simple CLI runner for running test suites through the standard shell interface. This\nworks particularly well for objects containing a series of unit tests. To use the command-line interface,\ncreate an object which extends `Suite`, giving the test suite a name. Then implement the `run` method to execute\nthe tests, in order, like so:\n```scala\nobject ProjectTests extends Suite(\"Project tests\"):\n  def run(using Runner): Unit =\n    test(t\"first test\"):\n      // test body\n    .assert(/* predicate */)\n```\n\nThe `Suite` class provides an implementation of a `main` method, so any object which subclasses `Suite` may be\nrun from the command line.\n\n### Test Expression\n\n_Probably_ provides a second way of defining a test: as an expression. For example,\n```scala\nimport probably.*\nimport java.io.*\n\ntest(t\"check the backup exists\"):\n  File(\"data.bak\")\n.check(_.exists).setReadOnly()\n```\n\nThis style should look familiar, apart from one superficial difference: the test predicate is applied to a\nmethod called `check` instead of `assert`. This transforms the test from a statement into an expression, which\nmeans that it returns the result of its body, instead of `Unit`. Note that it returns the value, regardless of\nwhether the test passes or fails, and execution continues.\n\nThis confers a few further differences with assertion tests:\n- exceptions thrown inside the body are not caught (but are recorded); exceptions in the check are still caught\n- test expressions cannot be skipped; their return value is necessary for execution to continue\n\n### Test Suites\n\nA test suite is a convenient grouping of related tests, and can be launched from a runner (the value `test` in\nthe following example) like so:\n```scala\ntest.suite(\"integration tests\") { test =\u003e\n  test(t\"end-to-end process\"):\n    System.process()\n  .assert(_.isSuccess)\n}\n```\n\nLike other tests, a suite has a name, and will be executed at the point it is defined, and like other tests, it\nwill pass or fail (or, produce mixed results). Its body, however, is a lambda which introduces a new `Runner`\ninstance which will be used to run the tests in the suite. By convention, the new `Runner` is also named `test`.\nThis will shadow the outer one, which is usually the desired behavior.\n\nWhen the test suite completes, its results are aggregated into the report of the runner which spawned it. If you\nlaunched it using the CLI, the table of results will show the nested tests indented.\n\nThe `Runner` introduced by the `suite` method is the same as any other `Runner`, so further test suites can be\ndefined inside other test suites, making it possible to organise tests into a hierarchy.\n\n\n\n\n\n## Status\n\nProbably is classified as __maturescent__. For reference, Soundness projects are\ncategorized into one of the following five stability levels:\n\n- _embryonic_: for experimental or demonstrative purposes only, without any guarantees of longevity\n- _fledgling_: of proven utility, seeking contributions, but liable to significant redesigns\n- _maturescent_: major design decisions broady settled, seeking probatory adoption and refinement\n- _dependable_: production-ready, subject to controlled ongoing maintenance and enhancement; tagged as version `1.0.0` or later\n- _adamantine_: proven, reliable and production-ready, with no further breaking changes ever anticipated\n\nProjects at any stability level, even _embryonic_ projects, can still be used,\nas long as caution is taken to avoid a mismatch between the project's stability\nlevel and the required stability and maintainability of your own project.\n\nProbably is designed to be _small_. Its entire source code currently consists\nof 840 lines of code.\n\n## Building\n\nProbably will ultimately be built by Fury, when it is published. In the\nmeantime, two possibilities are offered, however they are acknowledged to be\nfragile, inadequately tested, and unsuitable for anything more than\nexperimentation. They are provided only for the necessity of providing _some_\nanswer to the question, \"how can I try Probably?\".\n\n1. *Copy the sources into your own project*\n   \n   Read the `fury` file in the repository root to understand Probably's build\n   structure, dependencies and source location; the file format should be short\n   and quite intuitive. Copy the sources into a source directory in your own\n   project, then repeat (recursively) for each of the dependencies.\n\n   The sources are compiled against the latest nightly release of Scala 3.\n   There should be no problem to compile the project together with all of its\n   dependencies in a single compilation.\n\n2. *Build with [Wrath](https://github.com/propensive/wrath/)*\n\n   Wrath is a bootstrapping script for building Probably and other projects in\n   the absence of a fully-featured build tool. It is designed to read the `fury`\n   file in the project directory, and produce a collection of JAR files which can\n   be added to a classpath, by compiling the project and all of its dependencies,\n   including the Scala compiler itself.\n   \n   Download the latest version of\n   [`wrath`](https://github.com/propensive/wrath/releases/latest), make it\n   executable, and add it to your path, for example by copying it to\n   `/usr/local/bin/`.\n\n   Clone this repository inside an empty directory, so that the build can\n   safely make clones of repositories it depends on as _peers_ of `probably`.\n   Run `wrath -F` in the repository root. This will download and compile the\n   latest version of Scala, as well as all of Probably's dependencies.\n\n   If the build was successful, the compiled JAR files can be found in the\n   `.wrath/dist` directory.\n\n## Contributing\n\nContributors to Probably are welcome and encouraged. New contributors may like\nto look for issues marked\n[beginner](https://github.com/propensive/probably/labels/beginner).\n\nWe suggest that all contributors read the [Contributing\nGuide](/contributing.md) to make the process of contributing to Probably\neasier.\n\nPlease __do not__ contact project maintainers privately with questions unless\nthere is a good reason to keep them private. While it can be tempting to\nrepsond to such questions, private answers cannot be shared with a wider\naudience, and it can result in duplication of effort.\n\n## Author\n\nProbably was designed and developed by Jon Pretty, and commercial support and\ntraining on all aspects of Scala 3 is available from [Propensive\nO\u0026Uuml;](https://propensive.com/).\n\n\n\n## Name\n\nThe name _Probably_ acknowledges an appropriate level of confidence in writing tests which _probe_ the functionality of a program, in contrast to using types which (in theory, at least) _prove_ it.\n\nIn general, Soundness project names are always chosen with some rationale,\nhowever it is usually frivolous. Each name is chosen for more for its\n_uniqueness_ and _intrigue_ than its concision or catchiness, and there is no\nbias towards names with positive or \"nice\" meanings—since many of the libraries\nperform some quite unpleasant tasks.\n\nNames should be English words, though many are obscure or archaic, and it\nshould be noted how willingly English adopts foreign words. Names are generally\nof Greek or Latin origin, and have often arrived in English via a romance\nlanguage.\n\n## Logo\n\nThe logo shows a twenty-sided die, an icosahedron, alluding to probabilistic chance.\n\n## License\n\nProbably is copyright \u0026copy; 2025 Jon Pretty \u0026 Propensive O\u0026Uuml;, and\nis made available under the [Apache 2.0 License](/license.md).\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fprobably","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpropensive%2Fprobably","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpropensive%2Fprobably/lists"}