Laden...

XUnit-Attribut erstellen, was den SimpleInjector benutzt

Erstellt von Blaster vor 7 Jahren Letzter Beitrag vor 7 Jahren 3.093 Views
B
Blaster Themenstarter:in
66 Beiträge seit 2013
vor 7 Jahren
XUnit-Attribut erstellen, was den SimpleInjector benutzt

Hallo!
Es ist mir wirklch peinlich, mag am Kater liegen, aber irgendwie bekomme ich meine Gedanken nicht geordniet.
Könntet Ihr mir vielleicht kurz helfen?

Ich versuche folgenden Code anzupassen, der für Unity mit XUnit geschrieben wurde, für Simple Injector umzuschreiben:
Unit Testing and Dependency Injection, with xUnit InlineData and Unity

Das Problem ist: Unity kennt ein IUnityContainer, um den Container als Interface zu verwenden, aber Simple Injector nicht:
IUnityContainer Members
https://simpleinjector.org/ReferenceLibrary/?topic=html/T_SimpleInjector_Container.htm

Ziel ist es, dass was Tom DuPont in seinem Code als

[[UnityInlineData("My", 1, true)]

festgelegt hatte, in ein

[[SInjectorInlineData("My", 1, true)]

Attribute umzuwandeln, das mit jetzt Simple Injector läuft.

Also alles was mir bisher einfiel ist ein wenig öhmm unqualifiziert:


public interface ISimpleInjector
    {
        Container newContainer { get; set; }      
    } // Interface als Property ?!? Hmmm! ...

 public class StrategyTests : ISimpleInjector
    {

// Static Interface und Ctor irgendwie Käse
        public static readonly ISimpleInjector Container;
        static StrategyTests()
        {

            // var mContainer = new Container(); // var Type ist SimpleInjector  StrategyTest.Container
             
            IocInlineDataResolver<Container> ... // wird problemlos erkannt!

            // Auf die Memberfunktionen kann ich überhaupt nicht zugreifen
            newContainer.RegisterType<MyStrategy>(); // wird nicht erkannt
            Container.RegisterType<YourStrategy>(); // wird nicht erkannt
...
        }

// Ineterface Implemetierung ist auch irgendwie Käse
        public Container newContainer
        {
            get
            {
//             ???   wie ich den Container abhole ist mir im Moment auch nicht klar.
                throw new NotImplementedException();
            }

            set
            {
                value = new Container(); // soll später als Singleton abgeholt werden. Bildung funzt aber.
                throw new NotImplementedException();
            }
        }

Was immer ich heute code ist Blödsinn! 😭
Habt Ihr ein paar Stupser in die richtige Richtung für mich? - Danke!

Outsider:
https://simpleinjector.readthedocs.io/en/latest/quickstart.html
http://xunit.github.io/
https://www.microsoft.com/en-us/download/confirmation.aspx?id=39944

B
Blaster Themenstarter:in
66 Beiträge seit 2013
vor 7 Jahren
D
985 Beiträge seit 2014
vor 7 Jahren

Wieso sollte es eine dumme Idee sein? Bis auf die Tatsache, dass man hier auch ganz schnell zu einem Integration-Test verfallen kann, der zwar auch wichtig aber eben kein Unit-Test ist.

Anyway, hier mal ein ganz billiger Container


namespace WhateverIoc
{
    public interface IServiceLocator
    {
        object GetService( Type type, string name = "" );
        object[] GetAllServices( Type type );
    }

    public class ServiceLocator : IServiceLocator
    {
        private readonly Dictionary<Type, Dictionary<string, Func<object>>> _servicemap = new Dictionary<Type, Dictionary<string, Func<object>>>( );

        public void RegisterType( Type type, Func<object> factory, string name = "" )
        {
            if ( !_servicemap.ContainsKey( type ) )
            {
                _servicemap.Add( type, new Dictionary<string, Func<object>>( ) );
            }
            _servicemap[ type ].Add( name, factory );
        }

        object[] IServiceLocator.GetAllServices( Type type )
        {
            return _servicemap[ type ].Select( e => e.Value( ) ).ToArray( );
        }

        object IServiceLocator.GetService( Type type, string name )
        {
            try
            {
                return _servicemap[ type ][ name ]( );
            }
            catch ( Exception ex )
            {
                throw new InvalidOperationException( $"GetService( {type.Name}, {name} )", ex );
            }
        }
    }
}

und das dazugehörige DataAttribute


namespace Xunit.IoC.WhateverIoC
{
    public class WhateverDataAttribute : DataAttribute
    {
        private readonly object[] _args;
        private readonly WhateverIocInlineDataResolver _dataResolver;

        public WhateverDataAttribute( params object[] args )
        {
            _args = args;
            _dataResolver = new WhateverIocInlineDataResolver( );
        }

        public override IEnumerable<object[]> GetData( MethodInfo testMethod )
        {
            return _dataResolver.GetData(
                testMethod,
                testMethod.GetParameters( ).Select( e => e.ParameterType ).ToArray( ),
                _args );
        }

        public class WhateverIocInlineDataResolver : IocInlineDataResolver<IServiceLocator>
        {
            protected override object Resolve( IServiceLocator container, Type type )
            {
                return container.GetService( type );
            }

            protected override IEnumerable<object> ResolveAll( IServiceLocator container, Type type )
            {
                return container.GetAllServices( type );
            }

            protected override object ResolveByName( IServiceLocator container, Type type, string name )
            {
                return container.GetService( type, name );
            }
        }
    }
}

und die Test-Klasse


namespace FooLibrary.Tests
{
    using WhateverIoc;
    using Xunit;
    using Xunit.IoC.WhateverIoC;

    public class StrategyTests
    {
        public static readonly IServiceLocator Container;

        static StrategyTests()
        {
            var sl = new ServiceLocator( );
            sl.RegisterType( typeof( IStrategy ), () => new MyStrategy( ), "My" );
            sl.RegisterType( typeof( IStrategy ), () => new YourStrategy( ), "Your" );
            Container = sl;
        }

        [Theory]
        [WhateverData( "My", 1, true )]
        [WhateverData( "My", 2, true )]
        [WhateverData( "My", 3, false )]
        [WhateverData( "Your", 1, true )]
        [WhateverData( "Your", 2, false )]
        [WhateverData( "Your", 3, true )]
        public void BetterIsValid( IStrategy strategy, int value, bool expected )
        {
            var actual = strategy.IsValid( value );
            Assert.Equal( expected, actual );
        }


    }
}

B
Blaster Themenstarter:in
66 Beiträge seit 2013
vor 7 Jahren
D
985 Beiträge seit 2014
vor 7 Jahren

Diese Aussagen verstehe ich jetzt nicht.

Ich bin von keinem IoC-Container abhängig auch nicht von dem IServiceLocator.
Ich habe einfach ein Interface IServiceLocator deklariert und eine Minimal-Implementierung desselbigen Interfaces erstellt.

Niemand kann mich daran hindern dieses Interface mit einem Wrapper für jeden anderen IoC-Container meiner Wahl zu implementieren.

Ich kann das Interface auch mocken.

Generell könnte man dieses Attribut also auch ohne Generics direkt an dieses eine Interface hängen. Den Container mockt man oder wrappt einen Container der Wahl oder schreibt sich für den Test eine eigene Implementierung.

B
Blaster Themenstarter:in
66 Beiträge seit 2013
vor 7 Jahren

Ok!

"Ein System versteht man erst dann, wenn man versucht es zu verändern!" (Kurt Lewin)

Ich probiere es mal aus!

D
985 Beiträge seit 2014
vor 7 Jahren

Hier mal der etwas überarbeitete Vorschlag


using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xunit.Sdk;

namespace Xunit.IoC
{
    public abstract class IocDataResolver<TContainer>
        where TContainer : class
    {
        public const string DefaultContainerName = "Container";

        public IocDataResolver( string containerName )
        {
            ContainerName = containerName ?? DefaultContainerName;
        }

        public string ContainerName { get; }

        public IEnumerable<object[]> GetData( MethodInfo methodUnderTest, object[] args )
        {
            Type[] parameterTypes = methodUnderTest.GetParameters( ).Select( e => e.ParameterType ).ToArray( );

            if ( args == null )
                args = new object[ parameterTypes.Length ];

            if ( args.Length != parameterTypes.Length )
                throw new InvalidOperationException( "Parameter count does not match Argument count" );

            var container = GetContainer( methodUnderTest );

            var map = new Dictionary<int, IEnumerable<object>>( );

            for ( var i = 0; i < args.Length; i++ )
            {
                var arg = args[ i ];
                var paramType = parameterTypes[ i ];

                if ( arg != null )
                {
                    var argType = arg.GetType( );
                    if ( argType == paramType )
                        continue;
                }

                var underlyingType = Nullable.GetUnderlyingType( paramType );
                if ( underlyingType != null )
                    continue;

                var typeArg = arg as Type;
                if ( typeArg != null )
                {
                    var resolveByTypeArg = Resolve( container, typeArg );
                    if ( resolveByTypeArg != null )
                    {
                        map[ i ] = new[] { resolveByTypeArg };
                        continue;
                    }
                }

                var stringArg = arg as String;
                if ( stringArg != null )
                {
                    var resolveByName = ResolveByName( container, paramType, stringArg );
                    if ( resolveByName != null )
                    {
                        map[ i ] = new[] { resolveByName };
                        continue;
                    }
                }

                var resolveAll = ResolveAll( container, paramType );
                if ( resolveAll != null )
                {
                    map[ i ] = resolveAll;
                    continue;
                }

                var resolveByParamType = Resolve( container, paramType );
                if ( resolveByParamType != null )
                {
                    map[ i ] = new[] { resolveByParamType };
                    continue;
                }

                throw new InvalidOperationException( "Unable to resolve type: " + paramType.Name );
            }

            IEnumerable<object[]> results = new[] { args };
            foreach ( var pair in map )
            {
                var index = pair.Key;
                var values = pair.Value;
                results = results.SelectMany( r => ReplaceAndExpand( r, index, values ) );
            }

            return results;
        }

        private TContainer GetContainer( MethodInfo methodUnderTest )
        {
            TContainer value = null;

            var field = methodUnderTest.DeclaringType.GetField( ContainerName );
            if ( field != null )
                value = field.GetValue( null ) as TContainer;

            if ( value != null )
                return value;

            var property = methodUnderTest.DeclaringType.GetProperty( ContainerName );
            if ( property != null )
                value = property.GetValue( null ) as TContainer;

            if ( value != null )
                return value;

            throw new InvalidOperationException( "The test class must have a public Property or Field named " + ContainerName );
        }

        private static IEnumerable<T[]> ReplaceAndExpand<T>( IEnumerable<T> source, int index, IEnumerable<T> values )
        {
            foreach ( var value in values )
            {
                var list = source.ToArray( );
                if ( index > -1 )
                    list[ index ] = value;
                yield return list;
            }
        }

        protected abstract object Resolve( TContainer container, Type type );

        protected abstract object ResolveByName( TContainer container, Type type, string name );

        protected abstract IEnumerable<object> ResolveAll( TContainer container, Type type );
    }

    public interface IIocContainer
    {
        object Resolve( Type type );
        object ResolveByName( Type type, string name );
        IEnumerable<object> ResolveAll( Type type );
    }

    public class IocDataAttribute : DataAttribute
    {
        private readonly object[] _args;

        public IocDataAttribute( params object[] args )
        {
            _args = args;
        }

        public string ContainerName { get; set; }

        public override IEnumerable<object[]> GetData( MethodInfo testMethod )
        {
            var resolver = new IocContainerDataResolver( ContainerName );
            return resolver.GetData( testMethod, _args );
        }

        public class IocContainerDataResolver : IocDataResolver<IIocContainer>
        {
            public IocContainerDataResolver( string containerName ) : base( containerName )
            {
            }

            protected override object Resolve( IIocContainer container, Type type )
            {
                return container.Resolve( type );
            }

            protected override IEnumerable<object> ResolveAll( IIocContainer container, Type type )
            {
                return container.ResolveAll( type );
            }

            protected override object ResolveByName( IIocContainer container, Type type, string name )
            {
                return container.ResolveByName( type, name );
            }
        }
    }

}

und die Testklasse


using System;
using System.Collections.Generic;

namespace FooLibrary.Tests
{
    using WhateverIoc;
    using Xunit;
    using Xunit.IoC;

    public class WhateverIocWrapper : IIocContainer
    {
        private IServiceLocator _services;

        public WhateverIocWrapper( IServiceLocator services )
        {
            if ( services == null )
                throw new ArgumentNullException( nameof( services ) );
            _services = services;
        }

        public object Resolve( Type type )
        {
            return _services.GetService( type );
        }

        public IEnumerable<object> ResolveAll( Type type )
        {
            return _services.GetAllServices( type );
        }

        public object ResolveByName( Type type, string name )
        {
            return _services.GetService( type, name );
        }
    }

    public class StrategyTests
    {
        public static readonly IIocContainer Container;
        public static readonly IIocContainer OtherContainer;

        static StrategyTests()
        {
            Container = new WhateverIocWrapper(
                new ServiceLocator( )
                .RegisterType( typeof( IStrategy ), () => new MyStrategy( ), "My" )
                .RegisterType( typeof( IStrategy ), () => new YourStrategy( ), "Your" ) );
            OtherContainer = new WhateverIocWrapper(
                new ServiceLocator( )
                .RegisterType( typeof( IStrategy ), () => new MyStrategy( ), "Own" )
                .RegisterType( typeof( IStrategy ), () => new YourStrategy( ), "Other" ) );
        }

        [Theory]
        [IocData( "My", 1, true )]
        [IocData( "My", 2, true )]
        [IocData( "My", 3, false )]
        [IocData( "Your", 1, true )]
        [IocData( "Your", 2, false )]
        [IocData( "Your", 3, true )]
        [IocData( "Own", 1, true, ContainerName = nameof( OtherContainer ) )]
        [IocData( "Own", 2, true, ContainerName = nameof( OtherContainer ) )]
        [IocData( "Own", 3, false, ContainerName = nameof( OtherContainer ) )]
        [IocData( "Other", 1, true, ContainerName = nameof( OtherContainer ) )]
        [IocData( "Other", 2, false, ContainerName = nameof( OtherContainer ) )]
        [IocData( "Other", 3, true, ContainerName = nameof( OtherContainer ) )]
        public void BetterIsValid( IStrategy strategy, int value, bool expected )
        {
            var actual = strategy.IsValid( value );
            Assert.Equal( expected, actual );
        }


    }
}

B
Blaster Themenstarter:in
66 Beiträge seit 2013
vor 7 Jahren
static StrategyTests()
        {
            Container = new WhateverIocWrapper(
                new ServiceLocator( )
                .RegisterType( typeof( IStrategy ), () => new MyStrategy( ), "My" ) // den "." der nachfolgenden Memberfunnktion funktioniert nicht. 
                .RegisterType( typeof( IStrategy ), () => new YourStrategy( ), "Your" ) );
            OtherContainer = new WhateverIocWrapper(
                new ServiceLocator( )
                .RegisterType( typeof( IStrategy ), () => new MyStrategy( ), "Own" ) // den "." der nachfolgenden Memberfunnktion funktioniert nicht. 
                .RegisterType( typeof( IStrategy ), () => new YourStrategy( ), "Other" ) );
        }

Die zweite Memberfunktion akzeptiert VS 2015 nicht> Fehlermeldung:

Fehler CS0023 Der .-Operator kann nicht auf einen Operanden vom Typ "void" angewendet werden.

Ich verstehe die Semantik grundsätzlich nicht. Für mich ist dies ein Child-Member.

Trotzdem zaubert Sir Rufo in anderthalb Stunden einen IoC Absraction mit Inline Containerwechsel hin. - Respekt! 👍

Der Code funktuioniert nur bein xunit < V 2.2.
Btw,
Es existiert ein Nuget package für xunit.ioc .

D
985 Beiträge seit 2014
vor 7 Jahren

Upps, der ServiceLocator Klasse habe ich eine Mini-Fluent-API verpasst:


public ServiceLocator RegisterType(...)
{
  ...
  return this;
}

also nix Aufregendes.

Das mit dem Namespace ist ja auch heilbar 😉

B
Blaster Themenstarter:in
66 Beiträge seit 2013
vor 7 Jahren

Guten Morgen zusammen!

Sr Rufo's Code funktioniert jetzt fehlerfrei, aber in Testadapter passiert jetzt erst mal nix!

Denn wir haben noch gar keine Container bestimmt.
Für den nächatwn zwei Code Passagen, müssten wir zwei IoC Container definieren und als IServiceLocator service bestimmen und und mit einer Setter Funktion in die Schnittelle einbinden:Und dann hätte wir eine AOP und Verletzung des Single-Respons Principles.

        public class WhateverIocWrapper : IIocContainer
        {
        private IServiceLocator _services;

        public WhateverIocWrapper(IServiceLocator services)
        {
            if (services == null)
                throw new ArgumentNullException(nameof(services));
            _services = services;
        }
    }
.

    public interface IServiceLocator
    {
        object GetService(Type type, string name = "");
        object[] GetAllServices(Type type);
    }

Die nächsten vier Code-Passagen sind vendor-spezifisch und müssten gekapstelt/abstraiert werden:


        protected abstract object Resolve(TContainer container, Type type);

        protected abstract object ResolveByName(TContainer container, Type type, string name);

        protected abstract IEnumerable<object> ResolveAll(TContainer container, Type type);
    }


    public interface IIocContainer
    {
        object Resolve(Type type);
        object ResolveByName(Type type, string name);
        IEnumerable<object> ResolveAll(Type type);
    }


        public class IocContainerDataResolver : IocDataResolver<IIocContainer>
        {
            public IocContainerDataResolver(string containerName) : base(containerName)
            {
            }

            protected override object Resolve(IIocContainer container, Type type)
            {
                return container.Resolve(type);
            }

            protected override IEnumerable<object> ResolveAll(IIocContainer container, Type type)
            {
                return container.ResolveAll(type);
            }

            protected override object ResolveByName(IIocContainer container, Type type, string name)
            {
                return container.ResolveByName(type, name);
            }
        }

        public object Resolve(Type type)
        {
            return _services.GetService(type);
        }

        public IEnumerable<object> ResolveAll(Type type)
        {
            return _services.GetAllServices(type);
        }

        public object ResolveByName(Type type, string name)
        {
            return _services.GetService(type, name);
        }

Ich schlage vor wir versuchen es mit Unity und Simple Injector. Wir können dann später weiter Container einbinden!

D
985 Beiträge seit 2014
vor 7 Jahren

Wo hier Single Response verletzt wird ist mir ein Rätsel.

Und mit meinem letzten Code braucht man nur noch einen Wrapper für einen beliebigen Container der das IIocContainer Interface implementiert. Per Setter wird da aber nichts übergeben, sondern wie bei einem Wrapper üblich (und auch im Code gezeigt) wird der zu wrappende Container per Konstruktor in den Wrapper geschoben.

Meines Erachtens ist die Verwendung von fertigen Containern hier (im Testumfeld) aber eher kontraproduktiv. Ein sehr simpler Container (wie von mir gezeigt) ist da absolut ausreichend, schließlich geht es hier um die reine Erzeugung von Instanzen in einem kontrollierten Testumfeld.

Sollten diese Instanzen für die Erzeugung weitere Services benötigen, dann würde ich dies (im Testumfeld) nicht dem Container überlassen, sondern diese ganz bewusst in der Factory-Methode mit erzeugen und übergeben. Schließlich benötigen wir für einen Unit-Test dann dort Test-Implementierungen der abhängigen Services.

B
Blaster Themenstarter:in
66 Beiträge seit 2013
vor 7 Jahren

Wo hier Single Response verletzt wird ist mir ein Rätsel.

Um klar zu sein:
Single-Responsibility-Prinzip

Und mit meinem letzten Code braucht man nur noch einen Wrapper für einen beliebigen Container der das IIocContainer Interface implementiert. Per Setter wird da aber nichts übergeben, sondern wie bei einem Wrapper üblich (und auch im Code gezeigt) wird der zu wrappende Container per Konstruktor in den Wrapper geschoben.

Meines Erachtens ist die Verwendung von fertigen Containern hier (im Testumfeld) aber eher kontraproduktiv. Ein sehr simpler Container (wie von mir gezeigt) ist da absolut ausreichend, schließlich geht es hier um die reine Erzeugung von Instanzen in einem kontrollierten Testumfeld.

Sollten diese Instanzen für die Erzeugung weitere Services benötigen, dann würde ich dies (im Testumfeld) nicht dem Container überlassen, sondern diese ganz bewusst in der Factory-Methode mit erzeugen und übergeben. Schließlich benötigen wir für einen Unit-Test dann dort Test-Implementierungen der abhängigen Services.

Ja, klare Fall von Thema verfehlt! 😁 Du weißt gar nicht was ich mit den Container machen will.
Natürlich sind IoC Container keine "Silberkugel". Mark Seemann
führt das Kaptel V DI Anti-Patterns => Service Locator aus. S.a
Service Locator is an Anti-Pattern
Vielleicht hilft das
Fragile Test
und Kapitel IV
https://manning-content.s3.amazonaws.com/download/4/5cfe3a8-e45c-41ea-b6e5-7de2d326cb91/DIi.NET_sample_ch04.pdf

Ich wollte IoC Container verwenden, nicht einen entwickeln und die Memberfunktionen:


object Resolve(TContainer container, Type type)
object ResolveByName(TContainer container, Type type, string name)
IEnumerable<object> ResolveAll(TContainer container, Type type)

stammen von Unity!
IUnityContainer Members

D
985 Beiträge seit 2014
vor 7 Jahren

Mir scheint du hast den Sinn von meinem Code nicht verstanden und mir fehlen wohl die richtigen Worte dir den Sinn korrekt zu erläutern.

Eventuell kannst du ja mal die Stellen in meinem Code markieren, wo Single-Responsibility-Prinzip verletzt wird und wo da das ServiceLocator-Pattern auftaucht.

PS Hier ein Wrapper für den UnityContainer


    public class UnityIocWrapper : IIocContainer
    {
        private IUnityContainer _services;

        public WhateverIocWrapper( IUnityContainer services )
        {
            if ( services == null )
                throw new ArgumentNullException( nameof( services ) );
            _services = services;
        }

        public object Resolve( Type type )
        {
            return _services.Resolve( type );
        }

        public IEnumerable<object> ResolveAll( Type type )
        {
            return _services.ResolveAll( type );
        }

        public object ResolveByName( Type type, string name )
        {
            return _services.ResolveByName( type, name );
        }
    }

Der SimpleInjector Container unterstützt (soweit ich das sehe) keine Named Services. Da wird es schwierig dem das beizubringen.

B
Blaster Themenstarter:in
66 Beiträge seit 2013
vor 7 Jahren

Klare Frequenzprobleme! Soviel ist sicher. 😁

Ok, zurück zur "Dumme Idee"-These!

Why doesn't Simple Injector have an IContainer abstraction like Unity?

Simple Injector does not contain an IContainer abstraction, because that would be useless:
1.It would be useless for Simple Injector to define it, because your code would in that case still depend on the library (since Simple Injector defines that abstraction), and this causes a vendor lock-in, which Simple Injector tries to prevent. 1.Your code should not depend on the container, nor on an abstraction over the container. Both are implementations of the Service Locator anti-pattern. 1.You should NOT use a DI library when unit testing. When unit testing, you should manually inject all fake or mock objects in the class under test. Using a container only complicates things. Perhaps you are using a container, because manually creating those classes is too cumbersome for you. This might indicate problems with your code (you might be violating the Single Responsibility Principle) or your tests (you might be missing a factory method to create the class under test). 1.You might use the container for your integration tests, but you shouldn't have that many integration tests in the first place. The focus should be on unit tests and this should be easy when applying the dependency injection pattern. 1.It is trivial to define such interface (plus an adapter) yourself, which justifies not having it in the library. It is your job as application developer to define the right abstractions for your application as stated by the Dependency Inversion Principle. Libraries and frameworks that tend to do this will fail most of the time in providing an abstraction that works for everyone. 1.The library itself does not use that abstraction and a library should, according to the Framework Design Guidelines, in that case not define such abstraction for you. As stated in the previous point, Simple Injector would get the abstraction wrong anyway. 1.Last but not least, the Simple Injector container does actually implement System.IServiceProvider which is defined in mscorlib.dll and can be used for retrieving service objects.

Trotzdem waren Deine Mühen nicht umsonst!
Denn wir haben eine SimpleInjector API:
SimpleInjector Reference Container
Wenn ich irgendwie den Container einbinden kann.

Edit:
Ja, Du hattest mein einleitenden Post gar nicht richtig gelesen.
Dir fehlt die abstrakte Container-Schnittstelle bei Simple Injector.
Dein Code hat für den Unity Container nicht so viel erreicht.
Wie bereits oben im Punkt 2 aufgeführt, ist die Anbindung oder Abstraktion als Anti-Pattern für den Service Locator zu sehen.
Das Single Responsibility Principle könnte verletzt werden in Punkt 3.

Ziel war es die Schnittstelle zu bekommen.
Dabei haben wir uns wohl angestellt, wie zwei durchgeknallte Nerds, die mit einem Schaubendreher an einer verklemmten Kiste sich die Finger brechen, um für unsere Bierflaschen an den darin enthaltenden Öffner zu kommen. Meine Ausrede ist: Ich hatte einen Kater! 😁 😁

Praktische Intelligenz wäre es gewesen, den Container selbst für das Problem zu nutzen. Dann haben wir auch noch gratis ein Integrationstest dazu!

Folgenden Beispielcode in der Doku gerade gefunden:

2.3 Collections
Simple Injector contains several methods for registering and resolving collections of types. Here are some examples:

  
// Configuration  
// Registering a list of instances that will be created by the container.  
// Supplying a collection of types is the preferred way of registering collections.  
container.RegisterCollection<ILogger>(new[] { typeof(MailLogger), typeof(SqlLogger) });  
// Register a fixed list (these instances should be thread-safe).  
container.RegisterCollection<ILogger>(new[] { new MailLogger(), new SqlLogger() });  
// Using a collection from another subsystem  
container.RegisterCollection<ILogger>(Logger.Providers);  
// Usage  
var loggers = container.GetAllInstances<ILogger>();  

Note: When zero instances are registered using RegisterCollection, each call to Container.GetAllInstances will return
an empty list.
Just as with normal types, Simple Injector can inject collections of instances into constructors:

  
// Definition  
public class Service : IService {  
private readonly IEnumerable<ILogger> loggers;  
public Service(IEnumerable<ILogger> loggers) {  
this.loggers = loggers;  
}  
void IService.DoStuff() {  
// Log to all loggers  
foreach (var logger in this.loggers) {  
logger.Log("Some message");  
}  
}  
}  
// Configuration  
container.RegisterCollection<ILogger>(new[] { typeof(MailLogger), typeof(SqlLogger) });  
container.Register<IService, Service>(Lifestyle.Singleton);  
// Usage  
var service = container.GetInstance<IService>();  
service.DoStuff();  
  

The RegisterCollection overloads that take a collection of Type instances rely on the Container to create an instance
of each type just as it would for individual registrations. This means that the same rules we have seen above apply to
each item in the collection. Take a look at the following configuration:

  
// Configuration  
container.Register<MailLogger>(Lifestyle.Singleton);  
container.Register<ILogger, FileLogger>();  
container.RegisterCollection<ILogger>(new[] {  
typeof(MailLogger),  
typeof(SqlLogger),  
typeof(ILogger)  
});  
  

When the registered collection of ILogger instances are resolved, the Container will resolve each of them applying
the specific rules of their configuration. When no registration exists, the type is created with the default Transient
lifestyle (transient means that a new instance is created every time the returned collection is iterated). In the example,
the MailLogger type is registered as Singleton, and so each resolved ILogger collection will always have the same
instance of MailLogger in their collection.
Since the creation is forwarded, abstract types can also be registered using RegisterCollection. In the above example
the ILogger type itself is registered using RegisterCollection. This seems like a recursive definition, but it will work
nonetheless. In this particular case you could imagine this to be a registration with a default ILogger registration which is also included in the collection of ILogger instances as well.

Also einfach eine Schnittstelle deklarieren, registrieren, collection bilden und über

var service = container.GetInstance<IService>();
service.DoStuff();

zugeifen.

Bleibt noch die letzte Frage zu klären, warum ich, obwohl ich die Nachteile 1-6 kenne, unbedingt ein IoC Container im Unit-Test haben will.

Ich werde oft auf Misthaufen von Altlasten gesetzt, die keinerlei OO Strukturen kennen, Spagetti-Knoten pur, nix dokumentiert, viele Black Boxen, operative Umgebung kann nicht unterbrochen werden, Datenzustände können schwer festgestellt werden, keine Tests entwickelt usw. Dann ist ein IOC Container - trotz der Risiken und Nebenwirkungen - extrem hilfreich, um wenigestens die ersten Mikado-Stäbchen herauszuziehen und Theorien zu überprüfen, Test-Vektoren zu erhalten, Code Strukturen zu entwickeln usw.

Und sobald ein wenig Strukturen vorliegen, kannst Du den Kramm ja vom Register nehmen, mit Struktur aufbauen und manuell mocken o.ä. Deswegen musste der Container auch vor allem schnell sein, um Nebeneffekte zu vermeiden. Deshalb auch die Integration mit Xunit für die langfristige Testumgebung.

Jetzt geht es von meiner Seite auch nur langsam weiter, denn ich habe Riesenärger mit rechtsbeugenden Richtern und engen Fristen ... 😦

Warnung von gfoidl vor 7 Jahren

Bitte beachte [Hinweis] Wie poste ich richtig? Punkt 3.1

B
Blaster Themenstarter:in
66 Beiträge seit 2013
vor 7 Jahren

Der Stress war wohl umsonst.

Am 9.4.2017 ist Simple Injector 4.0 herausgekommen.
Dann lesen wir alle mal Kapitel 5.0.
http://simpleinjector.readthedocs.io/en/latest/howto.html

Fummeln muss aber immer noch.