Software is one of the most complex things humans can set out to do.
Software is complex because it's invisible. It's because it's not tangible.
You can't possibly "show" 500,000 lines of code to someone and tell him/her "see here? This is what my program does" the same way you can (if you were a regular civil engineer, for instance) show a friend a building and say: "This is what my building is like".
There's not much ambiguity in the physical world. You show someone a door (it might even be a somewhat more advanced door or something like that), you can be sure you and him/her are looking at the same thing. There's no risk of you pointing at a straight edge and him/her "seeing" a round edge.
Not so with software. The layers of abstraction are so deep that it's much more difficult to share your understanding of a codebase with someone else. Chances are that things will get misunderstood in the process.
But that's not really where testing comes in. This is not where the real complexity lies, in my opinion.
Perfection is expected from software. Perhaps because you can't negotiate with it (unless you dive into the code in order to hack it) on a physical level.
If you live in a building whose door is too small, you can perhaps hire someone who, with a little work, increase the size of the door with a few hammer strokes or something like that. In other words, the physical world is negotiable with the tools you have.
If you have a piece of software that doesn't do what you want, you're basically screwed. You can't adjust it for your own needs the same way you can a door that's too small.
However, since business objectives are always changing, users are always wanting other features from software. This isn't bad at all, mind you, it's something we accept when we become software developers.
The natural consequence of this is that software needs to be constantly changed and updated. Not just adding more features, but combining existing features into new ones, removing now useless features, adding newer levels of abstraction and so on. This is what kills bad software in the long run. Complexity becomes unmanageable and nobody knows what side is up anymore.
The fact is that we can't avoid changing software, because we live in a changing world. Software supports all other industries in the world. Every last sector in an economy needs us to make their process more effective, lower costs and so on.
So we can't get rid of changing software. So what do we do? We embrace it. And take steps to enhance the process.
The solution is testing. A lot of test cases. Unit tests, functional tests, integration tests, user interface tests, you name it. Yes it's a lot of work, but the alternative is code that does not survive more than a couple of years of changing requirements.
Having an all-around testing framework under our software is the only way we can proceed to change it to adapt to new expectations (remember, we just can't avoid it) and be sure that the system as a whole still works.
Removing unused code is just as important as adding more features if you're to keep your project to a manageable size. Without tests, you'll just never perform the necessary structural changes that newer requirements make unavoidable. You just won't do anything that may cause your existing system to break down. And you'll have no idea what sort of functionality may break when you change your code unless you can run tests to verify that.
Having a large number of test cases that you can run automatically after each change is the only way software can evolve with time and not turn into a frankenstein.