I can't emphasize enough the advantages of a TDD-based development method. When you adopt TDD, your unit-tests become first-class citizens alongside the code you write, instead of being code which is maintained for the sake of having unit-tests and not being kept up-to-date.
In TDD, you use your unit-tests as an executable specification of what your component should do. You would do this by considering what you want your component to do, and then writing tests to exercise this functionality. Since your code initially won't have any of this functionality, all of the new tests you write would fail or be red. Once you've got your tests, start implementing the component. Gradually, as you add the required functionality, the red tests will turn green. The nice thing is that after you've implemented enough functionality to make all your tests pass, you know that you've completed implementing the intended specification and know exactly where to stop. Too often I've seen situations where a developer will have completed implementing the required functionality but doesn't stop, instead enhancing the component and adding extra functionality and eye-candy, none of which is part of the required specification, wasting active development time.
Once you have your unit-tests, it's easy to set this up in a continual integration environment. This environment would check-out the latest code from your repository, build it, and then run your unit-tests. If any regression happens, if anyone checks in any code which breaks your unit-tests, you'll know about it pronto, instead of finding out after it's been deployed to your production environment. To ensure that new code doesn't introduce a regression, we've set up login-hooks on our repository to ensure that all code submitted has also had the accompanying tests run and that they pass. This is especially helpfully when you have multiple people working on a project, as they can see via whatever monitoring dashboard you might use whether the repository is good to sync to at that point in time or not. It's also easy to locale a particular version of the repository which does work, letting people work with known-good versions, while someone else is working on fixing whatever issue is currently breaking your build. This would also potentially mean that any 'green' build as indicated by the dashboard is a build that has a very good probability of not encountering issues when pushed to a production environment.
Many people think that adopting TDD would mean extra work and hassle, and that it would be more time-consuming. Consider though that the extra time spent writing tests will prevent any functionality that is being tested from breaking, and that you'd be finding those breaks sooner, rather than later.
Another advantage of using TDD is that you'll give your design far more attention, and that it would be far better structured and componentized over a non-TDD approach. This componentization is important to be able to have a unit-test suite which is quick to execute and non-brittle.
GUI-testing is difficult, but not impossible. Consider web-UI testing technologies such as Selenium, WebDriver and Watir, which can exercise web-UIs programmatically. It's easy to misuse these tools too, by performing only expensive end-to-end testing using them. A far better approach is to abstract your UI layer so that it can be tested in isolation, independently of your business logic. You don't want a UI test going and performing operations on a database.
To re-cap, you want to write efficient unit-tests to make using TDD a pleasant experience, rather than a burden. Your tests should be quick, test components in isolation, and should ideally be run all the time.
What I've described here is an ideal case. You don't have to adopt every single idea mentioned, but you can pick and choose whatever works to make your development process more efficient.