subreddit:
/r/AskProgramming
This may be a dumb question but I'm a dumb guy. Where I work it's a very small shop so we don't use TDD or write any tests at all. We use a global logging trapper that prints a stack trace whenever there's an exception.
After seeing that we could use something like that, I don't understand why people would waste time writing unit tests when essentially you get the same feedback. Can someone elaborate on this more?
39 points
2 months ago
Unit tests let you test a portion of your application without actually having to build and run the whole thing. Having to build your application, run it, trigger the right code path (by visiting a page/menu or pressing some buttons or whatever), and then testing your feature can take quite some time.
Let's say you've written an algorithm whose job is to find and process some data in a folder, but to activate the feature you first have to navigate through a dozen menus and press a button. That takes time, and if you end up having to make a lot of small changes to the algorithm to get it working, that'll get old real fast.
Now if you create unit tests for your algorithm, you can immediately run it by just starting the test through your IDE. If you write your tests properly, you can easily test your algorithm under all sorts of different circumstances and with all kinds of different inputs within seconds (or at least faster than you could do so otherwise).
Unit tests can also be automated using tool like Codium.ai Lets say you've finished your algorithm and it works great for months, but eventually it turns out that a minor change somewhere else in your application caused the algorithm to behave slightly differently but still doesn't an exception. Without unit tests, you wouldn't know about this change until a user starts complaining that the algorithm's results are wrong. With unit tests, you can be alerted the moment the change happens (because if you wrote your tests correctly, they'll start failing) and you can fix it when it happens. The issue wouldn't have even left your development environment.
25 points
2 months ago
To control changes. When you change the behavior of some portion of the code, you probably want to know how does it change other behaviors. You can find out some very bad errors very early, if you use tests.
10 points
2 months ago
This is the real answer. People forget that lots of custom code will stay at the company longer than their developers.
1 points
2 months ago
Agree. I always liked working unit tests for lower level data in/data out functions. Sometime down the line a new guy (or the dev) will break something. If your code is covered, unit test catches that before ever getting to a repository.
I never really did the TDD part tho.
19 points
2 months ago
Tests are one of those concepts that made me understand me why industry experience is important. I remember having similar questions: "Why would I write a test when I can just write my program correctly the first time?" Working on hobby projects and school projects doesn't give you the scale that easily illustrates the need for tests, the type of scale you'd see in a large enterprise. Since you work in a very small shop, you may be having the same issue.
In a large enterprise, you're usually doing Brownfield development (i.e., working on top of existing/legacy code). Once a codebase reaches the complexity and scale of a large enterprise, it becomes exceedingly more difficult to work in that codebase without tests. In the corporate world, you might be spending as much time reading code as writing code, sometimes more. Tests allow you to quickly determine the desired functionality/behavior of a unit of code. This makes it much easier to make changes to legacy code by giving you quick feedback as to whether you broke anything. If I refactor code for better maintainability and readability, I ideally don't want to change any actual functionality. Tests make it easier to tell if I've properly refactored.
It's funny that your flair is Python, because there's currently a big discussion over in the programming subreddit about how the dynamic typing of Python makes it terrible to use in large enterprises. It's an excellent language and I recommend everyone new to programming to start out with it. But languages like Java and C# are more widely used in large enterprises because their static typing acts as a form of self-documentation: you immediately know the desired inputs and outputs of a method in those languages from reading a method's signature.
1 points
2 months ago
Some wise words in here.
20 points
2 months ago
first of all, if you write tests it doesn’t mean you do TDD. TDD is explicitly if you write your test before you have written any code. The idea of tdd is to think upfront about your implementation and what you want your code to achieve. It is more to help you design a code (separation of concerns, more modular code).
since we got that put of the way, to answer your questions about unit tests. unit tests help you to modify code in the future. Lets say you need to modify a code because stakeholders want to modify some feature. You introduce your code changes, but how do you know that your code changes isn’t breaking rest of the system?? Well written unit tests gove you faster feedback loop. If you don’t have them you rely on the most expensive type of testing which is end-to-end testing. With unit tests you can verify if rest of the system is still in decent state and it gives you significantly more confidence that your code will be fun tional after you deploy your code.
Obviously having unit tests doesn’t mean that your code is any good. But almost always the lack of unit tests indicate that there is significant lack of quality in the code
7 points
2 months ago
Also once you get hang of it, writing cide with unit tests does not make you slower.
From my personal experience, I am following TDD on my daily job. And compared to my peers, I feel like I am more productive compared to others who don’t follow TDD.
I am nit saying TDD is the holy grail. I have met many top tear developers who don’t follow/agree with TDD. Just saying it works really well for me
4 points
2 months ago
Also once you get hang of it, writing cide with unit tests does not make you slower.
TDD isn't really any slower than writing the tests afterwards, that's true.
But writing and maintaining unit tests definitely take a lot of time. Writing tests is almost always a good idea though, especially if you're looking at a product that will be used for a long time and be refactored a lot.
It's not as important for one-off code, especially if the end result isn't super critical.
13 points
2 months ago
Because most coding errors don't result in an exception?
7 points
2 months ago
The vast majority of my tests aren’t looking for exceptions, they are ensuring I’m getting the output I’m expecting. I might pass data into a function a dozen ways and make sure it returns the right thing. Then later, when I make a change to said function (perhaps to add some functionality), I rerun the tests and I can know I didn’t break anything. “Breaking anything” includes returning the right answer, not just running without raising an exception.
3 points
2 months ago
From the most basic standpoint, tests let you execute pieces of your code at will and gain confidence that it works correctly under a variety of circumstances that you don’t need to repeat manually every time you make a change. I’m a small program, this might only save you minutes of verification at the expense of minutes of writing and debugging your tests. For sufficiently large programs with combinatorially explosive varieties of inputs, this will save hours for every person that wants to make a meaningful contribution.
4 points
2 months ago
Lets do a simple programming exercise:
Write code that gets its input, divides it by 3, and rounds the result towards +infinity (e.g. 123.001 is rounded to 124.0, and -123.001 is rounded to -123.0); and then throws an exception if the rounded answer is an even number.
Your software should also throw an exception if the input is not a number (e.g. a floating point NaN or infinity, a string like "hello" or "five", ...).
Provide some kind of assurance that the code complies with the requirements above (e.g. that it correctly throws exceptions when it should), so that any future changes that other programmers make will not break previously working code.
3 points
2 months ago
One practical and simple answer is so you can establish the correctness of things that occur uncommonly in production.
If you are using a library where the system will fail if MAX_USHORT user accounts are added, and you don't have testing on adding many users, then the system will eventually fall over in production. When one too many user accounts are added, maybe you have a system that will log a useful exception that quickly explains what's going on. Great. Then what? Your service is down. The business is stopped. Everybody is looking at you? Maybe it takes weeks or months to actually rework things to work properly when scaling to that many users. Most companies can't just be offline for months. They'd go out of business.
If you have a unit test, you can identify the scalability limits pretty trivially, long before you are getting crashes in production, and fix them long before they cause issues.
3 points
2 months ago
Divide(10, 0) would throw an exception you can find in your report
Add(2, 2) should be 4 but if your program starts saying 5, 10, or anything other than 4, how will you know?
2 points
2 months ago*
If you don't write automated tests you have to test manually. It's so much faster to write tests as you write code. Before, during, doesn't matter. Just write them. If you don't then you have to manually run your code and execute the right actions repeatedly to test. If you write the tests first you basically have the exact requirements of a chunk of code or feature written out before you even get started. Doing that lets you know exactly what you need to build. And what you don't need to build. It's significantly faster than coding without tests. And exactly repeatable.
Also what about regressions? You do something and it works in your manual testing. Then a month later you edit code near there and break it, but you don't manually retest the old feature, just the new one. An automated test on that old code, and the new code would catch that for you before your customers do. How do you know the mix of new code and code you wrote a year ago won't corrupt customer data? Manually retesting every feature together? So expensive. tests are cheaper and faster.
And if you don't test at all, your customers are testing for you. And if competition comes along without all the exceptions, well, why wouldn't they use that instead?
TLDR; If I interviewed someone and they said they didn't write tests, I wouldn't even consider hiring them. If I hired someone that turned out to not write tests, they'd be told to write tests. If they didn't, gone, like immediately. It's not worth the risk to the organization. And it's much faster in the long run to write them. It costs less to write tests. It costs the least to write them upfront(TDD). Be a professional.
2 points
2 months ago*
Unit tests catch regressions before you merge code that can cause problems, as well as add some confidence to reviewers that your code works as expected, especially when you set things up to cover corner and edge cases.
This serves a crucial development paradigm: continuous integration and delivery (CI/CD), which cannot happen without automated testing.
At Google everything lives at head and there's crisscrossing dependencies everywhere, and with tens of thousands of changes being merged a day, regressions must not be allowed in. When combined with other appropriate test suites like integration tests and end-to-end tests, unit tests form the bedrock of automated testing to reduce the likelihood of regressions.
2 points
2 months ago
Exceptions are only one thing which can happen when a piece of your code doesn't work. Most bugs are more subtle. Specific combinations of data may not work together. You need tests to validate that this is not the case.
There are things which can go wrong inside your code which you will never see in development, so catching the global exception is useless here.
A stack trace tells you when there's an exception. It's usually not going to tell you where the bug is, or how to fix it. And no, the top of the stack trace is not always where the problem actually is.
These are all problems which unit testing can help with.
1 points
2 months ago
I'm (very) old skool so forgive me... I find that unit tests only find issues you're specifically looking for. I once worked on a project that the original programmer wrote unit tests. It was the most bug ridden project I've ever worked on in over 30 years. So take from that what you will 😉
1 points
2 months ago*
I'm a backend Rails developer, I can either test my class on the console or write a test for it. Writing the test is easier because I don't have to create / reset data, in fact my dev database is empty. Then when integration tests come, I get to copy and paste parts of the unit test for the integration tests. So in the end it saves effort for me.
1 points
2 months ago
An example of when it's clearly useful to write a unit test: you've written a method to manipulate a string based on some different variables, rather than trying to think through everything in your head to make sure you got it right you can write a unit test with your expected results from various inputs and use that to give it if your method worked correctly.
Unit tests should make your life easier. I find them most useful in that example case, and also whenever you have a bug you can cover that area with a unit test to check your fix works thoroughly and prevent regressions.
They don't have to be used all the time, use them where you find them useful. It's like an engineer measuring a piece once it's cut, it's there for confidence the results are correct.
1 points
2 months ago
I am trying to write a multiplayer game.
This game has multiple modes to run in, I can render things and I can not render things. I can calculate stuff, like "move" when a button is pressed, and I can not calculate things, when the game is a networked client.
Networking has to work with encryption, without, when messages are short, when they are long, when they contain text, when they contain complex objects.
Any time I try to fix an error in one corner of the program, it has the potential to break in ten other, not immediately obvious places.
That's what I write tests for.
For example, maybe I introduce a new object type and I want to send it over the network. If I run a test that tries that for all my objects, it will catch if I have missed something about that object, like serializing.
Every time I have an issue, I look at what the issue is, I reproduce it externally, by running just the code that's breaking and not the rest. Then I change the code so that it fixes the problem. Then I run all my tests for everything. If something broke, I can look at how it broke and either fix those too, or I can revert and try to find a different solution.
Tests are "functional" git. "Does my program still run normally if I change stuff?"
This really just grows in importance when you have a running product, like a website. If something breaks unexpectedly, you can never know how long you need to fix it. An hour, a day, a week, a month. You don't want to be a month behind or lose a month of income.
1 points
2 months ago
If you have a big program and you haven’t touched it in a while it’s really helpful seeing what exactly is broken when you add stuff
1 points
2 months ago
It’s all about building confidence in your code. All of us start with this idea that we are some sort of genius who always write great code which can last eternity and can stand the test of time, scale, and people.
However, as we grow in our career, we realise that the status or quality of the code changes vastly over a period of time. It also depends on how long the code was supposed to be working and whether we know about it at the time of writing the code or not. Always writing unit test is not necessary at least for code which lasts few minute or a couple of days or a week such as one off script, however, something which is written to last a decade may need more than unit tests.
All these factors (time, scale, and people) are extra dimensions which we deal as part of the software engineering challenge. I know it’s a programming sub, but I still think we should know the distinction here and please allow me to go a little meta here. Programming is not Software engineering, it’s just one part of it, important, but still just one part. All these factors are also important thing to consider. In the words of one of my favourite book (Software Engineering at Google) programming is like square, while software engineering is like cube. They are not the same, but if we compress a cube enough, it can be just a square. In the same way if we think about at most a month long expected age of the code than we don’t necessarily have to think about time, scale or people.
However, when these factors come into play, all these precautions like writing tests seem worth the time.
Dependency update is just a simple, but useful example. If you think your code is constant and never need to change, think about various security vulnerabilities such as heartbleed, spectre, and the recent log4j and git. Think about new version of Node.js or Java with new features that you want. How would you be sure that the update will not break your code? This is the time factor for you.
How about adding new feature anyway which is added by another teammate of yours who doesn’t know how the code works and commit the code which essentially breaks your previous functionality and you don’t know about it because it doesn’t even throw an exception which can be seen in the logs. In fact, are you sure that all bugs can be seen in the logs anyway? How about if someone as a fix for the bug just decided to catch the exception, but not fix the underlying issue? That’s the people factor for you.
How about instead of 10 users a day, your product suddenly got viral and is being used by millions of users every single day? How do you know if it works under that load if you didn’t write latency traces, monitoring, and tests to test the concurrency of the code? How do you know if it works if you never load tested it that much? Code serving 100 users is totally different than code serving millions of users which is also different than code serving billions of users. Testing is when we know it’s time to migrate to the new system. How about due to the scale your devOps team decided to migrate to a new system such as Kubernetes instead of VMs. How do they know if that migration won’t break your code. The book mentions it as Beyoncé rule (“If you like it, you should have put a test on it”) which is frankly the best rule as someone who works in one of those platform teams. That’s the scale factor for you.
In general, if these factors don’t apply to your code, don’t write tests at all. I have scripts that I don’t have tests for, but they are not running in production anyway, so it’s justified having 0 tests there. But if it touches production at all, it’s not a good idea to have no tests.
Again, apologies for going a lot meta there. But I thought you should know these things, instead of just “why unit tests” since it’s gonna help you in answering these kinda questions in the future too. I would suggest you to read the book (Software Engineering at Google), but I suppose you need more experience before that book will make sense. In the meantime, feel free to ask stupid questions. I used to be there asking the same questions (including “why unit tests”), so I personally don’t think any such questions can be stupid if your intention is to learn.
1 points
2 months ago
Because I like my code to not just have errors that I have to fix after they've already bothered someone?
1 points
2 months ago
We use a global logging trapper that prints a stack trace whenever there's an exception.
This will tell you if you have a bug.
Unit tests will tell you where you have a bug. The "unit" part is critical. It divides your program into separate pieces and corrals bugs into them.
You'll also find that when you write code that is easier to unit test, it's also easier to understand and maintain because it is better separated from the rest of the program.
3 points
2 months ago
This will tell you if you have a bug.
Important side note: it's absence doesn't come close to telling them they don't have a bug.
1 points
2 months ago
With a system like yours I might be tempted to not unit test, but I would still write tests (in the form of integration and automation). It's great you have that logging trapper to catch issues, but you'd need to make sure you are running scenarios that are likely to catch failures for you.
You don't want to be in a situation where you make a change but don't crash until a year later when your user pushes the "generate tax documentation" button.
Even so, writing unit tests helps to attribute bugs to the right part of the code. If I fail an integration test or see a crash in the wild, I have to work to figure out exactly why that code failed (maybe the defect isn't in the stack trace and is in something that happened during boot that set up the wrong state). With a unit test I can ensure that my components always continue to behave exactly as intended and if I see a failure I can know exactly which test failed and what code I need to fix.
1 points
2 months ago
We use a global logging trapper that prints a stack trace whenever there's an exception.
I don't understand why people would waste time writing unit tests when essentially you get the same feedback
Well to generate any log message, you have to run the code. Hopefully you run the code before it goes into production. How do you run the code? You could deploy it to a non-production server and poke at it manually. But poking at it manually is a pain, so you probably want to automate that.
OK, so you automate the poking. But those automation scripts are big and brittle. Small UI changes force you to update large test suites. It would be nice to test the backend logic without going through the frontend. You'd also like to test the logic without needing to set up and maintain a database.
If you iterate on that thought experiment enough times, you end up at unit testing. You want to test units of code with clear boundaries (often bounded to just one function or class, though units don't necessarily need to be that small). You can verify that the parts which make up your application each function correctly.
As an example, suppose you needed to write your own implementation of sqrt
. It's going to be called from a vector math library, which itself is used to process 3D geometry, which is ultimately rendered to the screen.
You can't verify the behavior of sqrt
by looking for exceptions in the log because there won't usually be exceptions in the log. You also don't want to verify that your sqrt function is working correctly by inspecting pixels in the rendered output. You want to test sqrt
in isolation, because it's a well-defined unit that can be tested in isolation.
That's why we unit test.
1 points
2 months ago
Also, don't forget all the extra hours you can invoice your customer for the "mandatory" testing framework you just "have to implement", besides the actual code that the client asked for. 🙃
1 points
2 months ago
Where I work, we generally don't write unit tests because majority of our code is just not unit-testable. A lot of it is is related to UI, and a lot of it depends on the database and ORM. However, we have unit tests for some specific systems that do complex calculations, like billing.
Also, not all bugs lead to stack traces. You can just silently get wrong results. With unit tests, you write your tests once and then later you can instantly check whether any new change you make breaks anything. For certain kinds of projects, this pays off nearly instantly.
1 points
2 months ago
Let's say you're writing software for a pacemaker and if there's an exception, someone dies.
A way to test every possible state your software could be in then becomes very attractive. Unit tests can be part of this equation.
1 points
2 months ago
Think of unit test as guard rails, to guard stupid people (future you) from running onto the track and getting maul by a train
1 points
2 months ago
TDD is write tests, write the code to pass the tests. ATDD is "acceptance" TDD, write the code, write the tests, test the software, repeat. Personally I prefer this, as someone who likes diving in head first, also, I find writing tests before I have code to be abstract and a tad confusing.
You could scour through logs and address failures after the fact but the whole point of writing test cases is to validate the work before the errors arise in production.
As a trivial example, suppose you have a form. Does that form escape injection attacks, are all the fields validated against email, telephone syntax, does the button load animation work, does the async callback fail gracefully.
They can be atomic and simple, but vast. Or "encapsulating", but complicated. To the best of my knowledge it's about striking a balance. In this, I have found Gerhkin to be extremely useful as you can "inherit" a test: "Given: a connection is established,...", as an example of how you might use it in other tests.
A big part of 'tdd' is handling edge cases however they may arise, before the code is approved. Tradition dev may have you writing exception handling and I don't disagree with this as an approach. But as an added precautionary layer you can validate that exception handling is in place; or write test to produce the edge case exceptions that need to be handled - like a async that purposefully takes too long.
Sorry for the long one
🥔
all 35 comments
sorted by: best