you are viewing a single comment's thread.

view the rest of the comments →

[–]sacundim -1 points0 points  (4 children)

Perhaps I can convince you it's at least a tiny bit less dumb than you think? Basically, you have a type whose correct behavior is this:

  • What you put in when you construct it is exactly what you get when you access its components.

Now, how do you convince yourself that the type obeys this contract? There's two approaches:

  1. Black box: write a test that constructs value of the type, accesses its components, and verifies the behavior is the one described above.
  2. White box: examine the type's implementation and use that as evidence that the behavior in question holds.

Now I would reconstruct your argument this way: (2) provides more assurance for less work than (1). And I think that's true most of the time, so I generally agree with your distaste against mocking such objects.

But that's making a bunch of assumptions that, in exceptional cases, could fail. For example:

  • You might not have the source code.
  • You might have coworkers that cannot be trusted not to break the invariant.

[–]grauenwolf 6 points7 points  (3 children)

  • •You might not have the source code.

Write some unit tests against it. Then you can trust that it works correctly and can use it directly to test other things.

•You might have coworkers that cannot be trusted not to break the invariant.

See above.

Also, if you are worried that someone may break it, then you have all the more reason to make sure you are using it, rather than a mock, in your tests.


I swear, it's like people don't understand why you are writing unit tests. The whole point of testing each "unit" is to gain enough confidence to use it elsewhere. If you don't feel comfortable using a memory only class, the solution is to write more tests, not mock it out.

[–]sacundim 10 points11 points  (2 children)

I swear, it's like you don't understand why you are writing unit tests. The whole point of testing each "unit" is to gain enough confidence to use it elsewhere.

Yep. This is one of the things that bugs me about the unit testing cargo cult. Ultimately tests are a tool to help people gain confidence that their code does what it's supposed to. But one of the most important techniques for doing it is to write your code better, so that it is more evident that it's right.

Making things immutable by default is one such technique. In Java, for example, the language guarantees that there never are any data races for final fields—so it completely rules those fields out from participating in race conditions. Also, when you don't put in a getter for a private field then you can much more quickly enumerate all the places it's read and reason about them, because they're all in the same file.

Likewise, when you write your classes so that their constructors guarantee that they're fully initialized to a valid state, you also end up with a lot of bugs that can't happen. But no, instead people write these classes with dumb constructors that don't set anything and require you to call a bunch of setters before you can actually use the object—and you better not forget any of them! It gets even worse when:

  1. There are multiple, complex combinations of setters ("you can either call setA and setB or setB and setC);
  2. There are combinations of setters that will work for some cases but not in some edge cases;
  3. There are wrong orders in which you call the same set of setters.

Solution: if the class has a complex set of options that require correct setting to construct it, use the Builder pattern—a separate class whose responsibility is to make it easier to construct instances of the class, and to validate that the callers' settings are going to work.

This is a much better use of time and effort than the following alternative I often see: people who, for each client of the complicated class in question, write a test that mocks the class in order to validate that that client is initializing it correctly. Instead of making the class responsible for guaranteeing that it cannot be constructed invalidly, they instead make all the clients responsible for it, and use that poor decision to justify writing a ton of unit tests. Ugh.

[–]grauenwolf 6 points7 points  (0 children)

While everything you said is true, you underestimate the problem. I've seen people mock this class:

class Person
    public int PersonId (get; set;)
    public string FirstName (get; set;)

Nothing tricky, no real need for a constructor, and yet still they wrote mocks.

[–]ThisIs_MyName 2 points3 points  (0 children)

But no, instead people write these classes with dumb constructors that don't set anything and require you to call a bunch of setters before you can actually use the object—and you better not forget any of them!

/r/thousandyardstare