6 ms·
Using No Mocks to Improve Design (2012)
- andy_ppp 11y agoI've been thinking about this for a while; change code vs mocking. I think what I've been looking for is a justification for doing the first and I agree with the design argument here; it feels strange reaching into underlying code so supply plugable paths for not just tests but all code to be able to change the part that you were about to mock. It'll lead to a more flexible system overall and one that the tests are more explicit and use less magic.
- a-priori 11y agoI really don't understand why this post says that "mocks are a smell". For the last few years I've been mostly a Java developer and I've settled on a style of using a very mock-heavy testing style focusing on unit testing the interactions it has with other objects in the system. I use Mockito (http://mockito.org/ http://mockito.org/) to automatically generate mock objects from a class/interface. When you combine it with dependency injection you get a testing workflow where you can fully isolate the unit-under-test from the outside world and rigorously verify its interactions with its peers.
- mikekchar 11y agoI'll give my 2 cents (which may or may not be worth it ;-) ). Mocks are useful when you are doing TDD. At some point you have to build something which will need several collaborators, but the collaborators have not been built yet. You're not sure yet what final shape the design should take, so it's great to build an object, mocking the collaborators. You can then build the collaborators. This allows you to refactor your design easily and quickly when you are in this early stage. It's easy to change the interface of an object that hasn't been written yet ;-) Then when you implement the mocked object, it's easy to write because you just implement the bits that were mocked. At this point, I tend to replace my mocks with real objects. The reason for this is almost exactly the same reason for using mocks initially (ironically): mocked objects make your design hard to refactor. If I change the interface for an object, then anything that has mocked that interface needs to have its tests updated. This is not hard, but is crappy enough that it stops people from refactoring their interfaces. When you are using real objects all the way through, you often only need to change the production code and the unit tests for the object whose interface you changed. Real objects in tests are also really useful for documentation purposes. When you use real objects you have to show how to build the objects and how all the collaborators work. Some people tend to think that this is "integration testing" and should not be done in unit tests. I disagree fairly strongly with that idea, but I would be remiss if I didn't acknowledge that some of the anti-real-objects-in-unit-test people are pretty smart people ;-) Obviously real objects are safer to use as there are ways to confuse yourself with mocks so that you don't actually test anything meaningful. The main downsides with real objects is: sometimes you have to build the world to make them, and sometimes the real object has some external dependency which makes testing with them infeasible. The first issue is often a sign of poor design and should be rare -- but of course life happens. The second issue is usually not something you can control. In these circumstances, I will prefer to use mocks. In some circumstances you end up having to make mocks of mocks, or reaching down into structures you shouldn't know about. In those cases I prefer to use fakes. All of these techniques are valid and useful techniques. In any non-toy problem you will have to use all of them to have a good test system. The trick is knowing when you should be using each. That requires some experience.
- 1_player 11y agoOne thing I've learned from using immutable, functional languages (Elixir) is: "Don't tell your computer what to do, tell it how to transform data" While it may seem obvious, it's been a revelation for me and it has totally changed how I write code, and especially how I test it. In the example they read some command line arguments, read an input file, and process it. The natural instinct it to implement it exactly as it reads. You can't do that on a functional language, you need to reframe it into: 1) Transform command line arguments to some symbol representing the action to execute. 2) Transform a file name into a string representing the file contents. 3) Transform a string (file contents) into some data structure generated by processing it. For example, in an Elixir pseudocode: command = parse_command_line(argv) case command do {:process, file} -> contents = read_file(file) output = analyze(contents) _ -> print_usage() end Now each of these functions can be tested by itself and doesn't require mocking. They are just pure functions transforming input arguments into something else. But there's no error handling: Elixir would solve that very elegantly with both pipelines and pattern matching: parse_command_line(argv) |> read_file |> analyze |> output_result_or_error Each of these functions accepts and returns a tuple of {output, error}, and, if "error" is set, we just don't do anything but pass it down the pipeline. Basically the Either monad from Haskell. Errors-as-values are much better than exceptions for this exact use case. Exceptions are hard to test, to replicate, to inject, while in this example you can just test error handling by.. passing an error value to the function.
- kwhitefoot 11y agoAgreed, but I'd like to point out that you can write in functional style in pretty much any language. Most of my professional life involves VB.net which is mostly object oriented (for some value of object oriented) and procedural but I try to write as much of the code as possible as functions with no side effects. The biggest problem is convincing my colleagues to do the same.
- adrusi 11y agoThis article starts with the assumption that testable code is better code. Its primary point against mocks, that mocks let you write tests without changing your code, is dependant on that assumption. Without the assumption, there is no reason to believe that changing your code for the sake of tests is good or bad. I claim that that assumption is faulty. Code being easily testable indicates that it follows a certain set of good rules of thumb for writing good code. You're unlikely to find 500 line functions that encompass half of the program's logic. You're likely to find well delineated inputs and outputs of functions. The vast majority of the time, these are good things, and it's tempting to say that since writing testable code gave use these good thing, that only testable code is good. Or maybe you'd rather not leap that far — perhaps you'd rather only claim that as a rule, code should be written to be testable. It's not that it's an unreasonable rule. I'd be inclined to embrace it for novice programmers, since their judgement for what is and is not good code is lacking and they have to write tests regardless. Their code will probably come out better if they write for testing than if they try to use their best judgement. But once a programmer develops their judgement, they must use it. Programming is an art — a controversial statement, I know. It is not an art like music or painting or writing, it is more like architecture. Programming is about communicating simultaneously with humans and computers. Good code is that which achieves its technical requirements of reliability, performance, security, etc. in the way most accessible to a human. Good architecture is that which achieves its technical requirements of structural stability, cost-effective layout, energy efficiency, etc. in the way that best meets the needs of the people who will be seeing and using the space. There's nothing about testing in that definition. Optimizing code for tests is like optimizing a building for government inspectors. Yes, making all of the piping and wiring easily accessible via a hatch in the wall is a good thing because it will allow easier repairs and upgrades, but splitting the toilets into three different parts connected by rubber hose so that the inspectors can verify that the flush mechanism and the trap and what-have-you are all working properly independently would be inadvisable. I'm not going to attack the examples presented in the article because they are clearly a strawman. They serve to demonstrate the ideas the author sponsors rather than as a case study in how the author is right.
- mariodiana 11y agoWhen we use the term "art," we're adapting the Latin term "ars" to English. "Ars" also translates as "craft." Using "art" probably worked well 100 or more years ago when most educated people had taken Latin and, thus, had more ready access to such nuances, but today I think "craft" is the better word. "Programming is a craft" communicates the idea that its practice is not constrained to a set of hard and fast scientific principles; but, rather, that it's an endeavor that requires much experience to fit theory into practice, and that there are even certain aesthetic components to its practice. Programming is a craft in the same way that non-fiction writing -- or architecture, to use your example -- is a craft. We can of course say "art," but then we get distracted by the thought of men wearing hair buns and skinny jeans.