Obviously in this example the property is as simple as it possibly could be,
Just to get it out of the way: trivially readable code does not require testing. When you test down to this level, you are effectively testing the framework/language you're using, which is not your responsibility to test. This is effectlively as useful as Assert.AreEqual(1+1, 2)
, which is to say that it isn't useful testing.
but in a class when a setter is actually performing business logic, can/should the tests also rely on the getter? What if they're both performing business logic?
I'm torn between the idea that a test should only test a single piece of functionality
You're correct, but it's a bit counterintuitive that you hold to this notion while you are also implementing a property's getter and setter with complex and non-abstracted (therefore not separately testable) business logic.
As I said, trivially readable code should not be tested. If you follow that, then your question implies that your getter and setter are both implementing non-trivial logic.
I think you're stacking way too much logical complexity in this property's getters and setters. You're noticing this now because it reduces testability, but it should've thrown up a red flag earlier than that.
Think of it this way: by having complex logic in both the getter and setter, you are effectively doing a secret conversion of your data. For example:
public class Foo
{
private string mySecretValue;
public int MyInt
{
get { return Convert.ToInt32(mySecretValue); }
set { mySecretValue = value.ToString(); }
}
}
This means that anyone who uses this object doesn't need to care about this secret conversion. As long as the conversion is symmetrical, the test should always pass, even if I change the conversion logic. For example, let's say I have the following test:
var foo = new Foo() { MyInt = 123 };
Assert.AreEqual(123, foo.MyInt);
This test would currently pass. And suppose I change my code:
public class Foo
{
private string mySecretValue;
public int MyInt
{
get { return Convert.ToInt32(mySecretValue).Replace("ZERO", 0); }
set { mySecretValue = value.ToString().Replace(0, "ZERO"); }
}
}
The test should still pass, because the consumer of the Foo
object does not care about the conversion logic. It only cares about the fact that the conversion is symmetrical, which it still is.
Never forget that tests only care about external behavior. Private logic is by definition private and therefore not needed to be shared outside of the class (which means the test itself cannot rely on knowing a class' private logic).
It shouldn't matter what happens under the hood. As long as it walks like a duck, floats like a duck, and quacks like a duck, it's a duck. Even if under the hood (i.e. in the private logic) it's just a team of miniature people operating a duck-suit.
Should I be testing complex accessors with reflection and testing simpler ones together?
The long and short of it is that you shouldn't have complex accessors. If a bit of logic is less than trivial (and thus valid to test), it should be abstracted into a method of its own. The property can still call the method, and you can write a test which tests that method, without actually needing to worry about the actual property itself.
Besides, who says that this logic wouldn't be useful in more than one property? It makes no sense to make this non-trivial logic not reusable by only keeping it in the property's get/set accessor.
But what if the simple ones change down the road?
As a general rule of thumb, when things change, then you write tests for the no-longer-simple things.
It's a pitfall to think that you should write all of your tests now so they can be used in perpetuity without any further changes or expansions.
Codebases evolve, complexities appear and disappear, and your test suite needs to adapt to these changes as well. Otherwise, your tests are growing apart from the codebase they're supposed to be testing.