2009-05-08 10 views
5

Chcę wymusić na mój bazowy kod z reguły niezmiennej następujący test.net niezmienne obiekty

[TestFixture] 
public class TestEntityIf 
{ 
    [Test] 
    public void IsImmutable() 
    { 
     var setterCount = 
      (from s in typeof (Entity).GetProperties(BindingFlags.Public | BindingFlags.Instance) 
      where s.CanWrite 
      select s) 
       .Count(); 

     Assert.That(setterCount == 0, Is.True, "Immutable rule is broken"); 
    } 
} 

przechodzi on na:

public class Entity 
{ 
    private int ID1; 
    public int ID 
    { 
     get { return ID1; } 
    } 
} 

ale nie ma na to:

public class Entity 
{ 
    public int ID { get; private set; } 
} 

Tutaj pojawia się pytanie "WTF?"

Odpowiedz

3

Mała modyfikacja odpowiedzi opublikowanych w innym miejscu. Poniższe wartości zwracają wartość niezerową, jeśli istnieje co najmniej jedna właściwość z chronionym lub publicznym ustawiaczem. Zwróć uwagę na sprawdzanie, czy GetSetMethod zwraca wartość null (no setter) i test IsPrivate (tj. Nie jest publiczny lub chroniony), a nie IsPublic (tylko publiczne).

var setterCount = 
      (from s in typeof(Entity).GetProperties(
       BindingFlags.Public 
       | BindingFlags.NonPublic 
       | BindingFlags.Instance) 
      where 
       s.GetSetMethod(true) != null // setter available 
       && (!s.GetSetMethod(true).IsPrivate) 
      select s).Count(); 

Niemniej jednak, jak pointed out in Daniel Brückner's answer fakt, że klasa nie ma widocznych publicznie ustawiające właściwość jest konieczny, ale nie wystarczający warunek dla klasy należy uznać za niezmienne.

+0

Test s.GetSetMethod (true)! = Null jest zbędny, ponieważ CanWrite == true gwarantuje istnienie setera. –

+0

Prawda, "s.GetSetMethod (true)! = Null" jest równoważna z wartością CanWrite, więc jedna z nich jest zbędna. Usunąłem CanWrite, ponieważ chcę jasno określić testowanie na wartość null przed wyważeniem właściwości IsPrivate. – Joe

1

Z pewnością jest to Twoja private set w drugiej.

W pierwszym wystąpienie klasy Entity może mieć przypisaną właściwość ID1 do zewnętrznej klasy.

W tym ostatnim, seter jest prywatny do samej klasy, a więc może być wywołana tylko z wnętrza jednostki (czyli własnego konstruktor/inicjator)

Weź private Spośród swojej seter w drugim i powinno pass

+0

ID1 jest polem zaplecza, a nie właściwością w pierwszej klasie i jest tylko przez getter do świata zewnętrznego. W jaki sposób może być dostępny dla zewnętrznej klasy? –

+0

W swoim oryginalnym przykładzie miał setera publicznego na pierwszej nieruchomości. Od tego czasu zredagował Pytanie. http://stackoverflow.com/revisions/839066/list –

+0

Rozumiem. Dzięki Eoin. –

2

Myślę, że powodem jest to, że CanWrite mówi, że zwraca wartość true, jeśli istnieje seter. Prywatne seter jest również seter.

Co mnie trochę zaskakuje to, że pierwsze podania, ponieważ ma publicznego ustawiacza, więc jeśli nie jestem nadal zbyt niski na cafeine, settercount jest 1, więc aser powinien zawieść. Oboje powinni ponieść porażkę, ponieważ CanWrite po prostu zwraca się do obu. (i zapytanie linq po prostu pobiera właściwości publiczne, w tym identyfikator, ponieważ jest to publiczne)

(edycja) Widzę, że zmieniłeś kod pierwszej klasy, więc nie ma już setera.

Twoje założenie, że CanWrite analizuje akcesorów metody ustawiającej, tak nie jest. należy zrobić:

var setterCount = 
      (from s in typeof (Entity).GetProperties(BindingFlags.Public | BindingFlags.Instance) 
      where s.GetSetMethod().IsPublic 
      select s) 
       .Count(); 
+0

Właśnie wypróbowałem kod i zgadzam się. Dostaję liczbę 1 setera dla obu klas testowych. –

+0

Chyba, że ​​mam zbyt mało kofeiny, nie widzę setera w pierwszym przypadku. ID1 jest zmienną składową, a nie właściwością, więc nie ma metody "setter"? Dokładnie –

+0

. To, co powinien zrobić TS, to wywołanie metody GetSetMethod() na propertyinfo, a następnie sprawdzenie, czy IsPublic jest prawdziwe w zwróconej metodzie MethodInfo. –

7

Problemem jest to, że nieruchomość jest publiczna - ze względu na getter publicznej - i to jest zapisywalny - ze względu na prywatnej seter. Będziesz musiał udoskonalić swój test.

