Skip to content

Commit

Permalink
Feature/samplers docs (#78)
Browse files Browse the repository at this point in the history
* Upgrading sampler docs

* Running tut compiler

* Simplifying tut menu

* Removing menu and moving samplers docsto a separate file

* Adding content badge and fixing tut compilation.
  • Loading branch information
alexflav23 committed Dec 29, 2018
1 parent a06c75b commit ffd65d2
Show file tree
Hide file tree
Showing 4 changed files with 574 additions and 484 deletions.
259 changes: 17 additions & 242 deletions docs/README.md
Expand Up @@ -7,53 +7,18 @@ latest version of `util` available. The badges are automatically updated when a

![Util](https://s3-eu-west-1.amazonaws.com/websudos/oss/logos/util.png "Outworkers Util")

### Table of contents ###

<ol>
<li><a href="#integrating-the-util-library">Integrating the util library</a></li>

<li>
<p><a href="#util-parsers">util-parsers</a></p>
<ul>
<li><a href="#option-parsers">Option parsers</a></li>
<li><a href="#applicative-parsers">Applicative parsers</a></li>
</ul>
</li>

<li>
<p><a href="#util-parsers">util-parsers-cats</a></p>
<ul>
<li><a href="#option-parsers">Option parsers</a></li>
<li><a href="#applicative-parsers">Applicative parsers</a></li>
</ul>
</li>

<li>
<p><a href="#util-testing">util-testing</a></p>
<ul>
<li><a href="#async-assertions">Async assertions</a></li>
<li><a href="#data-sampling">Data sampling</a></li>
<li><a href="#automated-sampling">Automated sampling</a></li>
</ul>
</li>

<li>
<p><a href="#util-zookeeper">util-zookeeper</a></p>
<ul>
<li><a href="#zookeeperinstance">ZooKeeperInstance</a></li>
<li><a href="#zookeeperconf>ZooKeeperConf</a></li>
</ul>
</li>

<li><a href="#contributors">Copyright</a></li>
<li><a href="#style-guidelines">Scala Style Guidelines</a></li>
<li><a href="#git-flow">Git Flow</a></li>
<li><a href="#contributing">Contributing</a></li>
<li><a href="#copyright">Copyright</a></li>
</ol>


### Integrating the util library ###
### Table of contents

- [Integrating the util library](#integrating-the-util-library)
- [Parsers](#util-parsers)
- [Optional parsers](#option-parsers)
- [Applicative parsers](#applicative-parsers)
- [Testing](#util-testing)
- [Automated data sampling](./samplers.md)
- [Contributors](#contributors)
- [Copyright](#copyright)

### Integrating the util library
<a href="#table-of-contents">Back to top</a>


Expand Down Expand Up @@ -95,197 +60,7 @@ Summary:
- The default timeout value is ```1 second```.


### Data sampling ###
<a href="#table-of-contents">Back to top</a>

This is a very common pattern we use in our testing and it's very easy to interchange this generation with something like ScalaCheck. The idea is very simple, you use type classes to define ways to sample a given type.
After you define such a one-time sampling type class instance, you have access to several methods that will allow you to generate test data.

It's useful to define such typeclass instances inside package objects, as they will be "invisibly" imported in to the scope you need them to. This is often really neat, albeit potentially confusing for novice Scala users.


```scala

import com.outworkers.util.testing._

case class MyAwesomeClass(
name: String,
age: Int,
email: String
)
```

You may notice this pattern is already available in better libraries such as ScalaMock and we are not trying to provide an alternative to ScalaMock or compete with it in any way. Our typeclass generator approach only becomes very useful where you really care about very specific properties of the data.
For instance, you may want to get a user with a valid email address, or you may use the underlying factories to get a name that reassembles the name of a real person, and so on.

It's also useful when you want to define specific ways in which hierarchies of classes are composed together into a sample. If generation for the sake of generation is all you care about, then ScalaMock is probably more robust.


### Automated sampling

<a href="#table-of-contents">Back to top</a>

One interesting thing that happens when using the `@sample` annotation is that using `gen` immediately after it will basically
give you an instance of your `case class` with the fields appropriately pre-filled, and some of the basic scenarios are also name aware.

What this means is that we try to make the data feel "real" with respect to what it should be. Let's take the below example:

```scala

import java.util.UUID

case class User(
id: UUID,
firstName: String,
lastName: String,
email: String
)
```
This is interesting and common enough. What's more interesting is the output of `gen`.

```scala
import com.outworkers.util.samplers._

object Examplers {
val user = gen[User]

user.trace()

/**
User(
id = 6be8914c-4274-40ee-83f5-334131246fd8
firstName = Lindsey
lastName = Craft
email = rparker@hotma1l.us
)
*/
}

```

So as you can see, the fields have been appropriately pre-filled. The email is a valid email, and the first and last name look like first and last names. For
anything that's in the default generation domain, including dates and country codes and much more, we have the ability to produce automated
appropriate values.

During the macro expansion phase, we check the annotation targets and try to infer the "natural" value based on the field name and type. So
if your field name is either "email" or "emailAddress" or anything similar enough, you will get an "email" back.


It is also possible to generate deeply nested case classes.

```scala

case class Address(
postcode: String,
firstLine: String,
secondLine: String,
thirdLine: String,
city: String,
country: String
)

case class GeoLocation(
longitude: BigDecimal,
latitude: BigDecimal
)

case class LocatedUser(
geo: GeoLocation,
address: Address,
user: User
)

object GenerationExamples {
val deeplyNested = gen[LocatedUser]
}

```

#### Creating custom samplers and adding new types

The automated sampling capability is a fairly simple but useful party trick. It relies
on the framework knowing how to generate basic things, such as `Int`, `Boolean`, `String`,
and so on, and the framework can then compose from these samplers to build them up
into any hierarchy of case classes you need.

But sometimes it will come short when it doesn't know how to generate a specific type. For example,
let's look at how we could deal with `java.sql.Date`, which has no implicit sample available by default.

```scala

case class ExpansionExample(
id: UUID,
date: java.sql.Date
)
```

Let's try to write some tests around the sampler. All we need to do is create a sampler for `java.sql.Date`.

```scala

import org.scalatest.{ FlatSpec, Matchers }
import java.time.{LocalDate, ZoneId}

class MyAwesomeSpec extends FlatSpec with Matchers {

implicit val sqlDateSampler = new Sample[java.sql.Date] {
override def sample: java.sql.Date = java.sql.Date.valueOf(LocalDate.now(ZoneId.of("UTC")))
}

"The samplers lib" should "automatically sample an instance of ExpansionExample" in {
val instance = gen[ExpansionExample]
}
}

```

Now, no matter how deeply nested in a case class structure the `java.sql.Date` is located inside a case class,
the framework is capable of finding it as long as it's available in the implicit scope where the `gen` method is called.



#### Working with options.



### Generating data

There are multiple methods available, allowing you to generate more than just the type:

- ```gen[T]```, used to generate a single instance of T.
- ```gen[X, Y]```, used to generate a tuple based on two samples.
- ```genOpt[T]```, convenience method that will give you back a ```Some[T](..)```.
- ```genList[T](limit)```, convenience method that will give you back a ```List[T]```. The numbers of items in the list is equal to the ```limit``` and has a default value of 5 if not specified.
- ```genMap[T]()```, convenience method that will give you back a ```Map[String, T]```.


There is also a default list of available generators for some default types, and to get to their value simply use the `value` method if the type is not a primitive. For things like ```EmailAddress```, the point of the extra class is obviously to distinguish the type during implicit resolution, but you don't need to use our abstraction at all, there will always be an easy way to get to the underlying generated primitives.

In the case of email addresses, you can use ```gen[EmailAddress].value```, which will correctly generate a valid ```EmailAddress``` but you can work directly with a ```String```.

- ```scala.Int```
- ```scala.Double```
- ```scala.Float```
- ```scala.Long```
- ```scala.String```
- ```scala.math.BigDecimal```
- ```scala.math.BigInt```
- ```java.util.Date```
- ```java.util.UUID```
- ```org.joda.time.DateTime```
- ```org.joda.time.LocalDate```
- ```com.outworkers.util.domain.Definitions.EmailAddress(value)```
- ```com.outworkers.util.domain.Definitions.FirstName(value)```
- ```com.outworkers.util.domain.Definitions.LastName(value)```
- ```com.outworkers.util.domain.Definitions.FullName(value)```
- ```com.outworkers.util.domain.Definitions.CountryCode(value)```
- ```com.outworkers.util.domain.Definitions.Country(value)```
- ```com.outworkers.util.domain.Definitions.City(value)```
- ```com.outworkers.util.domain.Definitions.ProgrammingLanguage(value)```
- ```com.outworkers.util.domain.Definitions.LoremIpsum(value)```


### util-parsers ###
### util-parsers
<a href="#table-of-contents">Back to top</a>

The parser module features an easy to use and integrate set of ScalaZ Applicative based parsers, with an ```Option``` based parser variant. It allows us to
Expand All @@ -298,7 +73,7 @@ a parser that parses an end result from an ```Option[String]``` and parserOpt va
T]```, which allows for Monadic composition, where you need to "short-circuit" evaluation and validation, instead of computing the full chain by chaining
applicatives.

### Option parsers ###
### Option parsers
<a href="#table-of-contents">Back to top</a>

The full list of optional parsers is:
Expand All @@ -323,6 +98,7 @@ An example of how to use ```Option``` parsers might be:
```scala

import com.outworkers.util.parsers._
import com.outworkers.util.samplers._

object Test {
def optionalParsing(email: String, age: String): Option[String] = {
Expand All @@ -336,7 +112,7 @@ object Test {
```


### Applicative parsers ###
### Applicative parsers
<a href="#table-of-contents">Back to top</a>

The full list of ScalaZ Validation based applicative parsers is:
Expand Down Expand Up @@ -383,8 +159,7 @@ object Test {
- Bartosz Jankiewicz @bjankie1


<a id="copyright">Copyright</a>
===============================
### Copyright
<a href="#table-of-contents">Back to top</a>

Copyright (c) 2014 - 2016 outworkers.

0 comments on commit ffd65d2

Please sign in to comment.