It is often said that开发者_开发百科 interfaces make mocking and unit testing an easier process. How do interfaces help with this?
It is the nature of the Interfaces to provide many implementations, thus enable mocking.
Especially in integration testing you are able to give your version of a dependency system mock-up (e.g. a web-service). Instead of actually calling a dependent system or even a module, or a complicated and difficult to instantiate type, you can provide a simplest interface implementation that will provide results needed for the unit test to complete correctly.
In addition to that, when you use in unit testing, an actual dependent type (call it BigGraph) hiding a complicated object model behind it, you are in fact do integration testing not unit testing. Your test can easily break if there is a bug in any of the dependent types (BigGraph), not the type you're testing, thus not unit-testing. Using mock-ups reduce risk of that happening.
I've seen many continuous integration systems showing dozens of errors for one bug, when they should show up one, or at most a couple, all because of too complicated object models, and incorrectly written unit-test - not using mock-ups.
Today mocking frameworks are more sophisticated (bytecode modification, etc.) than the old days, so sometimes interfaces or even virtual methods aren't always needed, but nerveless interfaces enable them.
Interfaces will not help if your object model is too complicated and cluttered (e.g. your interface relies heavily on other types/interfaces); then implementing/mocking all this is a pain.
If you have a class, you can have lots of dependencies like
Insane constructors (lots of arguments, or it needs some other class which needs a third class which needs a fourth class which needs to a valid database connection)
To use the class in any way, you must initialize it correctly (for example, you must pass it some other objects that must be valid, too)
The class has a state. This state can change during the test for some reason.
The class might have or use static fields
These dependencies are usually irrelevant during many of your tests and you'd rather not have to deal with them.
With an interface, you can create a simple mock class which just implements the few methods you need. Most mocking frameworks have built-in support for this. "Implement" here usually means "return a fixed value". This way, you can quickly build the environment that the class under test needs.
So for example, if your class needs to read records from a database, you can instead mock a ResultSet
which just returns the rows. Hence you don't have to have a real database, you don't need to create a connection (which is slow and can fail for many reasons), you don't have to care about the data in the database (so you don't have to delete/drop tables and fill them again with test data), etc.
If you have several objects that have a different implementation but offer the same methods to the outside sharing the same interfaces enables you to write one unit test and run it against all the implementations.
If you have a class that says posts stuff to web back end and then retrieves an answer unit testing that would be problematic, because a failed internet connection could let your test fail. Therefore you define an interface for this class and then you can write a second implementation that logs the stuff that should be send and delivers the correct answer. This way you can test the classes that work with the answer from the back end without relying on a internet connection during test run time.
If you are not bound to a concrete implementation, you can switch behaviors behind an interface.
If your classes implement interfaces, behaviors can be mocked.
For example you do not need to spam all your customers in your database to test if your mail notification algorithm works. You will create a mock for your IMailSender
interface and only count the number of emails sent. Then you will test the actual implementation that is actually sending the emails on a single email address and you know that the whole notification process works.
In this particular example, the test uses a mock implementing IMailSender
that only counts the emails sent and your actual production code will use an implementation implementing IMailSender
that actually sends the emails via SMTP server.
When testing behaviour that crosses a port, having an interface for the adapter will help to be able to inject a different implementation during testing (i.e. a test double).
For example, a DAO that calls a database or web service could be replaced by an implementation that returns stubbed data.
精彩评论