However, as long as you trust that Liskov's Substitution Principle will be followed, then why would you not allow it to be overriden?
For example, because I want the skeleton implementation of an algorithm to be fixed, and only allow specific parts to be (re)defined by subclasses. This is widely known as the Template Method pattern (emphasis below by me):
The template method thus manages the larger picture of task semantics, and more refined implementation details of selection and sequence of methods. This larger picture calls abstract and non-abstract methods for the task at hand. The non-abstract methods are completely controlled by the template method but the abstract methods, implemented in subclasses, provide the pattern's expressive power and degree of freedom. Some or all of the abstract methods can be specialized in a subclass, allowing the writer of the subclass to provide particular behavior with minimal modifications to the larger semantics. The template method (which is non-abstract) remains unchanged in this pattern, ensuring that the subordinate non-abstract methods and abstract methods are called in the originally-intended sequence.
Update
Some concrete examples of projects I have been working on:
- communicating with a legacy mainframe system via various "screens". Each screen has a bunch of fields, of fixed name, position and length, containing specific data bits. A request fills up certain fields with specific data. A response returns data in one or more other fields. Each transaction follows the same basic logic, but the details are different on every screen. We used Template Method in several different projects to implement the fixed skeleton of the screen handling logic, while allowing subclasses to define the screen-specific details.
- exporting / importing configuration data in DB tables to/from Excel files. Again, the basic schema of processing an Excel file and inserting/updating DB records, or dumping the records to Excel is the same for each table, but the details of each table are different. So Template Method is a very obvious choice to eliminate code duplications and make the code easier to understand and maintain.
- Generating PDF documents. Each document has the same structure, but their content is different each time, depending on lots of factors. Again, Template Method makes it easy to separate the fixed skeleton of the generation algorithm from the case-specific, changeable details. In fact. it even applies on multiple levels here, as the document consists of several sections, each of which consists of zero or more fields. Template Method is applied on 3 distinct levels here.
In the first two cases, the original legacy implementation used Strategy, resulting in lots of duplicated code, which of course over the years grew subtle differences here and there, contained lots of duplicated or slightly different bugs, and was very difficult to maintain. Refactoring to Template Method (and some other enhancements, like using Java annotations) reduced code size by about 40-70%.
These are only the most recent examples which come to my mind. I could cite more cases from almost every project I have been working on so far.
Perspective:
So let's take a step back and ask what TDD is trying to help us with. TDD is trying to help us determine if our code is correct or not. And by correct, I mean "does the code meet the business requirements?" The selling point is that we know changes will be required in the future, and we want to make sure that our code remains correct after we make those changes.
I bring that perspective up because I think it's easy to get lost in the details and lose sight of what we're trying to achieve.
Principles - SAP:
While I'm not an expert in TDD, I think you're missing part of what Single Assertion Principle (SAP) is trying to teach. SAP can be restated as "test one thing at a time." But TOTAT doesn't roll off the tongue as easily as SAP does.
Testing one thing at a time means that you focus on one case; one path; one boundary condition; one error case; one whatever per test. And the driving idea behind that is you need to know what broke when the test case fails, so you can resolve the issue more quickly. If you test multiple conditions (ie. more than one thing) within a test and the test fails, then you have a lot more work on your hands. You first have to identify which of the multiple cases failed and then figure out why that case failed.
If you test one thing at a time, your search scope is a lot smaller and the defect is identified more quickly. Keep in mind that "test one thing at a time" doesn't necessarily exclude you from looking at more than one process output at a time. For example, when testing a "known good path", I may expect to see a specific, resulting value in foo
as well as another value in bar
and I may verify that foo != bar
as part of my test. The key is to logically group the output checks based upon the case being tested.
Principles - PMP:
Likewise, I think you're missing a bit about what Private Method Principle (PMP) has to teach us. PMP encourages us to treat the system like a black box. For a given input, you should get a given output. You don't care how the black box generates the output. You only care that your outputs align with your inputs.
PMP is really good perspective for looking at the API aspects of your code. It can also help you scope what you have to test. Identify your interface points and verify that they meet the terms of their contracts. You don't need to care about how the behind-the-interface (aka private) methods do their job. You just need to verify they did what they were supposed to do.
Applied TDD (for you)
So your situation presents a bit of a wrinkle beyond an ordinary application. Your app's methods are stateful, so their output is contingent upon not only the input but also what's been previously done. I'm sure I should <insert some lecture>
here about state being horrible and blah blah blah, but that really doesn't help solve your problem.
I'm going to assume you have some sort of state diagram table that shows the various potential states and what needs to be done in order to trigger a transition. If you don't, you're going to need it as it will help express the business requirements for this system.
The Tests: First, you're going to end up with a set of tests that enact state change. Ideally, you'll have tests that exercise the full range of state changes that can occur but I can see a few scenarios where you may not need to go that full extent.
Next, you need to build tests to validate the data processing. Some of those state tests will be reused when you create the data processing tests. For example, suppose you have a method Foo()
that has different outputs based upon an Init
and State1
states. You'll want to use your ChangeFooToState1
test as a setup step in order to test the output when "Foo()
is in State1
".
There's some implications behind that approach that I want to mention. Spoiler, this is where I'll infuriate the purists
First off, you have to accept that you using something as a test in one situation and a setup in another situation. On the one hand, this seems to be a direct violation of SAP. But if you logically frame ChangeFooToState1
as having two purposes then you're still meeting the spirit of what SAP is teaching us. When you need to make sure Foo()
changes states, then you use ChangeFooToState1
as a test. And when need to validate "Foo()
's output when in State1
" then you're using ChangeFooToState1
as a setup.
The second item is that from a practical point of view, you're not going to want fully randomized unit testing for your system. You ought to run all of the state change tests prior to running the output validation tests. SAP is kind of the guiding principle behind that ordering. To state what should be obvious - you can't use something as setup if it fails as a test.
Putting it together:
Using your state diagram, you'll generate tests to cover the transitions. Again, using your diagram, you generate tests to cover all of the input / output data processing cases driven by state.
If you follow that approach, the bloated, complicated, long, and difficult to write
tests should get a little bit easier to manage. In general, they should end up smaller and they should be more concise (ie less complicated). You should notice that the tests are more decoupled or modular as well.
Now, I'm not saying the process will be completely pain free because writing good tests does take some effort. And some of them will still be difficult because you're mapping a second parameter (state) over quite a few of your cases. And as an aside, it should be a little more apparent why a stateless system is a easier to build tests for. But if you adapt this approach for your application, you should find that you are able to prove your application is working correctly.
Best Answer
Fabio touched on it in his comment - it is perfectly legit to start using a dependency in a test before any interface or implementation code exists for it.
When you feel that your object under test needs indirect input from somewhere else, or that you need to test indirect outputs of your object, start typing your stubbing or mocking code as if those external dependencies existed. Then use IDE productivity features to generate the interfaces and their method signatures.
This is called "programming by wishful thinking" and is used a lot in outside-in TDD.
Your question makes less sense in that context, since you wouldn't create any new interface or any method inside an existing interface without mocking them first.
Another context is when you create dependencies by extracting new classes during the Refactor step of the TDD cycle and, in a more "Classicist" style, decide not to test them with mocks.
Here, the answer to your question
Should all class public methods come from an interface?
is clearly: "no". You can perfectly have a TDD-written test that tests objects A and B without either of them implementing an interface.However, I would use this approach only when the extracted dependency is in the same layer as the object under test, not at application boundaries/seams. Having interfaces at architectural boundaries is often more valuable because it is usually where you get the most fragility, the least performance, the most complex configuration, and the highest likelihood of needing multiple implementations of the same abstraction.