Hyperpragmatic pure FP testing with distage-testkit
1/31
Hyperpragmatic pure FP testing
with
distage-testkit
Functional Scala 2019
Septimal Mind Ltd
team@7mind.io
1 / 31
Hyperpragmatic pure FP testing with distage-testkit
Tests and Test Taxonomies 2/31
Not all the tests are equal
Tests are important. We know that.
Tests are the simplest way to define and verify important contracts.
And contracts are crucial for project maintainability and viability.
We write tests. A lot of.
But
. . .
some tests prove themselves useful and some do not.
2 / 31
Hyperpragmatic pure FP testing with distage-testkit
Tests and Test Taxonomies 3/31
Not all the tests are equal
Which tests are good and which are bad?
3 / 31
Hyperpragmatic pure FP testing with distage-testkit
Tests and Test Taxonomies 4/31
Bad test criterias
Bad tests are:
◮ Slow,
◮ Unstable: they fail randomly,
◮ Nonviable: they don’t survive refactorings,
◮ Demanding: they require complex preconditions to be met: external services up and
running, fixtures loaded, etc, etc,
◮ Incomprehensible: they signal about a problem but don’t help us to localize the
cause.
4 / 31
Hyperpragmatic pure FP testing with distage-testkit
Tests and Test Taxonomies 5/31
We spend more resources to write and
maintain bad tests
than the value they bring us.
5 / 31
Hyperpragmatic pure FP testing with distage-testkit
Tests and Test Taxonomies 6/31
How may we make our tests better?
6 / 31
Hyperpragmatic pure FP testing with distage-testkit
Tests and Test Taxonomies 7/31
Let’s ditch Unit/Functional/Integration trinity. . .
. . . and introduce some new terminology
7 / 31
Hyperpragmatic pure FP testing with distage-testkit
Tests and Test Taxonomies 8/31
Test Taxonomy: Encapsulation Axis
Let’s say that every test falls under one of the following categories:
1. Blackbox tests check just interfaces not knowing anything about implementations
behind them,
2. Whitebox tests may know about implementations and check them directly, sometimes
even breaking into some internal state to verify if it conforms to test expectations.
8 / 31
Hyperpragmatic pure FP testing with distage-testkit
Tests and Test Taxonomies 9/31
Test Taxonomy: Isolation Axis
Let’s say that every test falls under one of the following categories:
◮ Atomic tests check just one “unsplittable” software component,
◮ Group tests check multiple software components,
◮ Communication tests communicate with outer world (databases, API providers, etc,
etc).
9 / 31
Hyperpragmatic pure FP testing with distage-testkit
Tests and Test Taxonomies 10/31
You may extend and modify this Test Taxonomy as it would be convenient for you.
More about test taxonomies:
https://blog.7mind.io/constructive-test-taxonomy.html 10 / 31
Hyperpragmatic pure FP testing with distage-testkit
Tests and Test Taxonomies 11/31
Test Taxonomy: Why So?
According to our experience the best tests are
Blackbox tests with Atomic or Group Isolation Level.
This makes sense: such tests are fast and refactoring-resilient.
11 / 31
Hyperpragmatic pure FP testing with distage-testkit
Tests and Test Taxonomies 12/31
Communication tests
But in real projects most of the tests fall under Communication category (“Integration”
tests).
Why?
1. Engineers want to test The Whole Thing,
2. It’s hard to separate components,
3. etc, etc. . .
12 / 31
Hyperpragmatic pure FP testing with distage-testkit
Tests and Test Taxonomies 13/31
Communication tests can be more useful
We may replace integration components with Dummies1.
This way we may turn
Communication tests
into
Group or Atomic2
1
people also call them “Fakes”, “in-memory implementations” or “Mocks”
2
this statement is valid for Blackbox tests only
13 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 14/31
Dual Test Tactic
The same test scenario executed with both Production and Dummy implementations of
integration components is beneficial:
1. We can test business logic quickly, without any interference,
2. We still able to verify business logic behaviour with “real” integrations,
3. Dual Test Tactic enforces us to follow LSP and design better.
Refactorings are crucial for long-term health of the project.
Blackbox tests enable refactorings.
Dual Test Tactic drastically reduces the price of refactorings.
14 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 15/31
Dual Test Tactic: ideas
1. Ignore Communication tests in case their dependencies are unsatisfied (service
unavailable), don’t fail,
2. Avoid automatic “Mocks”, they prevent encapsulation,
3. Never run heavy dependencies in-process
◮ don’t bring whole Kafka or Cassandra into the classpath. PLEASE
15 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 16/31
Dual Test Tactic: The Main Issue
It’s easy to setup test dependencies while working with Dummies
But you have to do things differently for Production dependencies.
We have to take care of:
1. Resource acquisition and Cleanups,
2. Speed,
3. Memoization and resource reuse,
4. Interference,
5. . . .
16 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 17/31
Dual Test Tactic: dummy repositories testcase
make[UserRepo]
make[UserService]
make[AccountsRepo]
make[AccountsService]
run[MyTestCase] run[AnotherTestCase]
17 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 18/31
Dual Test Tactic: production repositories testcase
Setup
Execute
Cleanup
probe database port
make[DbDriver]
setup users table setup accounts table
make[UserRepo] make[AccountsRepo]
remove users table
close driver
remove accounts table
make[UserService] make[AccountsService]
run[MyTestCase]run[AnotherTestCase]
1. Order
2. How to avoid unneccessary job?
2.1 Memoize resources?..
2.2 In a singleton?..
2.3 When we close resources?..
2.4 On JVM shutdown?.. Oops, SBT. . .
2.5 Disambiguation (same class, different
parameters)?..
3. Resource deallocation
3.1 . . . even after a failure
3.2 Order!
4. Other integrations, e.g. run Dockers
4.1 . . . and await until they open ports
4.2 . . . and stop them after the tests
5. Configs 18 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 19/31
Dual Test Tactic: Real testcase steps
◮ Integration points stack together
◮ . . . and make the problem notably hard
◮ Manual wiring is hard to maintain
◮ . . . and suffers from combinatoric explosion of possible code paths
◮ Cake pattern doesn’t make much difference
◮ Conventional DI frameworks fail
It’s hard to setup variable contexts.
19 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 20/31
Dual Test Tactic
may be very pricy under usual circumstances.
20 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 21/31
Can we make it cheap?
21 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 22/31
Yes.
22 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 23/31
The Goal
We want to write code like this and never care about setting things up:
1 class JustATest[F[+_, +_]] {
2 "service" must {
3 "do something" in {
4 (users: UserService[F], accounts: AccountingService[F]) =>
5 for {
6 user <- users.create()
7 balance <- accounts.getBalance(user)
8 } yield {
9 assert(balance == 0)
10 }
11 }
12 }
13 }
14
15 object JustATestZioProd extends JustATest[zio.IO] with Prod
16 object JustATestZioDummy extends JustATest[zio.IO] with Dummy 23 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 24/31
dist✪ge
is a Dependency Injection mechanism for Scala
But it lacks negative traits of a typical DI thingy. . .
. . . and has many unique properties.
. . . and it’s blazing fast.
You may call it a module system for Scala. . .
. . . with automatic garbage-collecting solver
24 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 25/31
distage
1. Supports FP, wires Resources, Effects, ZIO Environments. . .
2. transparent and predictable
◮ first plan then do
◮ test the outcome without effect execution!
◮ compile-time checks and tests
3. no scala-reflect nor Java reflection1,
◮ ScalaJS support is coming
4. is non-invasive.
◮ You can add it to your project keeping business logic intact. . .
◮ . . . and remove it.
5. is not an ad-hoc thing, it has strong theory behind.
1
Since version 0.10.0, see https://blog.7mind.io/lightweight-reflection.html
25 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 26/31
Docker support in distage
While we develop and test we may need our integrations up and running. . .
. . . We have docker-compose for that. . .
But manual rituals are annoying and Compose is imperfect
(wait until service opens port? Hah)
Container orchestration is not easy.
But. . .
We made a distage extension allowing to get rid of Compose in most of the cases.
It can run test containers, reuse and shutdown them.
And it can await until the service is ready.
26 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 27/31
Let’s look at a real example. . .
Source code: https://github.com/7mind/distage-example
27 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 28/31
distage performs optimal ahead of time planning.
It doesn’t do any unnecessary job.
28 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 29/31
One More Thing: Roles
distage allow you to fuse microservices into “flexible monoliths”.
We may:
1. Develop services (Roles) separately, even in a multirepo,
2. Build a single Docker image with multiple Roles in it,
3. Run several Roles within one process
◮ We define roles we want to start as commandline parameters
4. ⇒ higher computation density, savings on infrastructure,
5. ⇒ substantial development simplification
◮ full environment can be started on a low-end machine
◮ with just one command
29 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 30/31
distage-framework is the most productive way to write
maintainable pure functional applications with ZIO.
(And any other monad)
distage-testkit is the best way to make your tests performant and reliable.
distage is adopted by several different companies and
tested by two years of production usage.
30 / 31
Hyperpragmatic pure FP testing with distage-testkit
Dual Test Tactic 31/31
Thank you for your attention
distage website: https://izumi.7mind.io
Septimal Mind is an Irish software consultancy and R&D Company
We’re looking for clients, contributors, adopters and colleagues ;)
Contact: team@7mind.io
Github: https://github.com/7mind
Slides: https://github.com/7mind/slides
Follow us on Twitter
https://twitter.com/shirshovp
https://twitter.com/kai_nyasha
31 / 31

