$30 off During Our Annual Pro Sale. View Details »

Side-effects within the Business Logic core

Side-effects within the Business Logic core

Testing is hard. When it comes to separate concerns around I/O, we often end up writing lot of test code that needs to be continuously updated for a long time. At some point we all asked ourselves: “If my business logic code is correct, why I have to keep updating mocks and stubs so often? Am I focusing on adding business value or just gardening test code and mock/stub objects?”

This talk will explore and present few scenarios where it seems we stopped asking ourselves: “What is the problem we are really trying to solve here? Would everything get easier if we try to adjust the boundaries slightly?”

Filippo Vitale

September 20, 2019
Tweet

More Decks by Filippo Vitale

Other Decks in Programming

Transcript

  1. @filippovitale
    September 2019
    Testing-with-Mock:
    hold my beer...
    Side-effects within the
    Business Logic Core

    View Slide

  2. The opinions expressed in this presentation are
    those of the author. They do not purport to reflect the
    opinions or views of NETSCOUT or its employees.

    View Slide

  3. ➔ 165+ service providers (Global)
    ➔ 66% Fortune 1000 Enterprise

    View Slide

  4. ASERT – https://twitter.com/ASERTResearch

    View Slide

  5. View Slide

  6. Happy Land

    View Slide

  7. Happy Land

    View Slide

  8. A f B

    View Slide

  9. Testing
    - Example-based
    - Mutation
    - Property-based

    View Slide

  10. Testing
    - Example-based
    - Mutation
    - Property-based
    Here, we don’t need
    mocking objects

    View Slide

  11. Mocking Objects are widely used
    An Empirical Study on the Usage of Mocking Frameworks in Software Testing, Shaikh Mostafa, Xiaoyin Wang (2014)
    - 23% are using a mocking framework
    - Small portion of the dependencies

    View Slide

  12. What dependencies get mocked?
    To Mock or Not To Mock? An Empirical Study on Mocking Practices (2017) Davide Spadini, Maurício Aniche
    - Mix of OS and enterprise (big) projects
    - 2,000+ mocking objects usage analyzed
    - 100+ devs surveys and interviews

    View Slide

  13. What dependencies get mocked?
    70%
    36%
    6%

    View Slide

  14. What dependencies get mocked?
    70%
    36%
    6%
    “Classes related to external resources
    are often mocked due to their
    complex setup and slowness”
    External Dependencies, DBs, Web Services

    View Slide

  15. What dependencies get mocked?
    70%
    36%
    6%
    External Dependencies, DBs, Web Services

    View Slide

  16. What dependencies get mocked?
    70%
    36%
    6%
    Business Logic (Domain)
    External Dependencies, DBs, Web Services
    “Classes related to Business logic
    are mocked only when they are
    too complex”

    View Slide

  17. What dependencies get mocked?
    70%
    36%
    6%
    Business Logic (Domain)
    Language Libs
    External Dependencies, DBs, Web Services
    To Mock or Not To Mock? An Empirical Study on Mocking Practices (2017) Davide Spadini, Maurício Aniche

    View Slide

  18. Why sometimes we feel
    mocking objects seems the only
    way to test the Business Logic?

    View Slide

  19. Test Double
    https://martinfowler.com/bliki/TestDouble.html – http://xunitpatterns.com/Test%20Double.html
    Martin Fowler

    View Slide

  20. Test Double
    Mock Object
    https://en.wikipedia.org/wiki/Mock_object

    View Slide

  21. Test Double
    Mock Object
    Stubs
    Mocks
    Fakes

    View Slide

  22. Test Double
    Mock Object
    Stubs
    Mocks
    Fakes

    View Slide

  23. Test Double
    Mock Object
    Stubs
    Mocks
    Fakes
    https://martinfowler.com/bliki/TestDouble.html

    View Slide

  24. Common Scenario:
    “Huge dependencies that
    are too hard to create”

    View Slide

  25. http://xunitpatterns.com/Test%20Stub.html
    “We can use a Test Stub [...] to
    control the behavior of the SUT
    with various indirect inputs”

    View Slide

  26. class PotatoService(config: Config) {
    val potatoVariety: String = config.getPotatoVariety
    val maxPotatoes: Int = config.getMaxPotatoes
    def makePotatoSalad(): Salad = ???
    }
    Imagine a world without mocks, Ken Scambler

    View Slide

  27. "PotatoService" should "make an expectedSalad" in {
    val configStub = mock[Config]
    (configStub.getPotatoVariety _).expects().returning("pontiac")
    (configStub.getMaxPotatoes _).expects().returning(33)
    val service = new PotatoService(configStub)
    service.makePotatoSalad() should equal (expectedSalad)
    }

    View Slide

  28. https://www.amazon.com/Wenger-16999-Swiss-Knife-Giant/dp/B001DZTJRQ

    View Slide

  29. “Know as little as you need”
    (to produce as little as you can)

    View Slide

  30. class PotatoService(variety: String, potatoes: Int) {
    def makePotatoSalad(): Salad = ???
    }

    View Slide

  31. class PotatoService(variety: String, potatoes: Int) {
    def makePotatoSalad(): Salad = ???
    }
    val appConfig: Config = readFromFile(appConfigFilename)
    val service = new PotatoService(appConfig.getPotatoVariety,
    appConfig.getMaxPotatoes)

    View Slide

  32. "PotatoService" should "make an expectedSalad" in {
    val service = new PotatoService("pontiac", 33)
    service.makePotatoSalad() should equal (expectedSalad)
    }

    View Slide

  33. Common Scenario:
    “Fragile mutable
    domain modelling”

    View Slide

  34. http://xunitpatterns.com/Mock%20Object.html
    “We can use a Mock as an
    observation point [...] caused by an
    inability to observe side-effects of
    invoking methods on the SUT”

    View Slide

  35. “Tell, don’t Ask”
    https://martinfowler.com/bliki/TellDontAsk.html

    View Slide

  36. trait Customer {
    def buyDrink(): Unit
    }
    trait Wallet {
    def removeCoins(amount: Int): Int
    def getAmount: Int
    }
    trait VendingMachine {
    def insertCoins(amount: Int): Unit
    def collectCan(): Can
    def getStoredCash: Int
    }
    Imagine a world without mocks, Ken Scambler

    View Slide

  37. "A Customer" should "collect aCan inserting 3 coins" in {
    val walletStub = mock[Wallet]
    (walletStub.removeCoins _).expects(3).returning(3) .once()
    val machineStub = mock[VendingMachine]
    (machineStub.insertCoins _).expects(3).returning() .once()
    (machineStub.collectCan _).expects().returning(aCan) .once()
    val customer = new RealCustomer(...)
    customer.buyDrink()
    }

    View Slide

  38. trait Customer {
    def buyDrink(vm: VendingMachine): (Customer, VendingMachine)
    }
    trait Wallet {
    def removeCoins(amount: Int): Wallet
    def getAmount: Int
    }
    trait VendingMachine {
    def insertCoins(amount: Int): VendingMachine
    def collectCan(): (Option[Can], VendingMachine)
    def getStoredCash: Int
    }
    Imagine a world without mocks, Ken Scambler

    View Slide

  39. "A Customer" should "collect a can inserting 3 coins" in {
    val wallet = new Wallet(10)
    val customer = new Customer(wallet, 0) // can held
    val machine = new VendingMachine(3, false) // price, paid
    val (poorCustomer, richMachine) = c.buyDrink(m)
    poorCustomer.getWallet.getAmount should equal (10 - 3)
    poorCustomer.hasCan should be (true)
    }

    View Slide

  40. View Slide

  41. "A Customer" should "collect a can inserting 3 coins" in {
    val wallet = new Wallet(10)
    val customer = new Customer(wallet, 0) // can held
    val machine = new VendingMachine(3, false) // price, paid
    val (poorCustomer, richMachine) = c.buyDrink(m)
    poorCustomer.getWallet.getAmount should equal (10 - 3)
    poorCustomer.hasCan should be (true)
    }
    But then it’s an
    Integration Test!
    val ab: A => B = ...
    val bc: B => C = ...
    val cd: C => D = ...
    val de: D => E = ...
    val ae: A => E = ab andThen bc andThen cd andThen de

    View Slide

  42. @macu_ic – https://unsplash.com/photos/fQWKGEllX2c
    FP is a tall tree

    View Slide

  43. Common Scenario:
    “Essential Effects or I/O
    coupled with pure Logic”

    View Slide

  44. case class Malware(name: String, hasSample: Boolean)
    case class Indicator(malware: Malware, hashes: List[SHA256Hash])
    val hakai = Indicator(Malware("Hakai DDoS botnet based off the
    Gafgyt family of IoT malware",
    hasSample = true),
    List("d96...88f", "2d7...c98"))
    ASERT Team, https://www.netscout.com/blog/asert/realtek-sdk-exploits-rise-egypt

    View Slide

  45. def fetchMalware(id: MalwareID): Malware = { }
    def fetchSampleHashes(id: MalwareID): List[String] = { }
    def getMalwareIndicator(malwareId: MalwareID): Indicator = {
    val malware = fetchMalware(malwareId)
    if (malware.hasSample) {
    Indicator(malware, fetchSampleHashes(malwareId))
    } else {
    Indicator(malware, List.empty)
    }
    }

    View Slide

  46. View Slide

  47. val m = mock[MalwareService]
    val s = mock[SampleService]
    (m.fetchMalware _).expects(123).returning(expectedMalware) .once
    (s.fetchSampleHash _).expects(123).returning(expectedHashes).once
    val i = new MalwareIndicator(m, s)
    i.getMalwareIndicator(123) should be(expectedIndicator)
    70%
    External Dependencies, DBs, Web Services

    View Slide

  48. val m = mock[MalwareService]
    val s = mock[SampleService]
    (m.fetchMalware _).expects(123).returning(expectedMalware) .once
    (s.fetchSampleHash _).expects(123).returning(expectedHashes).once
    val i = new MalwareIndicator(m, s)
    i.getMalwareIndicator(123) should be(expectedIndicator)

    View Slide

  49. def fetchMalware(id: MalwareID): Malware = { }
    def fetchSampleHashes(id: MalwareID): List[String] = { }
    def getMalwareIndicator(malwareId: MalwareID): Indicator = {
    val malware = fetchMalware(malwareId)
    if (malware.hasSample) {
    Indicator(malware, fetchSampleHashes(malwareId))
    } else {
    Indicator(malware, List.empty)
    }
    }

    View Slide

  50. Logic and Effects
    are entangled

    View Slide

  51. @yungnoma , https://unsplash.com/photos/qAShc5SV83M
    Programs combine Logic & Effects

    View Slide

  52. Tests for:
    Logic
    Effects
    Hard
    Easy

    View Slide

  53. Hard
    Easy
    Few
    Tests
    Many
    Tests
    Tests for:
    Logic
    Effects

    View Slide

  54. Hard
    Easy
    Few
    Tests
    Many
    Tests

    View Slide

  55. Hard
    Easy
    Few
    Tests
    Many
    Tests

    View Slide

  56. Hard
    Easy
    Few
    Tests
    Many
    Tests
    Test Doubles Are A Scam, Matt Diephouse

    View Slide

  57. - Testing Logic+Effects ⇒ possible
    - Legitimize the Design
    - No motivation on “pushing effects out”
    - Integration Test ⇒ Unit Test
    Mocking Objects give you

    View Slide

  58. Fog of War
    @we_the_royal https://unsplash.com/photos/Cju-BkSkM1k

    View Slide

  59. Mock objects for testing java systems, Why and how developers use them, and how they evolve (2018) Davide Spadini
    83%
    Mocks introduced from the beginning Later
    “Mocks are added at test creation and
    tend to stay in the test for its lifetime”
    87%
    Mocks never removed from the tests

    View Slide

  60. Mocking Objects are hard to maintain
    Mock objects for testing java systems, Why and how developers use them, and how they evolve (2018) Davide Spadini
    “Lines involving mocks change often”
    - Code changes impact the mocks
    - Mocks increase the coupling test-code
    - Maintaining the mocks is hard

    View Slide

  61. Separation of
    Logic and Effects

    View Slide

  62. Don’t commit too early
    to a specific effect monad!

    View Slide

  63. https://github.com/cutculus/not-a-blog/blob/master/opinionated-haskell-guide-2019.md

    View Slide

  64. The Cost of Abstraction

    View Slide

  65. The Cost of Abstraction
    “Given a choice of solutions,
    pick the least powerful solution
    capable of solving your problem”
    The Rule of Least Power, https://www.w3.org/2001/tag/doc/leastPower.html

    View Slide

  66. “Functional Core,
    Imperative Shell”

    View Slide

  67. Gary Bernhardt (2012) – https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell

    View Slide

  68. Ports and Adapters (2008), Alistair Cockburn – diagram by Nat Pryce
    Clean
    Architecture Hexagonal
    Architecture
    Onion
    Architecture
    Boundaries!

    View Slide

  69. “Branching” minimized
    in the shell (less “ifs”)

    View Slide

  70. Imperative Shell
    ⇒ few Integration tests
    Functional Core
    ⇒ many unit tests

    View Slide

  71. // Functional Core
    def sampleAvailable(malware: Malware): Either[String, Unit] =
    if (malware.hasSample) Right(())
    else Left("No Samples available")

    View Slide

  72. // Functional Core
    def sampleAvailable(malware: Malware): Either[String, Unit] =
    if (malware.hasSample) Right(())
    else Left("No Samples available")
    // Imperative Shell
    def getMalwareIndicator(id: MalwareID): Task[Indicator] = for {
    malware <- fetchMalware(id)
    hashes <- sampleAvailable(malware)
    .fold(_ => Task.now(List.empty[SHA256Hash]),
    _ => fetchSampleHashes(id))
    } yield Indicator(malware, hashes)

    View Slide

  73. The Complexity Trap, Daniel Westheide – https://danielwestheide.com/blog/2018/12/07/the-complexity-trap.html
    // Functional Core
    def sampleAvailable(malware: Malware): Either[String, Unit] =
    if (malware.hasSample) Right(())
    else Left("No Samples available")
    // Imperative Shell
    def get...[F[_]: Monad](id: MalwareID): F[Indicator] = for {
    malware <- fetchMalwareF(id)
    hashes <- sampleAvailable(malware)
    .fold(_ => Monad[F].pure(List.empty[SHA256Hash]),
    _ => fetchSampleHashesF(id))
    } yield Indicator(malware, hashes)

    View Slide

  74. “We can combine
    effectful computation
    and effect-free ones
    without making them
    pollute each other, the
    type system keeps
    them apart”
    Nirvana, Simon Peyton Jones – https://youtu.be/iSmkqocn0oQ
    Simon Peyton Jones

    View Slide

  75. https://github.com/lampepfl/dotty/issues/4712
    @pure / @impure

    View Slide

  76. https://www.reddit.com/r/scala/comments/crsuvp/scala_pure_annotation/
    @pure / @impure

    View Slide

  77. https://github.com/lampepfl/dotty/issues/4712#issuecomment-403218174
    @pure / @impure

    View Slide

  78. Integration tests

    View Slide

  79. Test
    - TestContainer-Scala
    - ...
    Integration Tests
    CI
    - Docker Compose
    - ...

    View Slide

  80. - Find discrepancies between current
    implementation and model
    - Effects vs. Side-Effects
    State-based property testing

    View Slide

  81. https://en.wikipedia.org/wiki/Don%27t_throw_the_baby_out_with_the_bathwater

    View Slide

  82. Stubs, Mocks and Fakes:
    The Good Parts™

    View Slide

  83. https://www.flickr.com/photos/nathansmith/4704268314

    View Slide

  84. The Good Parts™
    - Fakes for performance tests
    - Fake expensive RealWorld™ things
    - Concurrency bugs hard to replicate
    - Codebase with no tests / hard to test
    - Disposable tests (e.g. when TDD)

    View Slide

  85. Stop Mocking, Start Testing – https://www.youtube.com/watch?v=Xu5EhKVZdV8
    “When choosing between
    Zero and One Mock,
    Try Zero”

    View Slide

  86. Atomic Habits – https://jamesclear.com/common-mental-errors

    View Slide

  87. @filippovitale
    September 2019
    https://commons.wikimedia.org/wiki/File:Tortellini_Bolognesi.jpg

    View Slide