Unit testing

  • Not well defined, many different meanins of unit

  • it's a situational thing - the team decides what makes sense to be a unit for the purposes of their understanding of the system and its testing.

Speed

  • feature of unit testing

  • The faster the better

  • generally involves replacing slow collaborators with test doubles

  • test suites should run fast enough that you're not discouraged from running them frequently enough.

what

  • Probably best to be called Isolation Tests

    • opposite of integration tests

  • There are many definitions, mainly due to the ambiguous nature of the word unit

    • https://martinfowler.com/bliki/UnitTest.html

    • https://srikanth.sastry.name/defining-unit-tests-two-schools-of-thought/

    • Properties

      • https://www.youtube.com/playlist?list=PLlmVY7qtgT_lkbrk9iZNizp978mVzpBKl

      • Isolated — Unit tests are completely isolated from each other, creating their own test fixtures from scratch each time. (Note that I’m not saying these are the only useful tests, just that if tests aren’t isolated you’re going to have a hard time making the case that they are “unit tests”.)

      • Composable — Follows from isolation.

      • Deterministic — Should be. Code that uses a clock or random numbers should be passed those values by their unit tests.

      • Specific — If a unit test fails, the code of interest should be short.

      • Behavioral — If the behavior changes accidentally, a unit test should fail.

      • Structure-insensitive — This can be a challenge for unit tests. Too much mocking, especially strict mocking, is a structure sensitivity nightmare.

      • Fast — Yep.

      • Writable — Good interface design makes writing unit tests easier. Alternatively, difficult-to-write unit tests are the canary in the bad interface coal mine.

      • Readable — It can be challenge to write readable unit tests, because you are seeing so little context compared to the whole system.

      • Automated — Yep.

      • Predictive — Unit tests passing likely gives little confidence that the whole system is working. Unit tests failing should give great confidence that the whole system is not working.

      • Inspiring — A frequently-run unit test suite gives great confidence the programming is progressing. Sometimes I run my unit tests 2 or 3 times, just because it feels good (and they’re wicked fast).

  • there is the case of testing every behaviour a class does

    • but this can lead to highly coupled test code

  • APPROACH 1. Unit Testing Use Cases (Uncle Bob)

    • In Clean Architecture (Uncle Bob), the use cases are independent of any out-of-process dependencies, i.e., independent of the database, SMPT, messaging bug, and third-party systems.

    • We can thus unit-test the use cases by mocking out any out-of-process dependencies.

    • the Use Case Unit Tests indirectly cover the Domain (hence there are no Unit Tests targeting the Domain per see).

  • APPROACH 2. Integration Testing Use Cases & Unit Testing Domain (Khorikov)

  • In Unit Testing (Vladimir Khorikov), Khorikov distinguishes between managed out-of-process dependencies (e.g., database) versus unmanaged out-of-process dependencies (e.g., SMTP, message bus, third-party systems). Use Cases are implemented through Controllers (Application Service Layer). These controllers have a direct dependency on the database (e.g., reference the ORM), and interfaces are used only for unmanaged dependencies.

  • we apply a two-level approach for testing the Use Cases. The Use Cases are tested via Integration Tests (unmanaged dependencies are mocked out, but managed dependencies are not mocked out). The Domain is tested via Unit Tests.

  • Approach 1 vs Approach 2

    • Which is more optimal? Do we use a hybrid?

    • What about combinatorial explosion?

      • should this be tested at lower/specific level

