gs-datagen/README.md
Pat Garrity 768afc80c9
Some checks failed
/ Build and Release Library (push) Failing after 54s
(patch) Add automated builds to the project (#1)
Reviewed-on: #1
2024-05-01 01:31:13 +00:00

150 lines
3.5 KiB
Markdown

# gs-datagen
[GS Open Source](https://garrity.co/oss.html) |
[License (MIT)](./LICENSE)
Test data generation library for Scala 3. `gs-datagen` provides _composable_
generator definitions that can be reused across tests.
- [Usage](#usage)
- [Imports](#imports)
- [Examples](#examples)
- [Example: Generate a Random User](#example-generate-a-random-user)
- [Example: Require Input](#example-require-input)
- [Supported Generators](#supported-generators)
## Usage
```
object Gs {
val Datagen: ModuleID =
"gs" %% "gs-datagen-core-v0" % "$VERSION"
}
```
### Imports
The standard way to use `gs-datagen` is to import the entire package, which
pulls in `Gen[A]`, `Generated[A]` and `Datagen[A, -I]`:
```scala
import gs.datagen.v0.*
```
## Examples
For the following examples, the following types (abbreviated) are relevant:
```scala
opaque type Name = String
opaque type DateOfBirth = LocalDate
opaque type Karma = Long
enum Role:
case Regular, Mod, Admin
case class User(
name: Name,
dateOfBirth: DateOfBirth,
karma: Karma,
role: Role
)
```
### Example: Generate a Random User
One way to go about this is to define generators for each type and then compose
those generators for `User`:
```scala
import gs.datagen.v0._
import gs.datagen.v0.generators.MinMax
val nameGen: Gen[Name] =
Gen.string.alpha(4, 16).map(Name(_))
val dateOfBirthGen: Gen[LocalDate] =
Gen.date.beforeToday(
days = MinMax.Zero,
months = MinMax.nonNegative(0, 11),
years = MinMax.nonNegative(18, 80)
).map(DateOfBirth(_))
val karmaGen: Gen[Karma] =
Gen.long.inRange(-100L, 100L).map(Karma(_))
val roleGen: Gen[Role] =
Gen.oneOf.fixedChoices(Role.Regular, Role.Mod, Role.Admin)
```
and the composition:
```scala
val userGen: Gen[User] =
for
name <- nameGen
dateOfBirth <- dateOfBirthGen
karma <- karmaGen
role <- roleGen
yield User(name, dateOfBirth, karma, role)
```
Generators may also be defined inline if separate definitions are not useful:
```scala
val userGen: Gen[User] =
for
name <- Gen.string.alpha(4, 16).map(Name(_))
dateOfBirth <- dateOfBirthGen
karma <- Gen.long.inRange(-100L, 100L).map(Karma(_))
role <- roleGen
yield User(name, dateOfBirth, karma, role)
```
Once that generator exists, users may be generated:
```scala
val user: User = userGen.gen()
```
The `Generated` type class can be used as well:
```scala
given Generated[User] = Generated.of(userGen)
val user2: User = Generated[User].generate()
```
### Example: Require Input
What if we want a way to generate users randomly, but always require the caller
to specify a user role? This can be accomplished by requiring input when
generating data:
```scala
val roleUserGen: Datagen[User, Role] =
for
name <- Gen.string.alpha(4, 16).map(Name(_))
dateOfBirth <- dateOfBirthGen
karma <- Gen.long.inRange(-100L, 100L).map(Karma(_))
yield (role: Role) => User(name, dateOfBirth, karma, role)
```
The usage of this type is different and does not support `Generated`:
```scala
val admin: User = roleUserGen.generate(Role.Admin)
val mod: User = roleUserGen.generate(Role.Mod)
val regular: User = roleUserGen.generate(Role.Regular)
```
## Supported Generators
For a complete list of generators supported out of the box, please refer to the
[Gen](modules/core/src/main/scala/gs/datagen/v0/gen.scala) definition,
which enumerates and documents all options.
## Donate
Enjoy this project or want to help me achieve my [goals](https://garrity.co)?
Consider [Donating to Pat on Ko-fi](https://ko-fi.com/gspfm).