I write tests for methods that should exist, to test that the code does what it is expected of it. I also test the InstanceOf() the class (and inherited class definitions) to ensure the object did create what was suppose to be created. If FOO() extends BAR(), then I test that my object created is an InstanceOf(FOO) and InstanceOf(BAR). If the class gets changed to inherit from something else, or has the extends removed, my test will once again inform the developer to check the code to ensure this change is desired. Potentially some inherited functions are being called on FOO, and without the extends from BAR, this code will break.
I write tests on the various code paths that are there to be executed. Therefore, if I expect that the function should throw an exception when bad data is passed, I write a test for that. I do this to also help document our source code. The tests show the expected behavior. If someone removes the exception (new functionality to accept a different parameter type) then the test should be updated to show that this is allowed. Potentially, the change to the parameters could cause a problem elsewhere. Testing this way ensures that years later, I know that code required this to be a number, and I should definitely re-factor the code carefully if I am changing the parameter types.
Using Test Driven Development (TDD) may cause you to not write the code to throw the exception, as you write a test, then the code to make the test pass. As such, you might not be testing all the parameters and their types or values, but I try to do this as best as I can to validate reasonable data input to try to avoid the Garbage In/Garbage Out (GIGO) problem.
All these tests also give me a good code coverage metric, as the majority of the code base is tested, and the code does step through all the lines in the class files. However, testing to this level, and trying to achieve a high code coverage metric, is really a choice for your team to make if this is desired or not.