{"id":23902697,"url":"https://github.com/mtumilowicz/scala-zio2-test-aspects-property-based-testing-workshop","last_synced_at":"2026-06-30T05:31:08.327Z","repository":{"id":110879199,"uuid":"555551174","full_name":"mtumilowicz/scala-zio2-test-aspects-property-based-testing-workshop","owner":"mtumilowicz","description":"Introduction to test aspects and property based testing.","archived":false,"fork":false,"pushed_at":"2024-03-17T14:52:05.000Z","size":90,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-09T10:02:49.718Z","etag":null,"topics":["aspect","aspect-oriented","aspect-oriented-programming","aspects","functional-programming","polymorphic-functions","property-based-testing","property-testing","scala","test","testing","workshop","workshop-materials","zio","zio-effect","zio-test","zio2"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mtumilowicz.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-10-21T20:11:11.000Z","updated_at":"2022-11-06T13:35:08.000Z","dependencies_parsed_at":"2024-03-17T15:51:41.066Z","dependency_job_id":null,"html_url":"https://github.com/mtumilowicz/scala-zio2-test-aspects-property-based-testing-workshop","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mtumilowicz/scala-zio2-test-aspects-property-based-testing-workshop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-zio2-test-aspects-property-based-testing-workshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-zio2-test-aspects-property-based-testing-workshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-zio2-test-aspects-property-based-testing-workshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-zio2-test-aspects-property-based-testing-workshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mtumilowicz","download_url":"https://codeload.github.com/mtumilowicz/scala-zio2-test-aspects-property-based-testing-workshop/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-zio2-test-aspects-property-based-testing-workshop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34954283,"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-06-30T02:00:05.919Z","response_time":92,"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":["aspect","aspect-oriented","aspect-oriented-programming","aspects","functional-programming","polymorphic-functions","property-based-testing","property-testing","scala","test","testing","workshop","workshop-materials","zio","zio-effect","zio-test","zio2"],"created_at":"2025-01-04T22:49:57.532Z","updated_at":"2026-06-30T05:31:08.292Z","avatar_url":"https://github.com/mtumilowicz.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://app.travis-ci.com/mtumilowicz/scala-zio2-test-aspects-property-based-testing-workshop.svg?branch=master)](https://app.travis-ci.com/mtumilowicz/scala-zio2-test-aspects-property-based-testing-workshop)\n[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\n\n# scala-zio2-test-aspects-property-based-testing-workshop\n\n* references\n    * [What's Cooking in ZIO Test by Adam Fraser](https://www.youtube.com/watch?v=JtfdcxgQ71E)\n    * [Using Aspects To Transform Your Code With ZIO Environment](https://www.youtube.com/watch?v=gcqWdNwNEPg)\n    * https://zio.dev/reference/observability/logging\n    * https://zio.dev/reference/test/aspects/\n    * https://zio.dev/reference/test/property-testing/\n    * https://www.zionomicon.com\n    * https://github.com/adamgfraser/0-to-100-with-zio-test\n    * https://docs.spring.io/spring-framework/docs/4.3.15.RELEASE/spring-framework-reference/html/aop.html\n    * https://dotty.epfl.ch/docs/reference/new-types/polymorphic-function-types.html\n    * https://github.com/zio/zio/issues/4601\n    * https://zio.dev/reference/test/property-testing/built-in-generators/\n    * [Kacper Korban - Scala 3, but I have trust issues](https://www.youtube.com/watch?v=DiL4MMc60LU)\n\n## preface\n\n* goals of this workshop\n    * introduction to\n        * functional programming aspects\n        * property based testing\n    * understanding how to use test aspects in practice\n    * creating data generators\n* workshops\n    * task1: implement generator of `Accounts`\n        * then derive it using `zio-test/magnolia`\n        * solution: `AccountGenerators`\n    * task2: derive generator of `Contributors`\n        * then switch it to generate `Contributor` from file `src/test/resources/contributors.txt`\n        * solution: `ContributorGenerators`\n    * task3: implement and plug aspect to set specific seed (`TestSeed.seed`) before each test\n        * solution: `MainSpec`\n    * task4: experiment with intentionally failing some tests to see a seed\n        * try to reproduce problem by setting correct seed in TestSeed\n\n## aspect oriented programming\n* lib: caliban\n    * example\n        ```\n        val api =\n            graphQL(???) @@\n                maxDepth(50) @@\n                timeout(3 seconds) @@\n                printSlowQueries(500 millis) @@\n                apolloTracing @@\n                apolloCaching\n        ```\n    * supports aspects (called wrappers) that allow modifying: query parsing, validation and execution\n* introduction\n    * in any domain there are cross-cutting concerns that are shared among different parts of our main program logic\n    * often these concerns are tangled with each part of our main program logic and scattered across different parts\n    * we want to increase the modularity of our programs by separating these concerns from our main program logic\n    * cross-cutting concerns are typically related to how we do something rather than what we are doing\n        * what level of authorization should this transfer require?\n        * how should this transfer be logged?\n        * how should this transfer be recorded to our database\n    * example: testing\n        * main program logic: tests\n        * concerns\n            * how many times should we run a test?\n            * what environments should we run the test on?\n            * what sample size should we use for property based tests?\n            * what degree of parallelism?\n            * what timeout to use?\n    * example: graphql\n        * main program logic: queries\n        * concerns\n            * what is the maximum depth of nested queries we should support\n            * what is the maximum number of fields we should support\n            * what timeout should we use?\n            * how should we handle slow queries?\n            * what kind of tracing and caching should we use?\n* traditional approach: metaprogramming\n    * example: AspectJ\n        ```\n        @Aspect\n        public class BeforeExample {\n\n            @Before(\"execution(* com.xyz.myapp.dao.*.*(..))\")\n            public void doAccessCheck() {\n                // ...\n            }\n\n        }\n        ```\n    * relies on implementation details such as class and method names that may change\n    * no longer able to statically type check if code is dynamically generated\n* functional approach: polymorphic functions\n    * aspects are polymorphic functions\n    * polymorphic function: scala3\n        ```\n        // A polymorphic method:\n        def foo[A](xs: List[A]): List[A] = xs.reverse\n\n        // A polymorphic function value:\n        val bar: [A] =\u003e List[A] =\u003e List[A]\n        //       ^^^^^^^^^^^^^^^^^^^^^^^^^\n        //       a polymorphic function type\n               = [A] =\u003e (xs: List[A]) =\u003e foo[A](xs)\n\n        ```\n    * example: zio\n        ```\n        trait Aspect[-R, +E] {\n            def apply[R1 \u003c: R, E1 \u003e: E, A](zio: ZIO[R1, E1, A]): ZIO[R1, E1, A]\n        }\n        ```\n        * potentially constraining the environment or widening the error type\n        * transforms the how but not the what\n        * composable\n            ```\n            implicit final class AspectSyntax[-R, +E, +A)(private val zio: ZIO[R, E, A]) {\n                def @@[R1 \u003c: R, E1 \u003e: E](aspect: Aspect[R1, E1]): ZIO[R1, E1, A] =\n                    aspect(zio)\n            }\n            ```\n## test aspects\n* example\n    ```\n    test(\"concurrency test\") {\n        ???\n    } timeout(60.seconds)\n    ```\n* seamlessly control how tests are executed\n    * example\n        * without aspects\n            ```\n            test(\"foreachPar preserves ordering\") {\n                val zio = ZIO.foreach(1 to 100) { _ =\u003e\n                    ZIO.foreachPar(1 to 100)(ZIO.succeed(_)).map(_ == (1 to 100))\n                }.map(_.forall(identity))\n                assert(zio)(isTrue)\n                }\n            }\n            ```\n        * with aspects\n            ```\n            test(\"foreachPar preserves ordering\") {\n                assert(ZIO.foreachPar(1 to 100)(ZIO.succeed(_)))(equalTo(1 to 100))\n                }\n            } @@ nonFlaky\n            ```\n* common test aspects\n    * diagnose - do a localized fiber dump if a test times out\n    * nonFlaky - run a test repeatedly to make sure it is stable\n    * timed - time a test to identify slow tests\n    * timeout - time out a test after specified duration\n    * tag - tag a test for reporting\n        * example: \"this test is about database\"\n* composable\n    * test @@ nonFlaky @@ timeout(60.seconds)\n    * apply to tests, suites or entire specs\n    * order matters\n        * repeat(10) @@ timeout(60s)\n        * timeout(60s) @@ repeat(10)\n* implementing aspects\n    * when we need access to test itself\n        ```\n        new TestAspect.PerTest.AtLeastR[TestEnvironment] {\n          override def perTest[R \u003e: Nothing \u003c: TestEnvironment, E \u003e: Nothing \u003c: Any]\n          (test: ZIO[R, TestFailure[E], TestSuccess])(implicit trace: Trace): ZIO[R, TestFailure[E], TestSuccess] = for {\n            result \u003c- test // here comes the logic and we have handle to test itself\n          } yield result\n        }\n        ```\n    * when we want to do something independent of test itself\n        * TestAspect.before(zio.Console.printLine(\"before each\"))\n        * TestAspect.beforeAll(zio.Console.printLine(\"before all\")) \n        * etc\n\n## property based testing\n* example: ZIO test\n    ```\n    test(\"encode and decode is an identity\") {\n        // check operator, one or more generators, assertion\n        check(genEvents) { event =\u003e\n            assert(decode(encode(event)))(equalTo(event))\n        }\n    }\n    ```\n* is an approach where the framework generates test cases\n* strategies\n    1. hard to prove, easy to verify\n        * example: `sorting`\n    1. there and back again\n        * example: `reverse(reverse(list)) == list`\n    1. different paths same destination\n        * example: inverting binary tree\n            ```\n            invertTree(Node(Leaf, 0, t)) == Node(invertTree(t), 0, Leaf)\n            ```\n* advantage: allows to quickly test a large number of test cases\n    * potentially: reveal not obvious counterexamples\n* typically generate ~ 100-200 test cases\n    * Int ~2 billion values\n    * complex data types =\u003e number of possibilities increases exponentially\n        * complement property based testing with traditional tests (for particular degenerate cases)\n* common mistake: generator is not general enough\n    * example: generating user input using ASCII\n        * what about: 普通话 ?\n* generator represents a distribution of potential values\n    * each time we run a property based test we sample values from that distribution\n        ```\n        final case class Gen[-R, +A](\n            sample: ZStream[R, Nothing, Sample[R, A]]\n        )\n\n        final case class Sample[-R, +A](\n            value: A,\n            shrinks: ZStream[R, Nothing, Sample[R, A]]\n        )\n        ```\n* create generators\n    * construct generators for each field\n    * combine with operators\n        * example: flatMap, map, oneOf, zip\n    * recommended: flexible, explicit, composable\n    * example\n        ```\n        val genAccountStatus = Gen.fromIterable(AccountStatus.values)\n        val genNonEmptyString = Gen.stringBounded(1, 10)(Gen.char).map(NonEmptyString.unsafeFrom)\n\n        val genAccount2: Gen[Any, Account] =\n          (Gen.uuid \u003c*\u003e // symbolic alias for zip and zipWith; generate values in parallel\n            genAccountStatus \u003c*\u003e\n            Gen.string1(Gen.char)\n            ).map { case (uuid, status, str) =\u003e Account(AccountId(uuid), status, NonEmptyString.unsafeFrom(str))\n          }\n        ```\n    * problem: sealed non-enum traits\n        * we don't have access to all values\n            * you need to explicitly enumerate values\n            * every new case class should be added to generator\n                * example\n                    ```\n                    sealed trait TransactionParameters\n                    case class BitcoinTransactionParameters(...) extends TransactionParameters\n                    case class EthereumTransactionParameters(...) extends TransactionParameters\n                    case class XrpTransactionParameters(...) extends TransactionParameters\n\n                    val genTransactionParameters: Gen[Any, TransactionParameters] =\n                        Gen.oneOf(genBitcoinTxParams, genEthereumTxParams, genXrpTxParams)\n                    ```\n                * easy to forget\n                    * solution: auto-deriving generator\n* auto-deriving generator\n    * mutual correspondence gen \u003c-\u003e derive\n        * gen -\u003e derive: DeriveGen.instance(genA)\n            ```\n            val genA: Gen[Any, A] = ...\n            val deriveGenA: DeriveGen[A] = DeriveGen.instance(genA)\n            ```\n        * derive -\u003e gen:\n            ```\n            val deriveGenA: DeriveGen[A] = ...\n            val genA: Gen[Any, A] = deriveGenA.derive\n            ```\n    * usually used for sealed non-enum hierarchies\n        * example\n            ```\n            sealed trait TransactionParameters\n            case class BitcoinTransactionParameters(...) extends TransactionParameters\n            case class EthereumTransactionParameters(...) extends TransactionParameters\n            case class XrpTransactionParameters(...) extends TransactionParameters\n\n            val deriveTransactionParameters: Gen[Any, TransactionParameters] =\n                DeriveGen[TransactionParameters]\n            ```\n    * deriving is macro-based\n        * it is sometimes hard to know which generators will be used\n            * especially in case of multi-files imports\n            * we cannot just use `show implicits` IJ option\n    * it is hard to maintain specific constraints in multi-file imports\n        * usually we require objects that are correct/valid for our tests\n            * correct/valid = not complete random\n            * only one implicit for each type allowed\n    * not composable\n        * solution: unpack the `DeriveGen` instance to get a `Gen` (composable)\n    * example\n        * from case classes\n            ```\n            val genAccount: Gen[Any, Account] = DeriveGen[Account] // implicit for each field\n            ```\n        * same file implicits\n            ```\n            val genActiveAccountStatus: Gen[Any, AccountStatus] = Gen.fromIterable(AccountStatus.activeStatuses)\n\n            implicit val deriveActiveAccountStatus: DeriveGen[AccountStatus] = DeriveGen.instance(genActiveAccountStatus)\n\n            val genActiveAccount: Gen[Any, Account] = DeriveGen[Account]\n            ```\n        * multi-file implicits\n            ```\n            object AccountStatusGenerators {\n                val genActiveAccountStatus: Gen[Any, AccountStatus] = Gen.fromIterable(AccountStatus.activeStatuses)\n\n                implicit val deriveActiveAccountStatus = DeriveGen.instance(genActiveAccountStatus)\n            }\n            ```\n            ```\n            import app.AccountStatusGenerators._\n\n            object AccountGenerators {\n\n                val genActiveAccount: Gen[Any, Account] = DeriveGen[Account]\n            }\n            ```\n    * lib: https://zio.dev/api/zio/test/magnolia/index.html\n* don't use filter - transform instead\n    * filtering = \"throw away\" data that doesn’t satisfy our predicate\n    * example\n        ```\n        val evens: Gen[Random, Int] = ints.map(n =\u003e if (n % 2 == 0) n else n + 1) // transformation\n        ```\n* shrinking\n    * counterexample will typically not be the \"simplest\"\n        * test framework tries to shrink failures to ones that\n            * are \"simpler\" (in some sense)\n                * example: smaller integers, smaller collections\n            * and still violate the property\n    * ZIO Test uses \"integrated shrinking\"\n        * every generator already knows how to shrink itself\n            * all operators keep this property\n            * example: generator of even integers can’t shrink to 1\n    * under the hood\n        * Sample contains a \"tree\" of possible \"shrinkings\" for the value\n            * root: original value\n        * invariants\n            * any given level: value earlier in the stream, must be \"smaller\" than later values\n            * all children must be \"smaller\" than their parents\n        * machinery\n            1. generate the first Sample in the shrink stream\n            1. test whether its value is also a counterexample to the property being tested\n                * counterexample =\u003e recurse on that sample\n                * not =\u003e repeat with the next Sample in shrink stream\n            * example: shrinking logic for int\n                * first tries to shrink to zero\n                * then to half the distance between counterexample and zero\n                * then to half that distance, and so on\n\n## seed\n* TestRandom service\n    * provides a testable implementation of the Random service\n    * serves as a purely functional random number generator\n        * implementation takes care of passing the updated seed\n        * we can set the seed and generate a value based on that seed\n            * default seed\n                ```\n                /**\n                 * An arbitrary initial seed for the `TestRandom`.\n                 */\n                val DefaultData: Data = Data(1071905196, 1911589680)\n                ```\n            * we could set/get seed using: TestRandom.getSeed / TestRandom.setSeed\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtumilowicz%2Fscala-zio2-test-aspects-property-based-testing-workshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmtumilowicz%2Fscala-zio2-test-aspects-property-based-testing-workshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtumilowicz%2Fscala-zio2-test-aspects-property-based-testing-workshop/lists"}