Ponadto chcę dodać, że nie można zagwarantować niezmienności w ten sposób, ponieważ można modyfikować prywatne dane wewnątrz metod. Aby zagwarantować niezmienność, należy sprawdzić, czy wszystkie pola są zadeklarowane tylko do odczytu i nie ma właściwości automatycznie zaimplementowanych.

public static Boolean IsImmutable(this Type type) 
{ 
    const BindingFlags flags = BindingFlags.Instance | 
           BindingFlags.NonPublic | 
           BindingFlags.Public; 

    return type.GetFields(flags).All(f => f.IsInitOnly); 
} 

public static Boolean IsImmutable(this Object @object) 
{ 
    return (@object == null) || @object.GetType().IsImmutable(); 
} 

Dzięki tej metodzie wydłużania można łatwo przetestować typów

typeof(MyType).IsImmutable() 

i instancje

myInstance.IsImmutable() 

ich niezmienność.

Uwagi

  • Patrząc na polach instancji pozwala mieć właściwości zapisywalnych, ale zapewnia, że ​​nie są już pola, które mogą być modyfikowane.
  • Własności zaimplementowane automatycznie spowodują test niezmienności jako sprawdzony z powodu prywatnego, anonimowego pola podkładu.
  • Nadal można modyfikować pola readonly za pomocą odbicia.
  • Może należy sprawdzić, czy typy wszystkich pól są niezmienne, ponieważ te obiekty należą do stanu.
  • Nie można tego zrobić za pomocą prostego FiledInfo.FieldType.IsImmutable() dla wszystkich pól z powodu możliwych cykli i ponieważ typy podstawowe są zmienne.
+0

Jakiekolwiek powody używania 'Object' i' Boolean' zamiast 'object' i' bool'? –

+0

Wyglądają ładniej (podświetlanie składni i wielka litera pierwsza litera) i są rzeczywistymi typami i nie zależą od tego, co generuje kompilator dla ciągu, bool, int, obiektu i wszystkich innych (podczas gdy wiem, że kompilator nie ma wyboru). –

+0

Czy istnieją ku temu dobre powody? –

3

Prawdopodobnie trzeba zadzwonić

propertyInfo.GetSetMethod().IsPublic 

ponieważ metoda i set-get-metoda nie mają ten sam modyfikator dostępu, nie można polegać na samej PropertyInfo.

var setterCount = 
     (from s in typeof (Entity).GetProperties(
      BindingFlags.Public 
      | BindingFlags.NonPublic 
      | BindingFlags.Instance) 
     where 
      s.GetSetMethod() != null  // public setter available 
     select s) 
+0

+1 To już prawie. Ale funkcja GetSetMethod() zwróci wartość null dla prywatnych ustawiaczy lub właściwości bez ustawień. Musisz to zmienić, aby przetestować pod kątem zerowej wartości. – Joe

+0

@Joe: Ok, naprawiłem to, nie potrzebuję już CanWrite –

+0

Nie potrzebujesz s.GetSetMethod(). IsPublic też, ponieważ ProeprtyInfo.GetSetMethod() zwraca tylko publiczne ustawione metody, chyba że wywołasz ProeprtyInfo. GetSetMethod (Boolean nonPublic). –

1

Jeśli dobrze cię zrozumiałem, chcesz, aby Podmiot był niezmienny. Jeśli tak, to badanie powinno zostać zmienione na

var setterCount = (from s in typeof(string).GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(p => p.GetSetMethod()) 
    where s != null && s.IsPublic 
    select s).Count(); 

Assert.That(setterCount == 0, Is.True, "Immutable rule is broken"); 
+0

Asercja jest poprawna - typ jest niezmienny, jeśli setterCount == 0 jest prawdziwy, a asercja wyświetli komunikat "Niezmienna reguła jest zepsuta", jeśli ten test się nie powiedzie. –

+0

Naprawiono, dzięki Daniel – SeeR

2

Proponuję zmianę stanu własności do:

s.CanWrite && s.GetSetMethod().IsPublic 
0

Czy próbowałeś debugować go zobaczyć jakie właściwości, których CanWrite właściwość ma wartość true są zwracane przez zapytanie LINQ? Podobno zbiera rzeczy, których się nie spodziewasz. Dzięki tej wiedzy możesz sprawić, by twoje zapytanie LINQ było bardziej selektywne i działało tak, jak chcesz. Ponadto, zgadzam się z @Daniel Bruckner, że twoja klasa nie jest całkowicie zmienna, chyba że pola są również tylko do odczytu. Osoba dzwoniąca może wywołać metodę lub użyć modułu pobierającego, który wewnętrznie modyfikuje prywatne pola, które są publicznie udostępniane jako readonly za pomocą modułów pobierających, co złamałoby niezmienną regułę, zaskoczy klienta i spowoduje problemy. Operacje na zmiennym obiekcie nie powinny mieć skutków ubocznych.

Powiązane problemy