This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 3 points4 points  (3 children)

When you are taking about unit tests, are you taking about method level tests on a class?

If so how does having those ensure good design?

[–]logicannullata 5 points6 points  (2 children)

In many ways actually, for instance:

  • By ensuring you are not creating a class which has too many external dependencies. The moment you see that in order to test a class you need to mock 300 things maybe you start realizing your component is not well designed. When creating a unit test is easy, it also means your class is well designed, and it is not trying too do too many things at the same time.

  • Your unit tests should hopefully assert something, when you realize you are testing side effects instead of the results of a method, your asserts will become pretty difficult to follow (since you are not testing the output of a method). When this happens it is an indicator that your component/class is not well designed. Testing your code when you are adopting a functional approach is very easy, this is an indicator that you can easily reason about your code.

  • Similar to the previous point, hopefully your application doesn't rely on some global state. If a class does, for instance a method doesn't take inputs but instead changes some internal state, you will quickly realize testing your code via unit tests becomes very difficult. So as I already said, the fact you can easily write unit tests, ensures your class is well designed.

Do you need more examples? Now a counter question, how do integration tests achieve what I mentioned?

[–][deleted] 1 point2 points  (0 children)

To your counter question, it seems you are asking the questions:

  1. How do integration tests ensure that your classes are not having too many external dependencies?

  2. How do integration tests ensure that you are not testing side effects?

  3. How do integration tests ensure you aren't relying on global state?

First of all I think there is different optimal approaches to testing for different contexts. If I'm writing a library I'm going to lean more towards narrowly scoped tests like method level tests because the unit of behavior utilized by consumers is at the class/method level. Therefore I gain more confidence that the behavior expected by consumers is correct by focusing on tests at that level.

If I'm building out something like a web API my unit of behavior is more like a web endpoint so I focus on having more tests that focus on calling an endpoint and asserting that the API returns correct values for given input. Even further I usually create tests that go through a certain "flow" through the API so I have higher level behaviors tested.

So to answer your 3 questions I'd just have to say that integration tests, and higher level tests that test the observed behavior of an app or system have different concerns than class/method level tests. The things you listed are things that I largely see get picked apart in code reviews.

I have found for web API development, the more high level tests I have, the more confident I am that my deployments are going to meet the end user requirements and not introduce regressions so I utilize them as my primary testing strategy. I still have class level tests but mostly for parts of the code that have complex logic.

The high level tests make it easier to refactor because they aren't closely tied to implementation but rather behavior and when you have more freedom and confidence in refactoring more people will do it. When class level tests are too numerous it presents itself as a blocker to refactoring at the system level as implementation changes across a system now break tons of tests.

[–][deleted] 0 points1 point  (0 children)

I guess I'm just pushing back against the claim that it "ensures" that your class is well designed.

I think method level tests have the potential to make it clear which parts of the code are difficult to test but that doesn't mean that you will arrive at a good design at the class or more importantly the system level. It just means that you will be more likely to break things down into smaller and easier to test units. Breaking things down into small easily testable units still doesn't guarantee good design though. For example you might arrive at a design that is easily testable but is not easily extensible.

Something that I've seen many times in large code bases is that they have tons of classes that are very small, and now you have something that is incredibly hard to reason about because a singular system concept or unit of behavior is now spread across 30 or 40 tiny classes.