Tuesday, 23 October 2018

Sandro Mancuso & Uncle Bob comparative design episode 6 notes

Notes from episode 1
Notes from episode 2
Notes from episode 3/4/5

The episode 6 was the first episode where Uncle Bob's approach is presented. This approach is much less familiar to me than Sandro's so I had a lot of observations about both uncle Bob's testing and his design approach. Not everything is clear to me but more, I hope, will unveil in the next episodes.
  1. As described in the episode summary, he starts with UML diagram to identify a domain model consisting of abstractions and relationship between them. Also, Bob identifies a set of use cases on a whiteboard first.
  2. Bob starts his first test with a test file for a use case (e.g. for "create user" use case, he creates a file called CreateUserTest) and creates a first test called "nothing".
  3. The "nothing" test quickly transforms into creation test (testing that a use case object can be created) 
  4. The second test is for the use case logic and is called "canCreateUser". The use case class is called CreateUser - a verb, not a noun, which I am fine with. I don't think all classes names should be nouns and I consider this one a good example.
  5. When writing this test, Uncle Bob introduces a DTO and calls it CreateUserRequest. Also, he puts all the data as public fields, to stress that this is a data structure and not a class with behaviors.
  6. As a refactoring step, Uncle Bob moves all but assertions to the setUp() method, creating essentially what is called "testcase class per fixture" test organization scheme.
  7. A surprising step - Uncle Bob does not inject repository to the use case. Rather, he makes a class called Context and places the repository in a mutable public static field of this class. In each test, he assigns his own in-memory repository to this field. Then the production class accesses this global variable to get the repository. This is, AFAIK, the Ambient Context pattern. The rationale for using it is that Uncle Bob doesn't like passing the reference to repository around. My comments on that choice:
    1. Personally, I don't think the reference would be "passed around" - had Uncle Bob used a factory for the use cases, he could inject the repository from composition root into the factory and from there into the use case and that's it. Not sure I would call it "passing around". 
    2. Another point would be that probably not every unit testing framework would let him get away with this. For example in xUnit.net, where tests are ran in parallel with each other, I assume he would see some nasty non-deterministic behavior.
    3. By creating the global mutable state, Uncle Bob creates a persistent fixture, which requires a manual teardown (either at the beginning or at the end of a test). I believe him he has enough discipline to keep it torn down at all times, but in team setup, I can see two programmers, each adding new tests and new fields to the Context, each anaware of what the other does and then they merge and it turns out that the changes made by programmer "A" do not include tearing down the things that programmer "B" added and vice versa. In other words, I believe it requires an immense amount of discipline and communication to keep this stuff clean at all times.
    4. This approach, as Sandro suggested, makes dependencies implicit. Uncle Bob argues that although it does make the dependency implicit, it does not hide it. Having looked at several definitions of what "implicit" means (I'm not a native speaker), from my perspective I would call it "hiding" anyway.
  8. When testing whether he can get the user that was put inside the repository by the use case, Uncle Bob makes a test fail by changing the in-memory repository to return a clone of the user instead of the original reference that was put inside the repository. Here I don't know what to think of it. This is because I am not sure how Uncle bob treats the InMemoryRepository class. If it's treated as a fake, a part of the test, then this is a legit TDD step, because by modifying the repository, Bob modifies the test fixture, i.e. he modifies the test. On the other hand, if the in-memory repository is a test fixture, then how did it end up in the org.openchat.repositories production package? This is the part that confuses me.
  9. An interesting part is that Bob tries to write the controller logic without a passing test and only goes to write one when he doesn't know what to write next in the controller logic. Bob doesn't want to mock the framework types (I wouldn't want to as well) and searches for another way, but this will be resolved in episode 7.

No comments: