These pages all work fine right now, but no one on our team completely understands the business logic behind them and reading through the code line by line will be very painful.
Ok, so this is really the crux of your problem. Do you have access to the stakeholders of this application (i.e whoever it was designed for?) They are probably the best people to explain to you how its supposed to work. You need to get access to these folks and have them give you at least a crash-course in the "domain" of the application.
Only when you and your colleagues fully understand how this system works can you test it. If you don't have access to the stakeholders, then don't panic - just take this thing one module at a time and start mapping it out.
You don't have to go all out and learn the whole thing up-front - take it one module or subsystem at a time, make plenty of diagrams about how the various parts of the business domain work from the perspective of the users, and do the same then for how they currently work. Put both diagrams side by side and start planning how you might refactor the code to be organised more like the business flow, and less like the existing flow.
This can be a tricky process for sure but actually once you get going, especially once you know how the system should ideally flow based on the previous point, it's not that bad - bear in mind that you will surely be able to copy/paste a lot of your existing code - in fact, you should probably avoid the temptation to try to fix bugs on the fly at this stage. Focus instead on the organisation of your classes, etc, so as to make then adhere to SOLID - any classes that broadly stick to this will typically be very testable.
Any bugs or really poorly written code can be flagged at this stage for fixing later on; a key point here is reaorganise not re-write!
Armed with that knowledge, the next step is to write a test specification for the various parts of the application, based on the new design of the modules. That means, lots of tests and test methods (using whatever framework you like, MSTest or xUnit, etc). You really can't avoid this but remember, one module at a time!
As DanielMann pointed out, it might be worth looking at something like Specflow that will let you write test specifications in a natural(ish) language form - you may even be able to get he stakeholders on board to help write the tests!
You don't have to have literally every detail specified at first; once you have identified the major "business units" in terms of logic, you can break them down into smaller and smaller chunks of conceptual behaviour
So you may end up with tests like (just an example)
LoginModule_WhenPasswordIsWrong_RedirectswithErrorMessage()
{
//write some code in here that exercises the LoginModule and assert that it behaves as expected
//The really important thing is to write these tests based on the NEW design
//and NOT the existing system.
Assert.Fail("Write the test!");
}
Now, the key thing here - most, if not all of these tests, will not even compile and even the few that do, will probably fail. That's actually a good thing! Because now you have a clear path of what you have to do - which is to make those tests pass by implementing the new design. Best to do this in a branch of the original!
So in the example above, you might not even have a clearly defined login "module" - the code might be scattered across several pages and classes. But by writing your "ideal" tests up-front, based on an ideal design, you now have a target to aim for. And also you don't have to be totally purist about it - there is no sin in bending the rules and making some tests less granular than the ideal case - you can come back and do that later.
- Rinse and repeat - every system you do, is one less to do tomorrow!
- Once your initial set of test methods is passing, you can then "zoom in" and start refining them, in the process fixing bugs and crappy code (the same thing, in many respects :) you came across earlier.
Best of luck with it!