Domain and Test Driven Design - raisercostin/software-wiki GitHub Wiki

Have a bug that fails.

Books

- TDD by kent beck -> test infected
- ddd by adams
- nature of software dev - focus on what is needed
  - for MVP even for moderate complex problems accumulated debugging time could be greated than testing time 

Tests taxonomies

There are tests

  • business tests - interested in values/outputs
  • technological tests - systematically, independent of the bugs in business tests
    • they don't need to be executed against a code coverage
  • bullshit tests

Assumptions

  • any unforseen development situation should trigger an exception at least in log. And this is a bug.
  • Not only TDD - but eXtreme Programming - a set of interrelated practices
  • developer friendly design & error handling
    • static factories for common creation patterns instead of new ...
  • the goal is not coverage but design - clarifies the problem(minimal inputs/outputs) not how and mechanisms/libraries
    • with coverage - people can always circumvent

E2E business tests using TestDD/BusinessDD/DomainDD DDD&DIP -

  • integration - external systems
  • mocked systems/stub but preffer test implemention
  • unit test using simple local/non external systems
    • this will act also as a regression test
      • in what context it worked (is not always saying why it worked)
        • could be generated automatically with production data
        • they are additive (while debugging is not additive)
    • regardless of my future choosing of specific technology/library how do I want to interract with the system? what is the interface
    • you don't necessary need to have assertions
      • being an e2e then clearly you have some accumulated lateral effects/state
      • when having pure functions you must alwasy have asserts
      • for inpure functions / you have accumulated state depending on the order of the calls and you can test that without assertions
        • in the end you need to observe something from the system.
        • a lot of times you need to observe that it doesn't crash/exceptions

E2E technology systematic tests - where the purpose is not to check the business values but the surrounding technology

  • the best approach with these is to choose one or several business methods and not all

Don't want to create

  • stupid, no value unit tests, we don't test getters
  • whitebox testing - tests for internal stuff
    • are brittle
    • temporary tests - this changes too often
      • because of the "redesigns"/"refactoring" that change more often than business e2e tests
      • and then you have a battery of tests that needs to be discarded
    • tests that need high maintance
  • spring problem - annotations/metadata -> engine is obscure and hard to control/extend
    • at least use only constructor injection (no setter/field injection)
      • you could wire the objectgs by yourself
    • global state in ThreadLocal

Questions

  • should we create a test for controllers?
    • no. hard to execute. low value since spring (that is thorrowfully tested) will map the parameters and pass in controller. anyway the controller responsibility should just adapt the received http parameters to a service and the response back.
    • testing mapping http - controller - low value testing
    • testing mapping params in controller - low value
    • testing code in service - huge value -> best ROI
package namek.auth;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

class UserServiceTest {
  @Test
  void test() {
    UserService us = new UserService(null, null);
    //HttpAuthService auth = HttpAuthService.createForTests();
    LocalSessionContext lsc = new LocalSessionContext();
    assertThat(us.login(lsc.createSession(), "admin", "adminpass2").get().toString()).isEqualTo("");
  }

  /***
   * Don't have persistance, serialization.
   * Technology adaptor.
   */
  static class LocalInMemoryDatabaseForTests implements BettingServicePlatformHelperOrTechnologyAdaptor {
    @Override
    public void addBet(String user, int amount) {
      throw new RuntimeException("Not implemented yet!!!");
    }

    @Override
    public void shutdownNow() {
      throw new RuntimeException("Not implemented yet!!!");
    }

    @Override
    public void acceptBet() {
      throw new RuntimeException("Not implemented yet!!!");
    }

    @Override
    public void startNow() {
      throw new RuntimeException("Not implemented yet!!!");
    }
  }

  /**
   * Technology adaptor.
   */
  static class PostgresDatabase implements BettingServicePlatformHelperOrTechnologyAdaptor {
    //postgress + jdbc + jpa/...
    // - serialization
    // - joins in database
    // - caching by jpa
    @Override
    public void addBet(String user, int amount) {
      throw new RuntimeException("Not implemented yet!!!");
    }

    @Override
    public void shutdownNow() {
      throw new RuntimeException("Not implemented yet!!!");
    }

    @Override
    public void acceptBet() {
      throw new RuntimeException("Not implemented yet!!!");
    }

    @Override
    public void startNow() {
      throw new RuntimeException("Not implemented yet!!!");
    }
  }

  static class BettingService {
    BettingServicePlatformHelperOrTechnologyAdaptor database;

    public BettingService(BettingServicePlatformHelperOrTechnologyAdaptor database) {
      this.database = database;
    }

    public void betStart(String user, int amount) {
      database.addBet(user, amount);
    }

    public void managerAcceptsBet() {
      database.acceptBet();
    }

    public void betStartCommit() {
      throw new RuntimeException("Not implemented yet!!!");
    }
  }

  /**Inverted interface. Needs from an underlying technological system.*/
  interface BettingServicePlatformHelperOrTechnologyAdaptor {
    void addBet(String user, int amount);

    void shutdownNow();

    void acceptBet();

    void startNow();
  }

  @Test
  void test2() {
    //Responsibility to store in a specific order all betting operations
    //The LocalInMemoryDatabaseForTests can strategically choose what constraints keeps from a real implementation, will not have joins
    BettingServicePlatformHelperOrTechnologyAdaptor database = new LocalInMemoryDatabaseForTests();
    BettingService bs1 = new BettingService(database);
    BettingService bs2 = new BettingService(database);
    BettingService bs3 = new BettingService(database);
    bs1.betStart("costin", 100);
    bs2.betStart("costin", 100);
    database.shutdownNow();
    bs3.managerAcceptsBet();
    bs2.betStartCommit();
    database.startNow();
    bs1.betStartCommit();
  }
}