Avatar billede iakob Nybegynder
25. marts 2010 - 08:29 Der er 10 kommentarer og
1 løsning

C# 3.5: Generics, polymorfi, NUnit test, Rhino mocks

Jeg har en række entities (customer, product etc), hvoraf nogen bruger lazyload. Entities hentes og gemmes via en mindre række businessklasser, der alle nedarver fra en generic interface: IBaseBusiness<T>

Mit problem er nu unittest (jeg bruger NUnit og Rhino mocks)

Jeg vil gerne lave en testfixture pr entity som benytter sig af en fælles basisklasse. For hver property, der er lazyloaded, skal jeg så teste om den bliver loaded korrekt og hertil skal jeg sætte expects op på mine business klasser. Men det har jeg svært ved.

Her er et eksempel

[TestFixture]
CompanyEntityTest : LazyLoadTest
{
  protected overwrite object GetBusinessObject(Type theType)
{
if (Type == typeof(Group)
return ServiceFacace.GroupBusiness;
}
}

LazyLoadTest
{
protected abstract object GetBusinessObject(Type theType);

[Test, TestCaseSource="PropertyDataSource"]
public void TestLazyLoad(string propertyName, string idPropertyName)
{
PropertyInfo property = ...
PropertyInfo idproperty = ...
...
object business =  this.GetBusinessObject(property, idproperty);

business.Expect(b => b.GetObject(0)).Return(newValue).IgnoreArguments();
}
}

Mit problem er at typen object ikke indeholder metoden GetObject(), så den her vil ikke kompilere. Og jeg kan ikke sende IBaseBusiness<T> tilbage som retur-parameter fra GetBusinessObject da jeg ikke på compiletime ved hvad T er.

Findes der en måde at få en variabel som Type type sat ind som T ?

Findes der en måde at sætte expect op på et object selvom den ikke umiddelbart indeholder metoden? Jeg bruger C# 3.5 så jeg kan ikke bruge nøgleordet dynamic.
Avatar billede janus_007 Nybegynder
25. marts 2010 - 10:26 #1
Lyder lidt som en decideret Rhino mock spørgsmål :) Mon ikke du kan få en mere præcist svar ved at spørge i deres forum?
Avatar billede arne_v Ekspert
25. marts 2010 - 18:18 #2
Interessant spoergsmaal.

De kode snippets du har angivet i spoergsmaalet er ikke helt komplette/konsistente.

Men jeg vil antage at foelgende er en valid beskrivelse af problemet:


using System;

namespace E
{
    public interface IBaseBusiness<T>
    {
        T GetObject();
    }
    public class Foo
    {
        public override string ToString()
        {
            return "I am a Foo";
        }
    }
    public class Bar
    {
        public override string ToString()
        {
            return "I am a Bar";
        }
    }
    public class FooBusiness : IBaseBusiness<Foo>
    {
        public Foo GetObject()
        {
            return new Foo();
        }
    }
    public class BarBusiness : IBaseBusiness<Bar>
    {
        public Bar GetObject()
        {
            return new Bar();
        }
    }
    public abstract class LazyLoadTest
    {
        protected abstract object GetBusinessObject(Type theType);
        public void TestLazyLoad(string typnam)
        {
            // compile time error when calling GetObject
            object business = GetBusinessObject(Type.GetType(typnam));
            // runtime error when assigning
            IBaseBusiness<object> business = (IBaseBusiness<object>)GetBusinessObject(Type.GetType(typnam));
            Console.WriteLine(business.GetObject());
        }
    }
    public class CompanyEntityTest : LazyLoadTest
    {
          protected override object GetBusinessObject(Type theType)
        {
              if(theType == typeof(Foo))
              {
                  return new FooBusiness();
              }
              else if(theType == typeof(Bar))
              {
                  return new BarBusiness();
              }
              else
              {
                  throw new Exception("No Foo or Bar");
              }
          }
    }
    public class Program
    {
        public static void Main(string[] args)
        {
            CompanyEntityTest cet = new CompanyEntityTest();
            cet.TestLazyLoad("E.Foo");
            cet.TestLazyLoad("E.Bar");
            Console.ReadKey();
        }
    }
}
Avatar billede arne_v Ekspert
25. marts 2010 - 18:20 #3
En mulig loesning er at vaere mindre generisk og have et ikke generisk super interface og bruge eksplicit interface implementation.