I/O Free vs I/O Dependent

  • https://ted.dev/articles/2023/04/02/i-m-done-with-unit-and-integration-tests/

  • Instead of using the confusing names of unit and integration tests (where differences in meanings cause confusion)

  • Use I/O Free which defines sociable or class specific tests that dont talk to any I/O

    • Can include library we use

      • in java, the use of collections is still library but we dont mock it

      • So even individual class tests, still uses the library as they are not mocked (sociable tests)

    • No I/O is fast and predictable/deterministic

    • Improves speed of TDD, by gettign fast feedback

    • If the majority of these are used, faster builds

    • Any I/O is stubbed or use a fake in place of adapters (who talks to I/O)

      • or use Nullables

        • https://www.jamesshore.com/v2/projects/nullables/a-light-introduction-to-nullables

      • AVoid mocking, improve ease of refactoring and reduce integration issues with libraries

    • In hexagonal arch, can take advantage of the dependency inversion and isolate the usecases (core of app) from I/O and write tests at this layer and new everything up and fake all I/O integrations

      • any class that has complex rules that is called on by an usecase, can be tested in isolation

  • I/O dependent includes

    • database

    • files

    • Across network (queues/bus/http/rpc etc)

    • time/date

    • randomness

  • Need to have I/O Dependent tests

    • These are slower, hard to write, unpredictable, lots of setup

    • Use of test containers help, esp for DB/queues, but still slow

      • Cannot use for external serivce say API - but can use a service virtualisation (wiremock)

    • Needed to show things work, up to a limit

      • Can only do so much, as need to test in sandbox environment which actually talks to real db/services

    • This can be bottom of stack

      • where the integration points happen (talk to the db)

    • or top of the stack

      • bring the whole app and having these I/O services up

    • Can be separated from I/O free tests, and run after them during a build

Solitary vs sociable

  • A way of writign unit tests

solitary tests

  • writing unit tests at the class/function level

  • where all dependencies are replaced by test doubles

    • class under test is run in total isoalation

  • Mockists school

pros

  • Isolates issues:

    • Solitary unit testing can help to isolate issues to a specific unit or component, making it easier to identify and fix issues.

  • Simplifies testing:

    • Solitary unit testing can simplify the testing process, as each unit can be tested independently without requiring coordination with other units or components.

  • More granular:

    • Solitary unit testing allows for more granular testing, as each unit can be tested thoroughly in isolation.

Cons

  • May miss integration issues:

    • Solitary unit testing may not catch issues that only arise when units are combined in a real-world scenario.

  • Can be time-consuming:

    • Solitary unit testing can be time-consuming, as each unit must be tested separately, which can result in a large number of tests.

  • May not fully test system behavior:

    • Solitary unit testing may not fully test the behavior of the system as a whole, as it does not test the interaction between components.

###sociable test

  • where groups of interconnected units or components are tested together as a group, rather than testing each unit in isolation.

  • writing unit tests at the class/function level

  • where only IO dependencies, randomness, time are replaced by test doubles ie awkward collaborators and remove randomness

    • all other dependencies are instantiated

  • Classic school

pros

  • Tests the interaction between components:

    • Sociable unit testing can identify issues that may not be caught by testing individual units in isolation.

    • Testing the interaction between components can help uncover issues that may arise when different components are combined in a real-world scenario.

  • Can be more efficient:

    • In some cases, testing a group of interconnected components together can be more efficient than testing each component in isolation, as it can reduce the need for redundant or overlapping tests.

  • Better mimic real-world scenarios:

    • Sociable unit testing can better mimic real-world scenarios and can provide a more accurate representation of how the software will function in the real world.

Cons

  • Can be more complex:

    • Sociable unit testing can be more complex and difficult to implement than testing individual units in isolation.

    • It can be more difficult to isolate issues and to identify the root cause of issues that arise during testing.

  • Requires a more comprehensive understanding of the system:

    • Sociable unit testing requires a more comprehensive understanding of the system and its components, and can require more coordination between developers and testers.

  • May miss certain issues:

    • Sociable unit testing may miss certain issues that could be caught through testing individual units in isolation, such as issues that only arise when certain units are combined in a specific way.

Rules of unit testing

  • https://www.artima.com/weblogs/viewpost.jsp?thread=126923

  • A test is not a unit test if:

    • It talks to the database

    • It communicates across the network

    • It touches the file system

    • It can't run at the same time as any of your other unit tests

    • You have to do special things to your environment (such as editing config files) to run it.

  • https://martinfowler.com/bliki/UnitTest.html

Last updated

Was this helpful?