Ask HN: How do I start with test driven development?
But one thing I don't like with me is that I never write tests.
Why I never followed that aspect is because I always felt that the thing that I'm writing, sounds totally OK.
One another negative point is that I don't actually get it that How to do/start this and what type of tests I should write for any given project. Maybe this is because I have never done this before.
For example recently I wrote a identicons library, Penticons[1]. It generates GitHub contribution flavored identicons. You can read the development story here[2].
Now, the thing that stopped me from writing tests was, I thought that ultimately it's a image generation library, why this thing needs any tests.
I don't want to lost my 1 point in the Joel score. Whenever I see a build passing label on a GitHub repository, It makes me crazy.
I'm asking here because I just love HN, I know people will suggest some awesome things here.
1. https://github.com/penticons/penticons.go
2. http://pravj.github.io/blog/penticons-the-hash-game/
65 comments
[ 2.6 ms ] story [ 120 ms ] thread2) A story about unit testing at Airbnb: http://nerds.airbnb.com/testing-at-airbnb/
3) Practical tips for test driven development: http://soundsoftware.ac.uk/unit-testing-why-bother/
Part 1 - https://www.youtube.com/watch?v=z9quxZsLcfo
Part 2 - https://www.youtube.com/watch?v=JoTB2mcjU7w
Part 3 - https://www.youtube.com/watch?v=YNw4baDz6WA
Part 4/5 - https://www.youtube.com/watch?v=gWD6REVeKW4
There are two aspects of testing that you need to discern: the development and the project management point of view. The first regards what and how to test things. This can be achieved by any developer and is moderately easy with the right help and mentoring. The second instead is the most difficult: trying to force tests in a non-test-focused environment, where the business does not understand the meaning and/or the value of testing can be poisonous to us and the business itself. In other words there must be an effort in both directions. I can keep talking about these two points, but I'll skip them for now unless you're interested.
Regardless of the current environment you're in, and taking for granted that the second part is - if not fully, at least partially satisfied, let's have a look at how to start with TDD: the major problem you're facing is that TDD does not define "what to test". That's why BDD was introduced, to fill in the gaps that TDD has.
Still BDD is somewhat lacking depth and proactive understanding from the project level on how much should be spent on unit tests, how much on functional and acceptance tests. On this note, Google has defined the ACC, which helps you define the testing plan for your application; I think it's totally worth from both a developer and business perspective to dig a bit more into that, especially if you want to get into testing properly.
If you still fail to see why tests are important, let's put it down this way, using James Whittaker's words: "although it is true that quality cannot be tested in, it's equally evident that without testing is impossible to develop anything of quality".
my 2 cents
"trying to force tests in a non-test-focused environment, where the business does not understand the meaning and/or the value of testing can be poisonous to us and the business itself"
Sorry to be a bit negative, bit it sound like you have a conclusion that you want to reach before you have done your research. That's not really how research should work. Research should be done and conclusions reached afterwards.
Writing tests will change your coding style and way of thinking which is great. You might read some articles about testing for your language of choice.
Just start :)
This week, one of my coworkers described being new to TDD like being new to vim. There is a horribly steep learning curve but once you "get it" it becomes second nature. The real win of TDD, especially for open source, is that when the projects start to get bigger and harder to manually test you can merge PRs with a bit more confidence that you're not breaking things. I can speak with a bit of experience here as an OSS co-maintainer of two pretty large (>500 unique contributors) projects, graphite, and salt.
Answering the why,
scenario 0 - it forces you to have functions that do one thing and one thing well. Unit testing mostly helps people keep functions small and focused.
scenario 1 - assume you are working on a fun app without tests. Everything is fine and the app works great. After some time you decide to add a new feature X, and you realize you need to modify an existing function to achieve that.. you modify that, and the new feature X works fine, but two days later you will realize that an old feature Y is broken.
scenario 2 - scenario 1 but with collaborators instead of yourself revisiting the app.
scenario 3 - people use unit tests to understand code - while reading/working on other people's code. Unit tests also serve as excellent examples on how to call/use something, which params to pass etc.
There are a lot more reasons esp for large projects, and you never know when a small one off project grows into something big and critical so its recommended to always start with unit tests as adding them later is usually 10x more work as it would require refactoring etc.
However, the longer you wait, the higher the risk that you'll discover your code is not easily testable... which will require lots of refactoring to make testable. And that refactoring is made harder by the lack of tests...
In the meantime, if you're truly stuck, write down a list of how you're currently manually testing your code and write down edge and corner cases you're worried about.
(disclosure: I make testing tools for a living.)
If you don't believe that you need unit tests, you won't write them - so step one is persuading yourself that they're useful. So try to remember a time when you broke your code in an unexpected way, and then try to work out what kind of unit test would have helped avoid that.
I'm not sure this one does. It's a very simple library and there's no bugs in the issue tracker.
I would write a test for the first bug you encounter, though.
Or, if you start writing a more complex method on a new feature, start TDD'ing that.
- Build something super easy (<4 hours), using Test-Driven Development. The example I used was a URL shortener.
- Rebuild one of your existing apps using Test-Driven Development. If you're wondering where to get started, watch videos titled "creating a simple app using ____ and TDD", or even consider calling the Rails hotline.
- Get someone to look at your test code for you. I learned a ton from this, I can't even begin.
- Use MiniTest. RSpec is amazing, but I found MiniTest to be more transparent as to what was actually happening.
- Check out the codeschool and codeacademy tutorials. Very helpful.
Best of luck. It's a very difficult topic, one that I really struggled with.
Once you start testing your code, it's addictive. Testing gives you confidence.
- By forcing you to detail your requirements, writing tests helps you make sure that you're building the right thing.
- Testing lets you refactor your code with confidence -- if your old tests still pass after you refactor, your new version works the same way as your old version.
- Testing makes your code much (MUCH!) more maintainable. As long as your old tests still pass, you can be confident that the new code you wrote didn't break any of your old code.
I remember last year contributing[1] to a 2048 python game, where the maintainer committed[2] test suite lengthy than my commit and He was doing it like a charm.
I think I should start from that very repository as I know it very well already.
1. https://github.com/bfontaine/term2048/commits?author=pravj
2. https://github.com/bfontaine/term2048/commit/6f9a46283c2cc0a...
I have Emacs set up to run them on a keypress without even having to save the file; you can create and run these tests even quicker than trying things out in a REPL, and unlike the REPL they persist to be rerun after the next change.
If you've been feeling like testing is too much work, one angle is to reduce the amount of work it'll take to start.
Then I can go off and change stuff and as soon as something breaks I see it in the side window. If I break something and the tests don't break, well, time to write more tests :-)
Take those things, write automated tests to test them. Run the automated tests instead of testing manually, every time you can. Especially when making changes don't run the software yourself to see if your changes did what you wanted, write a test to do it and run that. The first time one of your tests catches a bug that you never would have expected, you'll be hooked.
"Test-Driven Development" actually refers to (almost) always writing the tests _before_ you write the code they test. Not everyone subscribes to this, at least universally. I wouldn't worry about that for now, I think if you just start writing automated tests every time you would have been manually testing, you'll be on the path and things will fall into place.
I learned how to write tests and I was sold on the value of TDD only after I was in the middle of writing a non-trivial, multi-module client/server app. Right now, you think "I don't need to write tests for my penticons library, because I know it works." That's understandable. But as soon as you start incorporating it into other projects, or you extend it with other projects, having tests will make the process so much simpler.
Think about it this way: computing is essentially a way to repeatedly do things really quickly. I could take a pencil and a paper and write the numbers from 1-100 without too much trouble. But when that number is over 1000, or if instead of simply writing the number you perform some tedious formula, then you start to yearn for something like a `for` loop control structure.
The same with testing. Once you have a bunch of moving pieces, and every change you make you find yourself diving into the repl for 10 minutes doing the same thing over, and over, and over... you will actively WANT to know how to test.
So don't sweat it. Learn it if you want. Sooner or later, you're going to _need_ and _want_ to know how to write good tests, simply to save yourself the hassle.
My favorite part is when Josh Clayton excitedly said "Writing our tests is the fun part! Failed tests tell you what to do next!". It was a sort of an aha moment in Rails for me where everything came together.
Moreover, the emphasis was on integration testing (Capybara/Rspec), which I could more easily grok and see the importance, then say unit testing.
Here's a link to the Integration Testing Videos and I hope they could publish this for every one to see freely. I personally feel this was an eye opener to me, and could possibly be one for others as well.
https://upcase.com/test-driven-rails
P.S. I want Thoughtbot to make more real-world applicable quick-cut videos (e.g. Proper usage of the GH PR/Merge Model would be great). The weekly iteration is interesting, but its too conversational. I like rehearsed sentences that quickly convey the knowledge to me. That's the missing piece for me to subscribe.
First grab a testing framework for your language, if it's not built-in. Something that runs the tests in your project once you run a command.
Following that, add a test to a function in your project. You can start with an easy one. Try that and see what works, try covering corner cases, etc
Then you can go for actual TDD once you write new code.
Specifically episodes 6 and 7. But if you have time - I recommend most of the videos
Automated testing (unit or integration tests) is an extension of taking those little one-off things we do during development to make sure things work, and formalizing them to be a permanent part of the code base. Some people go further then and try to add tests for every feature or (gasp) every single line and sub-line of code.
It's easy to get carried away with testing. And some people have valid arguments against testing. But it's nothing too hard or unapproachable. The point really is that everyone does testing already, but not everyone keeps all their tests and makes them nice.
One alternative I've heard about is bug-driven testing. Whenever the code doesn't work as expected, write a test verifying the desired behavior. The reasoning was that the functionality was clearly fragile or complex, and therefore more likely to be accidentally broken in the future.
Seriously, the most fun I ever had with TDD was when using a language that my IDE had great refactoring tools for. Just write all your code inside a test to start with, don't bother with production anything. The test becomes a little like a command line program, launches immediately from the IDE and you can iterate super quickly. Your output is whether the test (with the test data) passes or fails. I'd pick the test framework by whatever the IDE liked best. Pursue solutions that viscerally satisfy until you know better.
Once you have a test working like that, do "Extract Method" followed immediately by "Move Method". The destination of the move is the production class. With two menu selections, you have a perfectly testable interface, called perfectly by the test. A good extract refactoring always proposes parameters for the method and lets you order and rename them. It will also recognize extractions that can't work, like when two results are generated by the selected code. In that case, extract more than one method and move all the methods to the production code.
Want to add more code? Do the same thing again and again and again, each time as a new test.
When you are done, you'll have production classes that are the composite of the extractions and all the tests that once contained inline code (but now contain method calls to the production code).
At some point, you will ask "how can I improve my tests?" The answer is easy: Code coverage tools. You should shoot for > 80% of your production code having test coverage. Some say more, few say less. Make some rules for yourself about things you don't bother testing, like getters and setters, use the 20% leeway so you aren't flogging yourself. Keep having fun, don't make a death march out of testing every last line of code.
This habit becomes really fun. You'll start telling your friends. Nirvana will ensue.
JetBrains is the company that makes ReSharper. I also use AppCode (their alternative to Xcode) in a way that's similar to how I use ReSharper. I do most of my iOS development in Xcode but load the project in AppCode before committing so I can take advantage of its superior code inspection and refactorings.
https://www.destroyallsoftware.com
a) contributed to by a lot of developers that aren't familiar with the source code,
b) so large that you're scared of making any substantial changes because the last time you did that another piece broke and it took forever to figure out it was actually broken and another forever to figure out how to get both pieces working together and in the process you half-broke this other piece and GAH maybe I should just rewrite the entire thing, or:
c) mission-critical, where a piece not doing what it's supposed to means lost lives or money,
then TDD is a net waste of time. Much like learning git, you'll be fighting an uphill battle with your mind at every stumbling block ("ugh this is so complicated - why am I learning this again? Doesn't putting my source code in Dropbox solve all of these problems already?") until you have an actual, real, legitimate use for the solution ("oh right, I'm learning git because using Dropbox to collaborate means 'conflicted copy' city").
Tests don't have to be complicated. They don't have to use the next new shiny framework, they don't have to have a smart lookup system that auto-runs files that are checked in and sends you an email if you push bad code; tests are just if statements, and they're mostly the if statements that you're already running through your mind when you program. Here's a hopefully not too contrived example:
When you wrote commit 4a069[1], how did you know the code you wrote actually did anything? From my quick understanding of reading this over, it looks like you changed `padding` and `size` from constants (5 and 30 respectively) to variables set somewhere else (in `utils.go`). If you made all these changes at once, then re-ran whatever code you use to make a penticon, the expected behavior is that you'd see the exact same thing as before (test#1).
But wait, were you even editing the same file you ran? Let's double `Padding` and `Size` in `utils.go` and make sure it changes the image (test#2). Alright cool, it does. But wait, the padding looks way too big - is it twice what it needs to be? Let's try `Padding = 1` and `Padding = 0` and make sure that looks how I expect it to (test#3, test#4). Awesome - that's perfect.
Does the size work too? I wonder if this would break if the `Size=0` (test#5). Oh shoot, what if both the size and padding were 0 (test#6). OH SHOOT WHAT IF THEY WERE NEGATIVE? (test#7).
---
All of that to illustrate: the difference between what you're (probably) doing right now and TDD is that in TDD you write down each test as you're testing it. At the end of adding those two variables you'd have a second file of 7 if statements that make sure that changing the size and padding works like it's supposed to.
Unfortunately, `penticons.go` is a horrible project to bring TDD into your workflow because 1) it's tiny and can probably fit in your head all at once so you'll know immediately anyway if something breaks, and 2) testing images is hard. It looks like a lot of your other public projects are equally as TDD-unnecessary.
What you could do is generate a bunch of references images (like a 1x1px empty image for when the padding=1 and Size=0), then write a script that runs the code that should generate the same image and make sure they match bit-for-bit. Again, though: until you're feeling the pain that having the piece of mind that no other part of your program was just broken by what you did, you don't need to add TDD to your workflow.
1. https://github.com/penticons/penticons.go/commit/4a069619545...
For bringing some value back into automated testing, one guy I worked with thought that usually the tests themselves aren't that important, but instead what was beneficial about an automated testing mindset is that it forces you to architect your code well, into small, modular pieces with carefully specified cross-dependencies.
I will also say that I've found Git to be very beneficial for some types of hobby projects. Particularly the kind where you end up deploying the result in many places. Like if you're toying around with an Android app, it's nice to know exactly what version of the app is on this emulator, that emulator, your main phone, your tablet, your spare test phone, etc. That's why I find it useful to maintain a Git repo for it, carefully and religiously commit every new feature to it, and create a build system that tags the built files with the current Git commit somehow.
TeamCity is a gatekeeper. Builds that fail unit testing don't get automatically deployed. Locally NCrunch keeps you updated on what is broken without you having to think about running your tests, since it runs them in the background for you. Resharper unit test runner helps you run and debug specific tests or groups of tests.
I work on a large application with many other developers and this flow works well for us.
Like most other learning endeavours, I found that the best approach was to start small. Try something simple like a fizz-buzz test. You'll probably have more trouble setting up and writing the tests than writing the code under test. That's okay. Like any other learning process, it takes time to get used to the new language and behaviour that is required to become fluent in writing testable code. Make sure that every time you save your working code, your tests run and you can see the result so that you get immediate feedback. And make sure to always work in as small a unit of work as seems comfortable.
Here are some example test function names for fizzbuzz
1. itPrintsFizzForMultiplesOfThree() 2. itPrintsBuzzForMultiplesOfFive()
These tests might be too broad in scope. Where is it getting the numbers that it's checking for multiples? What does "print" mean? How are the tests going to confirm what was printed?
1. itReturnsTrueForMultiplesOfThree() 2. itReturnsTrueForMultiplesOfFive() 3. itReturnsEitherFizzOrBuzzOrTheNumber() 4. itSendsTheResultToOutput()
These tests are against simpler methods and are easier to write but the tradeoff is that they make the code under test too fine grained at times. Can you have too many tests? I don't think that's likely in practice, not if they're true unit tests and extremely quick to execute. But some would disagree when they see a five method fizzbuzz class rather than a class with one "doTheWholeThing" method. You have to find what works for you. When you return to your code a day or two later, do the tests immediately make sense? If not, they're probably too complicated. Start small, get a feel for the shift in mindset, grow from there.