Eksempel:


using System;

namespace E
{
    public interface IBaseBusiness
    {
        object GetObject();
    }
    public interface IBaseBusiness<T> : IBaseBusiness
    {
        new T GetObject();
    }
    public class Foo
    {
        public override string ToString()
        {
            return "I am a Foo";
        }
    }
    public class Bar
    {
        public override string ToString()
        {
            return "I am a Bar";
        }
    }
    public abstract class BaseBusiness<T> : IBaseBusiness<T>
    {
        public abstract T GetObject();
        object IBaseBusiness.GetObject()
        {
            return GetObject();
        }
    }
    public class FooBusiness : BaseBusiness<Foo>
    {
        public override Foo GetObject()
        {
            return new Foo();
        }
    }
    public class BarBusiness : BaseBusiness<Bar>
    {
        public override Bar GetObject()
        {
            return new Bar();
        }
    }
    public abstract class LazyLoadTest
    {
        protected abstract IBaseBusiness GetBusinessObject(Type theType);
        public void TestLazyLoad(string typnam)
        {
            IBaseBusiness business = GetBusinessObject(Type.GetType(typnam));
            Console.WriteLine(business.GetObject());
        }
    }
    public class CompanyEntityTest : LazyLoadTest
    {
          protected override IBaseBusiness GetBusinessObject(Type theType)
        {
              if(theType == typeof(Foo))
              {
                  return new FooBusiness();
              }
              else if(theType == typeof(Bar))
              {
                  return new BarBusiness();
              }
              else
              {
                  throw new Exception("No Foo or Bar");
              }
          }
    }
    public class Program
    {
        public static void Main(string[] args)
        {
            CompanyEntityTest cet = new CompanyEntityTest();
            cet.TestLazyLoad("E.Foo");
            cet.TestLazyLoad("E.Bar");
            Console.ReadKey();
        }
    }
}
Avatar billede arne_v Ekspert
25. marts 2010 - 18:21 #4
En anden mulighed er at blive mere generisk og brugere generisk type fremfor Type argument.


using System;

namespace E
{
    public interface IBaseBusiness<T>
    {
        T GetObject();
    }
    public class Foo
    {
        public override string ToString()
        {
            return "I am a Foo";
        }
    }
    public class Bar
    {
        public override string ToString()
        {
            return "I am a Bar";
        }
    }
    public class FooBusiness : IBaseBusiness<Foo>
    {
        public Foo GetObject()
        {
            return new Foo();
        }
    }
    public class BarBusiness : IBaseBusiness<Bar>
    {
        public Bar GetObject()
        {
            return new Bar();
        }
    }
    public abstract class LazyLoadTest
    {
        protected abstract IBaseBusiness<T> GetBusinessObject<T>();
        public void TestLazyLoad<T>()
        {
            IBaseBusiness<T> business = GetBusinessObject<T>();
            Console.WriteLine(business.GetObject());
        }
    }
    public class CompanyEntityTest : LazyLoadTest
    {
          protected override IBaseBusiness<T> GetBusinessObject<T>()
        {
              if(typeof(T) == typeof(Foo))
              {
                  return (IBaseBusiness<T>)new FooBusiness();
              }
              else if(typeof(T) == typeof(Bar))
              {
                  return (IBaseBusiness<T>)new BarBusiness();
              }
              else
              {
                  throw new Exception("No Foo or Bar");
              }
          }
    }
    public class Program
    {
        public static void Main(string[] args)
        {
            CompanyEntityTest cet = new CompanyEntityTest();
            cet.TestLazyLoad<Foo>();
            cet.TestLazyLoad<Bar>();
            Console.ReadKey();
        }
    }
}
Avatar billede arne_v Ekspert
25. marts 2010 - 18:23 #5
Jeg er langtfra sikker paa at de loesninger kan bruges i din kontekst, men det er ihvertfald to relevante indgange. Fordi dit grundliggende problem er dit mix af generisk og ikke-generisk, saa det giver mening at skifte til enten at vaere mere konsekvent generisk eller mere konsekvent ikke-generisk.
Avatar billede iakob Nybegynder
26. marts 2010 - 09:39 #6
Allerførst: Hvordan skriver man koden pænt? Jeg kan ikke finde det i hjælpen.

