7 ms·
> Unit tests for existing projects often fail - Unit tests for existing projects take several minutes to run - Our current build process cannot be automated. S
by LandR 8y ago
> Unit tests for existing projects often fail - Unit tests for existing projects take several minutes to run - Our current build process cannot be automated.
Same here. Except we have no almost no unit tests because
1) Most of the devs don't know how to write unit tests
2) Most of the devs don't know hot to write code that is testable, even if they could write the actual unit tests.
About a year or so ago we had a project that got a few unit tests added to it, within days they were broken nad failing. The dev just added a [TestCategory("Blah")] and excluded them from being run...
- AllegedAlec 8y ago> Except we have no almost no unit tests because > 1) Most of the devs don't know how to write unit tests > 2) Most of the devs don't know hot to write code that is testable, even if they could write the actual unit tests. Sounds really familiar... I'd like to add the following one: > 3) Cannot make the distinction between code that needs unit testsand code that does not need unit tests
- fatoons21 8y agoI'm new to development, would you recommend any resource out there that covers points 1) 2) and 3)? Thanks
- AllegedAlec 8y agoTo be fair: much of it it practice and prior knowledge. I personally found the book Pragmatic Unit Testing helpful, as well as the Clean Code chapter on unit tests. Others may be more able to find you more readily available sources.
- TeMPOraL 8y agoI'm old to development, and would like to ask for the same thing. But something beyond "Pragmatic Unit Testing". You see, I've read that. I've read one other book on unit tests too, and been on 2-day long training in TDD, and spent many hours trying to write unit tests, and yet the skill still eludes me. It's like I have a blind spot there, because I can't for the best of me figure out how to test most of the code I write. In the projects I'm working on, I find roughly 10% of the code to be unit-testable even in principle. That's the core logic, the tricky parts - like the clever pathfinding algorithm I wrote for routing arrows in diagrams, or the clever code that diffs configurations to output an executable changeset (add this, delete that, move this there...). This I usually write functional-style (regardless of language), I expect specific inputs and outputs, so I can test such code effectively. Beyond that, I can also test some trivial utilities (usually also written in functional style). But the remaining 80-90% of any program I work on turns out to be a combination of: - code already tested by someone else - external dependencies - code bureaucracy, which forms vast majority of the program - that is, including/injecting/managing dependencies, moving data around, jumping through and around abstraction layer; this I believe is untestable in principle, unless I'm willing to test code structure itself (technically doable for my Lisp projects...) - the user interface, the other big part, which is also hilariously untestable, and rarely worth testing automatically, as any regression there will be immediately noticed and reported by real people I'm having trouble even imagining how to unit-test the three things above, and it's not something covered in unit testing books, tutorials or courses I've seen - they all focus on the basics, like assertions and red-green-refactor, which are the dumb part of writing test. I'm looking for something for the difficult part - how to test the three categories of code I mentioned above.
- vishalh 8y ago> - code already tested by someone else - external dependencies You test for whether the integration works correctly, for example if you use a library for computation you will test whether your function which uses the library is producing the expected result > - code bureaucracy, which forms vast majority of the program - that is, including/injecting/managing dependencies, moving data around, jumping through and around abstraction layer; this I believe is untestable in principle, unless I'm willing to test code structure itself (technically doable for my Lisp projects...) Here again you do some integration testing, for example when handling database operations, you can setup a test to load fake data in the database, run operations on this fake data and compare the results, and then purge the fake data. > - the user interface, the other big part, which is also hilariously untestable, and rarely worth testing automatically, as any regression there will be immediately noticed and reported by real people This sort of testing has become much more common for web apps and mobile apps, there are two things being tested one is whether the interface loads correctly under different conditions(device types, screen resolutions etc) this is tested by comparing the image for the 'correct' configuration with the test results, the other thing is whether the interface behaves correctly for certain test interactions - this is tested by automating a set of interactions using an automation suit and then checking whether the interface displays/ouputs the correct result.
- cellularmitosis 8y agoThis "Architecture: the lost years" keynote from uncle bob was helpful for me: https://www.youtube.com/watch?v=WpkDN78P884 https://www.youtube.com/watch?v=WpkDN78P884 I've also struggled with this notion of "untestable" code. Untestable code seems to usually be a big pile of function calls. Testable code seems to be little islands of functionality which are connected by interfaces where only data is exchanged. Practically, this seems to be about shying away from interfaces which look like 'doThis()', 'doThat()', and more like 'apply(data)'. Less imperative micro-management and more functional-core/imperative-shell. Edit: a network analogy might be: think about the differences between a remote procedure call API vs. simply exchanging declarative data (REST).
- icc97 8y agoThe only thing that smelled like a path to enlightenment seemed to be Haskell's QuickCheck [0]. It's actually going out searching for bugs for me, rather than me having to pretend like I can think of all the failure cases. I haven't implemented it in a project yet though so I don't know how much easier it is than unit testing. [0]: https://hackage.haskell.org/package/QuickCheck https://hackage.haskell.org/package/QuickCheck
- frobozz 8y agoI'd like to add: 4) Don't know how to mock effectively I've seen three flavours of this one: - tests that take forever because they don't mock a slow external process that gets called 100s of times in the suite. - tests that randomly break because someone left a file in /tmp/ or some other lack-of-isolation mystery. - tests that run like shit off a shiny shovel, because everything is mocked, meaning that nothing is actually tested.
- polygotdomain 8y ago>tests that run like shit off a shiny shovel, because everything is mocked, meaning that nothing is actually tested. Can you expand on this a bit? I've always worked under the impression that tests should be focused on testing a narrow slice of code. So if my SUT has some dependencies, those will be mocked with the expected result from the dependency, and possibly even with the expected parameters in the arguments. The asserts will check the result from the SUT, but also that the expected method on the mock was called. This way, I'm just testing the code for the SUT, nothing more.
- merlincorey 8y agoI'm not the GP in question, but I have worked on code that I think fits this phrasing. In that project's case, 99% of tests were mocks where the only thing being tested was whether or not the mocked function got called the expected number of times or with expected arguments. So the many thousands of tests ran very quickly, and over 90% of the code was covered by tests; however, nothing was actually being functionally tested in those cases. In other words, the tests ran like shit off a shiny shovel.
- frobozz 8y agoYes. This. What then happens is that the mocked out functions change what they return, or the order of what they do inside (e.g so that for a given set of inputs now throws a different exception). Someone forgets to update the mocks. All the tests continue to pass, even though none of the conditions are actually possible in the program.
- BeetleB 8y agoDepending on the language: Unit testing is easy for beginners. Unit testing done well is much less so. This results in many inexperienced people writing poor unit tests. In my last job, I worked on a legacy C++ code base. 500K lines of thorough testing, but none was a unit test. It took 10 hours to run the full test suite. I set about the task of converting portions for unit testability. I was surprised how much of a challenge it was (and I learned a lot). There were few resources on proper unit testing in C++. Mostly learned from Stack Overflow. Lessons learned: 1. If your code did not have unit tests, then likely your code will need a lot of rearchitecting just to enable a single unit test. The good news is that the rearchitecting made the code better. As a corollary: You'll never know how much coupling there is in your code until you write unit tests for it. Our code looked fairly good, but when I wanted to test one part, I found too many dependencies I needed to bring in just to test it. In the end, to test one part, I was involving code from all over the code base. Not good. Most of my effort was writing interface classes to separate the parts so that I could unit test them. 2. For C++, this means your code will look very enterprisey. For once, this was a good thing. 3. Mocking is an art. There's no ideal rule/guideline for it. Overmock and you are just encoding bugs into your tests. Undermock and you are testing too many things at once. 4. For the love of God, don't do a 1:1 mapping between functions/methods and tests. It's OK if your test involves more than one method. Even Robert Martin (Uncle Bob) says so. I know I go against much dogma, but make your unit tests test features, not functions. 5. If your unit tests keep breaking due to trivial refactors, then architect your unit tests to be less sensitive to refactors. 6. For classes, don't test private methods directly. Test them through your public interface. If you cannot reach some code in a private method via the public interface, throw the code away! 7. Perhaps the most important: Assume a hostile management (which is what I had). For every code change you make so that you can write a unit test, can you justify that code change assuming your project never will have unit tests? There are multiple ways to write unit tests - many of them are bad. This guideline will keep you from taking convenient shortcuts. This advice is all about unit tests, and not TDD. With TDD, it is not hard to test yourself into a corner where you then throw everything away and restart. If you insist on TDD, then at least follow Uncle Bob's heuristic. For your function/feature, think of the most complicated result/boundary input, and make that your first unit test. This way you're less likely to develop the function into the wrong corner. When I completed my proof of work, the team rejected it. The feedback was it required too much skill for some of the people in the team (40+ developers across 4 sites), and the likelihood that all of them will get it was miniscule. And too much of the code would need to change to add unit tests. I later read this book: https://www.amazon.com/Art-Unit-Testing-examples/dp/1617290890 https://www.amazon.com/Art-Unit-Testing-examples/dp/16172908... And it contains quite a bit of what I learned from unit testing.