質問

I am trying to make my first project that can be unit tested. And it is amazing how I have to rewire my brain of some vicious coding styles.

This article got me the attention that Singletons are pathological liars

I am not trying to be radical on that, but I am used to an artifact that I am not sure how can i get rid of it

example:

initialization
  ModelFactory.RegisterFactoryMethod('standard.contasmovimento',
    function(AParam: Variant) : TModel
    begin
      result := TModelContasMovimento.Create(AParam);
    end);

end.

ModelFactory is a singleton, defined on its unit and part of the uses clause of this unit.

In my MVP structure I define each of the Models, Views and Presenters in its own unit (1 class 1 unit). All these units are available to be used, according the needs of each project. So, I use it like a parts catalog, according the project I add the units to the project and it gets automatically registered and ready to be used from the factories.

To solve the singleton problem i was thinking in move them to a framework class, so I could create the object at one point and then could use dependency injection to pass the framework object. All the factories and other environment stuff are sit there:

TMyFramework = class
  FModelFactory: IModelFactory;
  FViewFactory: IViewFactory;
  FPresenterFactory: IPresenterFactory;

  property ModeFactory: IModelFactory read FModelFactory;
  ...

My ideia is remove the singletons in a way that I can mock them in a test unit. With singletons in place I cant remove them easily for testing.

But that will make me loose the automatic initialization of each unit, an I rely on that to add the units. I don't want to manually create a list of available classes.

Is there a way to solve this situation?

役に立ちましたか?

解決

For unit testing to get off the ground without a complete rewrite of all the singletons you have, I have found that just adding the ability to Free and re-Create the singletons is usually more than enough to get you started.

Assuming the singletons are instantiated in an initialization section (Ban those. They are the bane of any unit test. Go for separate registration and initialization units.), you can simply add two procedures to the interface section. Put them between conditional defines if you don't want other units in your normal project to use them:

{$IFDEF DUNIT}
procedure InstantiateMySingleton;
procedure FreeMySingleton;
{$ENDIF}

Move the code that you now have in your initialization and finalization sections to the implementation of these procedures and just call them from the initialization and finalization.

procedure InstantiateMySingleton;
begin
   // ...
end;

procedure FreeMySingleton;
begin
   // ...
end;

initialization
  InstantiateMySingleton;
finalization
  FreeMySingleton;

With this done you can start to use the InstantiateMySingleton and FreeMySingleton in your unit tests' setup and teardown methods.

All you have then left to do is to make sure that creating and destroying the singleton doesn't leak memory and is something that can actually be repeated with the same functional results every single time. One thing I have found that helps in ensuring this is to use the GUI runner and run the whole test suite twice (without exiting the GUI of course!). If there are tests that succeed on the first run and fail on the second run, you have problems in the initialization or finalization of your singleton.

他のヒント

It depends if you need the instance in another class or the factory to instantiate the class at a later point. In the first case you can simply pass the constructed instance as dependency. In the other case you pass the factory. In both cases you are using dependency injection instead of looking it up in some other unit ("Don't look for things but ask for things").

Of course this requires some wire up code for the dependency ("poor man's DI") or the use of a DI container.

Dependency injection really is the only way to make things testable in a clean way because you just simply pass mocks to the SUT.

In general, Misco is making the case about (not) using Singletons in other objects. It's a bit of a stretch but one could view a delphi unit as an object so the question is what to do to remove the Singleton dependency in the unit.

The easiest solution, and one I have been using for some time is to move all initialization code for a give project into one (or several) units who's only purpose is to do the initialization for your application.

As for your testcases, you are now free to ommit that unit and initialize (mock) to whatever you want.

If I look enough I am sure I can find an article describing each and every design pattern either as an anti-pattern or the devil incarnate. For example, here is article that describes the Service Locator pattern as an anti-pattern:

http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/

I have seen comments describing factory patterns as anti-patterns.

I have often used singletons in factories in the past and I have no problem testing the adapters/plugins/etc that I "load" with these factories in unit tests. Typically the code in singletons is simple and not something that I would test in itself. The code is tested enough just through normal use. All of the Dependency Injection libraries that I have seen include a singleton for their global container.

Where possible I like to configure my adapters in XML. The system itself is then completely modular and easy to test.

I am not saying that dependency injection is bad. Far from it. I like it. If you are building a system from scratch I would certainly advocate it as a good framework to design around and Spring4D is a good implementation. I am putting forward a different view point. I think that rewriting your system to try and take advantage of a newer or different technology should be your last port of call. It reminds me of the Joel Spolsky article about the Netscape rewrite of Navigator (http://www.joelonsoftware.com/articles/fog0000000069.html).

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top