问题
I've recently got a task to create a simple utility that allows to import data from a file with special format to the database. I've implemented console application with few classes(Program class operates with business logic class, business logic class in turn operates with data access class). Everything works ok, but now I'm thinking about creating some unit tests and refactoring application (I have not created real unit tests before, just a bunch of integration tests a long time ago, so I believe this application is perfect field for practicing).
So, here is the problem: the data access class has been made static, this doesn't allow to mock it and as a result create real unit tests. To fix this I need to create an interface and implement it in the data access class. Also I will have to add a constructor to the business logic class that will accept parameter of that interface type. So this means that I will end up creating data access class in the application Main() method and something tells me this not the best approach (is it really ok that the entry point should know about some data access things? what if the chain is much longer or there should be several chains?). I know I can use some IoC container, but I think this is too simple application to use containers.
Thanks!
回答1:
I need to create an interface and implement it in the data access class. Also I will have to add a constructor to the business logic class that will accept parameter of that interface type. So this means that I will end up creating data access class in the application Main() method and something tells me this not the best approach (is it really ok that the entry point should know about some data access things? what if the chain is much longer or there should be several chains?)
On the contrary! This is the best approach, at least from a testability perspective.
The only way to make your business logic layer testable is to isolate it from your data access layer by doing exactly what you're contemplating.
Your top-level application is where the buck stops - it's the only component that should need to know what the concrete data access class is.
If the chain is much longer or there are several chains, that's no big deal (though you may want to consider collapsing some application layers if it gets out of hand). Consider this potential code in a Model-View-Presenter
app's View
, where the Presenter
has a dependency on a CustomerService
, which has a dependency on a Repository
and a dependency on an AccountingService
(which is also dependent on the Repository
):
public CustomerView() {
IRespository repository = new ConcreteRepository();
IAccountingService accountingService = new ConcreteAccountingService(repository);
ICustomerService customerService = new ConcreteCustomerService(accountingService, repository)
this._Presenter = new CustomerPresenter(customerService);
}
Finally, there's no need to use a dependency injection container if you don't want to (though some of them are surprisingly lightweight) - dependency injection by hand works fine until you start repeating yourself all over the place (or find you want to configure the dependencies at runtime).
回答2:
Assuming that you are using LINQ to SQL, maybe you could use the repository pattern to wrap the DataContext into an interface that you can later mock, thus making unit testing possible.
There are some articles about this subject around the internet, here is one: http://andrewtokeley.net/archive/2008/07/06/mocking-linq-to-sql-datacontext.aspx
回答3:
Here is a simple solution: Instead of calling you data access class directly, use helper methods:
public void insert (...) {
DataAccess.insert (...);
}
Now you can override these calls. I suggest to split the tests like so:
Create a couple of tests which make sure that
DataAccess
does the right thing when it gets the right parameters.In the mockup tests, simply collect the parameters sent to
insert()
. Don't callDataAccess
at all.
The tests in #1 will make sure that writing the data into the DB will work while the tests in #2 will make sure that you call DataAccess
with the correct values. The latter tests will run very quickly, which will make it easy to test special cases, etc.
You don't need to run the tests from #1 all the time, either. Only when you change something in DataAccess
or before a release. This will make testing efficient and pleasant.
来源:https://stackoverflow.com/questions/1454859/data-access-unit-testing-dependency-injection