I'm not sure I agree with the previous answer so I'm going to attempt my own. I think the point the the previous poster is trying to make is that you should focus on code quality and testability rather than following these as hard and fast rules. What I'd add to that is that just in manual QA, when writing automated tests (such as unit tests, integration tests, etc) the overall code quality and test strategy is often more important than the bullet points.
What you've found is a series of guidelines which you can implement in your code to make it more testable. I think this is actually coming at the problem backwards - think about your tests, THEN think about your code. This is known as Test Driven Development or TDD.
Let's talk about why we write automated tests for our code at all. We write them because we don't want our methods to change behaviour unexpectedly. Go back to your SOLID Principles and think about "O" - we want our code to be "Open to Extension but Closed to Changed"
The Open/Closed principal encourages us to write code which can be extended without needing to change existing code. This is because changing the existing of code which is already in production brings risk with it, one strategy to mitigate this risk and prevent the introduction of bugs is to test our code (either manually or in an automated manner) and one of the most popular methods of doing this is with Unit Tests.
Unit Tests break your code into it's smallest units and test the behaviour of each method/operation in isolation. If the functionality of each method remains unchanged then the larger application will not change unexpectedly and fewer bugs should be introduced.
However, Unit Tests are not going at detecting bugs in code - they only help to enforce this Open/Closed principal and prevent bugs being introduced into already working code. That's why Unit Tests should form part of your overall test strategy, they're not a silver bullet.
Having established the value of Unit Tests let's talk about TDD.
My main issue with the bullet points above is that they imply that you're writing code which can then be tested at a later date, a far better approach is to:
- Create a method stub which contains the signature and throws something like a NotImplementedException
- Create a test class which represents this method
- Create tests which "document" what the method does in each scenario, this will include scenarios like the happy path, passing in nulls, passing in out of range values, and potentially internal errors
- Implement your method and ensure all the tests pass
Creating a test to pass in a null is fairly easy:
public void An_exception_is_thrown_when_null_is_supplied()
var myClass = new MyClass();
Assert.Throws<ArgumentNullException>(() => myClass.DoSomething(null));
However testing what will happen if there's an error inside your class (for example accessing the database) is where things get a little more complicated and that's where mocks and stubs come in. Following a dependency injection (or DI pattern) we can write a test which says:
public void An_exception_is_thrown_when_the_database_cannot_be_contacted()
var myDataAccess = new Mock<IDataAccess>();
myDataAccess.Setup(x => ConnectToDatabase()).Thows<InvalidOperationException>());
var myClass = new MyClass(myDataAccess.Object);
Assert.Throws<InvalidOperationException>(() => myClass.DoSomething("user1"));
Now I've waffled and built up some background let's look at your first bullet point (the one you're asking about).
Public methods virtual if not using interfaces -- makes mocking easier
In our implementation we can assume MyClass looks something like this:
public class MyClass
public MyClass(IDataAccess dataAccess)
// Null check dataAccess and assign to an internal variable somewhere
However, what if (for whatever reason) you can't pass in the dependency?
What if your MyClass implementation looks like this:
public class MyClass
public void DoSomething(string username)
throw new NotImplementedException();
public void ConnectToDatabase()
// connect to the database
Our mocking libraries are going to find it VERY hard to simulate an error when connecting to the database. However some mocking libraries allow you to mock virtual methods by returning a class which inherits from your class and overrides the virtual methods. This would mean you could mock out the ConnectToDatabase() method forcing it to throw an exception for you. However the mocking library will not be able to do this if if the class is sealed (the last bullet point) or if the methods are not virtual.
Hopefully that helps explain why your bullet list isn't ideal, the rational for the first bullet point and how I'd do things instead?
Making methods virtual helps mocking libraries override them if DI cannot be used.