In writing my several implementations of that SHA family of functions, I've become more experienced. A particularly queer aspect of implementing SHA, compared to other programming, is the debugging. I very begrudgingly used the loathsome ``print debugging'' at times, due to lacking proper familiarity with some of the available debuggers. I've since resolved to not stoop to such lowly methods again.
Using a debugger properly, there were still issues. There's very little worth in stepping through a hashing, as that intermediate state is still purposefully opaque, obfuscated, and unimportant in all other things. In some environments, there can be a decision between having the environment optimize the implementation for speed or debugging, which can interfere when a large file hashing returns the wrong result. It's certainly easiest to compare with a known good implementation, as any divergence whatsoever be telling, regardless of which half such fault stands in. Thus using a debugger is most useful in handling language-level errors as, once the implementation runs without error, even if the resulting digest be incorrect, that debugger will cease to be of much help. It's unusually tedious.
I've learned to appreciate the schemes which avoid requiring padding, as padding can be very easy to get subtly incorrect. I've found it most convenient, with most languages, to treat the padding as a piecewise function, which invites rare errors. Unlike the remainder, padding is far easier to test.
A fortunate aspect of SHA is how it's particularly easy to test before at all bothering to validate. The only way I'm even somewhat confident in my SHA implementations is by having long stared at them. The digest yielding from this is SHA is an example of that which simply must be known to be correct.