Hyper-pragmatic Pure FP testing with distage-testkit

  • 1.
    Hyperpragmatic pure FPtesting with distage-testkit 1/31 Hyperpragmatic pure FP testing with distage-testkit Functional Scala 2019 Septimal Mind Ltd team@7mind.io 1 / 31
  • 2.
    Hyperpragmatic pure FPtesting with distage-testkit Tests and Test Taxonomies 2/31 Not all the tests are equal Tests are important. We know that. Tests are the simplest way to define and verify important contracts. And contracts are crucial for project maintainability and viability. We write tests. A lot of. But . . . some tests prove themselves useful and some do not. 2 / 31
  • 3.
    Hyperpragmatic pure FPtesting with distage-testkit Tests and Test Taxonomies 3/31 Not all the tests are equal Which tests are good and which are bad? 3 / 31
  • 4.
    Hyperpragmatic pure FPtesting with distage-testkit Tests and Test Taxonomies 4/31 Bad test criterias Bad tests are: ◮ Slow, ◮ Unstable: they fail randomly, ◮ Nonviable: they don’t survive refactorings, ◮ Demanding: they require complex preconditions to be met: external services up and running, fixtures loaded, etc, etc, ◮ Incomprehensible: they signal about a problem but don’t help us to localize the cause. 4 / 31
  • 5.
    Hyperpragmatic pure FPtesting with distage-testkit Tests and Test Taxonomies 5/31 We spend more resources to write and maintain bad tests than the value they bring us. 5 / 31
  • 6.
    Hyperpragmatic pure FPtesting with distage-testkit Tests and Test Taxonomies 6/31 How may we make our tests better? 6 / 31
  • 7.
    Hyperpragmatic pure FPtesting with distage-testkit Tests and Test Taxonomies 7/31 Let’s ditch Unit/Functional/Integration trinity. . . . . . and introduce some new terminology 7 / 31
  • 8.
    Hyperpragmatic pure FPtesting with distage-testkit Tests and Test Taxonomies 8/31 Test Taxonomy: Encapsulation Axis Let’s say that every test falls under one of the following categories: 1. Blackbox tests check just interfaces not knowing anything about implementations behind them, 2. Whitebox tests may know about implementations and check them directly, sometimes even breaking into some internal state to verify if it conforms to test expectations. 8 / 31
  • 9.
    Hyperpragmatic pure FPtesting with distage-testkit Tests and Test Taxonomies 9/31 Test Taxonomy: Isolation Axis Let’s say that every test falls under one of the following categories: ◮ Atomic tests check just one “unsplittable” software component, ◮ Group tests check multiple software components, ◮ Communication tests communicate with outer world (databases, API providers, etc, etc). 9 / 31
  • 10.
    Hyperpragmatic pure FPtesting with distage-testkit Tests and Test Taxonomies 10/31 You may extend and modify this Test Taxonomy as it would be convenient for you. More about test taxonomies: https://blog.7mind.io/constructive-test-taxonomy.html 10 / 31
  • 11.
    Hyperpragmatic pure FPtesting with distage-testkit Tests and Test Taxonomies 11/31 Test Taxonomy: Why So? According to our experience the best tests are Blackbox tests with Atomic or Group Isolation Level. This makes sense: such tests are fast and refactoring-resilient. 11 / 31
  • 12.
    Hyperpragmatic pure FPtesting with distage-testkit Tests and Test Taxonomies 12/31 Communication tests But in real projects most of the tests fall under Communication category (“Integration” tests). Why? 1. Engineers want to test The Whole Thing, 2. It’s hard to separate components, 3. etc, etc. . . 12 / 31
  • 13.
    Hyperpragmatic pure FPtesting with distage-testkit Tests and Test Taxonomies 13/31 Communication tests can be more useful We may replace integration components with Dummies1. This way we may turn Communication tests into Group or Atomic2 1 people also call them “Fakes”, “in-memory implementations” or “Mocks” 2 this statement is valid for Blackbox tests only 13 / 31
  • 14.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 14/31 Dual Test Tactic The same test scenario executed with both Production and Dummy implementations of integration components is beneficial: 1. We can test business logic quickly, without any interference, 2. We still able to verify business logic behaviour with “real” integrations, 3. Dual Test Tactic enforces us to follow LSP and design better. Refactorings are crucial for long-term health of the project. Blackbox tests enable refactorings. Dual Test Tactic drastically reduces the price of refactorings. 14 / 31
  • 15.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 15/31 Dual Test Tactic: ideas 1. Ignore Communication tests in case their dependencies are unsatisfied (service unavailable), don’t fail, 2. Avoid automatic “Mocks”, they prevent encapsulation, 3. Never run heavy dependencies in-process ◮ don’t bring whole Kafka or Cassandra into the classpath. PLEASE 15 / 31
  • 16.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 16/31 Dual Test Tactic: The Main Issue It’s easy to setup test dependencies while working with Dummies But you have to do things differently for Production dependencies. We have to take care of: 1. Resource acquisition and Cleanups, 2. Speed, 3. Memoization and resource reuse, 4. Interference, 5. . . . 16 / 31
  • 17.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 17/31 Dual Test Tactic: dummy repositories testcase make[UserRepo] make[UserService] make[AccountsRepo] make[AccountsService] run[MyTestCase] run[AnotherTestCase] 17 / 31
  • 18.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 18/31 Dual Test Tactic: production repositories testcase Setup Execute Cleanup probe database port make[DbDriver] setup users table setup accounts table make[UserRepo] make[AccountsRepo] remove users table close driver remove accounts table make[UserService] make[AccountsService] run[MyTestCase]run[AnotherTestCase] 1. Order 2. How to avoid unneccessary job? 2.1 Memoize resources?.. 2.2 In a singleton?.. 2.3 When we close resources?.. 2.4 On JVM shutdown?.. Oops, SBT. . . 2.5 Disambiguation (same class, different parameters)?.. 3. Resource deallocation 3.1 . . . even after a failure 3.2 Order! 4. Other integrations, e.g. run Dockers 4.1 . . . and await until they open ports 4.2 . . . and stop them after the tests 5. Configs 18 / 31
  • 19.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 19/31 Dual Test Tactic: Real testcase steps ◮ Integration points stack together ◮ . . . and make the problem notably hard ◮ Manual wiring is hard to maintain ◮ . . . and suffers from combinatoric explosion of possible code paths ◮ Cake pattern doesn’t make much difference ◮ Conventional DI frameworks fail It’s hard to setup variable contexts. 19 / 31
  • 20.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 20/31 Dual Test Tactic may be very pricy under usual circumstances. 20 / 31
  • 21.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 21/31 Can we make it cheap? 21 / 31
  • 22.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 22/31 Yes. 22 / 31
  • 23.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 23/31 The Goal We want to write code like this and never care about setting things up: 1 class JustATest[F[+_, +_]] { 2 "service" must { 3 "do something" in { 4 (users: UserService[F], accounts: AccountingService[F]) => 5 for { 6 user <- users.create() 7 balance <- accounts.getBalance(user) 8 } yield { 9 assert(balance == 0) 10 } 11 } 12 } 13 } 14 15 object JustATestZioProd extends JustATest[zio.IO] with Prod 16 object JustATestZioDummy extends JustATest[zio.IO] with Dummy 23 / 31
  • 24.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 24/31 dist✪ge is a Dependency Injection mechanism for Scala But it lacks negative traits of a typical DI thingy. . . . . . and has many unique properties. . . . and it’s blazing fast. You may call it a module system for Scala. . . . . . with automatic garbage-collecting solver 24 / 31
  • 25.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 25/31 distage 1. Supports FP, wires Resources, Effects, ZIO Environments. . . 2. transparent and predictable ◮ first plan then do ◮ test the outcome without effect execution! ◮ compile-time checks and tests 3. no scala-reflect nor Java reflection1, ◮ ScalaJS support is coming 4. is non-invasive. ◮ You can add it to your project keeping business logic intact. . . ◮ . . . and remove it. 5. is not an ad-hoc thing, it has strong theory behind. 1 Since version 0.10.0, see https://blog.7mind.io/lightweight-reflection.html 25 / 31
  • 26.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 26/31 Docker support in distage While we develop and test we may need our integrations up and running. . . . . . We have docker-compose for that. . . But manual rituals are annoying and Compose is imperfect (wait until service opens port? Hah) Container orchestration is not easy. But. . . We made a distage extension allowing to get rid of Compose in most of the cases. It can run test containers, reuse and shutdown them. And it can await until the service is ready. 26 / 31
  • 27.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 27/31 Let’s look at a real example. . . Source code: https://github.com/7mind/distage-example 27 / 31
  • 28.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 28/31 distage performs optimal ahead of time planning. It doesn’t do any unnecessary job. 28 / 31
  • 29.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 29/31 One More Thing: Roles distage allow you to fuse microservices into “flexible monoliths”. We may: 1. Develop services (Roles) separately, even in a multirepo, 2. Build a single Docker image with multiple Roles in it, 3. Run several Roles within one process ◮ We define roles we want to start as commandline parameters 4. ⇒ higher computation density, savings on infrastructure, 5. ⇒ substantial development simplification ◮ full environment can be started on a low-end machine ◮ with just one command 29 / 31
  • 30.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 30/31 distage-framework is the most productive way to write maintainable pure functional applications with ZIO. (And any other monad) distage-testkit is the best way to make your tests performant and reliable. distage is adopted by several different companies and tested by two years of production usage. 30 / 31
  • 31.
    Hyperpragmatic pure FPtesting with distage-testkit Dual Test Tactic 31/31 Thank you for your attention distage website: https://izumi.7mind.io Septimal Mind is an Irish software consultancy and R&D Company We’re looking for clients, contributors, adopters and colleagues ;) Contact: team@7mind.io Github: https://github.com/7mind Slides: https://github.com/7mind/slides Follow us on Twitter https://twitter.com/shirshovp https://twitter.com/kai_nyasha 31 / 31