Mit problem ligger i at de objekter, jeg ønsker at teste er properties på Foo og Bar - og de har samme baseclass. Jeg kan derfor ikke gøre testen mere generic, fordi jeg får typen get at lave en typeof(Foo).GetProperties().

Jeg kan heller ikke lave testen mindre generic, fordi implementationen af disse klasser ligger fast.
Avatar billede arne_v Ekspert
26. marts 2010 - 16:35 #7
Det lyder som om at du har faaet tildelt nogle meget daarlige kort.

Med de restriktioner er Reflection den eneste udvej jeg kan se.


using System;
using System.Reflection;

namespace E
{
    public interface IBaseBusiness<T>
    {
        T GetObject();
    }
    public class Foo
    {
        public override string ToString()
        {
            return "I am a Foo";
        }
    }
    public class Bar
    {
        public override string ToString()
        {
            return "I am a Bar";
        }
    }
    public class FooBusiness : IBaseBusiness<Foo>
    {
        public Foo GetObject()
        {
            return new Foo();
        }
    }
    public class BarBusiness : IBaseBusiness<Bar>
    {
        public Bar GetObject()
        {
            return new Bar();
        }
    }
    public abstract class LazyLoadTest
    {
        protected abstract object GetBusinessObject(Type theType);
        public void TestLazyLoad(string typnam)
        {
            object business = GetBusinessObject(Type.GetType(typnam));
            object something = business.GetType().InvokeMember("GetObject", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, business, new object[0]);
            Console.WriteLine(something);
        }
    }
    public class CompanyEntityTest : LazyLoadTest
    {
          protected override object GetBusinessObject(Type theType)
        {
              if(theType == typeof(Foo))
              {
                  return new FooBusiness();
              }
              else if(theType == typeof(Bar))
              {
                  return new BarBusiness();
              }
              else
              {
                  throw new Exception("No Foo or Bar");
              }
          }
    }
    public class Program
    {
        public static void Main(string[] args)
        {
            CompanyEntityTest cet = new CompanyEntityTest();
            cet.TestLazyLoad("E.Foo");
            cet.TestLazyLoad("E.Bar");
            Console.ReadKey();
        }
    }
}
Avatar billede arne_v Ekspert
26. april 2010 - 03:09 #8
iakob ?
Avatar billede iakob Nybegynder
26. april 2010 - 08:27 #9
Hm.. foo og bar er bare to i en større, potentiel voksende mængde af klasser (det er jo entities - business objects). Derfor er det ikke optimalt at lave en case på typerne.
Avatar billede arne_v Ekspert
10. maj 2010 - 03:55 #10
Selvfølgelig ikke.

Men så vidt jeg kan se har I malet jer inde i et hjørne.
Avatar billede iakob Nybegynder
10. maj 2010 - 10:16 #11
Hvilket også var grunden til de mange points - det var en svær opgave som jeg ikke lige umiddelbart kunne se nogen elegant løsning på.

Oh well.. tak fordi I prøvede
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
IT-kurser om Microsoft 365, sikkerhed, personlig vækst, udvikling, digital markedsføring, grafisk design, SAP og forretningsanalyse.

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester