ich habe den Fall, dass ich in einer abstrakten Klasse ein privates Feld in einem Unit-Test überprüfen möchte. Egal was ich mache, ich bekomme immer eine System.MissingFieldException ... (und das obwohl der Code kompiliert und mir Intellisense auch das Feld schon richtig vorschlägt).
Hier mal ein (sinnfreies) Beispiel ...
Die abstrakte Klasse:
public abstract class Person { private int m_LengthOfName = 0; // <== Das Feld möchte ich testen!
private string m_LastName;
public string LastName { get { return m_LastName; } set { m_LastName = value; m_LengthOfName = m_LastName.Length; } } }
Eine abgeleitete Klasse:
public class Woman : Person { public bool LikesShopping { get; set; } }
Und hier der Test, der nicht funktioniert:
[TestMethod()] public void LastNameTestPrivateField() { Woman_Accessor target = new Woman_Accessor(); string lastName = "Meier"; int expected = 5;
target.LastName = lastName; // In der nachfolgenden Zeile wird die System.MissingFieldException geworfen. // Das Feld "PrivateFields.Woman.m_LengthOfName" wurde nicht gefunden. int actual = target.m_LengthOfName;
Assert.AreEqual(expected, actual); }
Ich kann auch target als Person_Accessor deklarieren, der Fehler bleibt.
Ich möchte eigentlich nicht den Code nur für den Test ändern. Wenn jemand eine Lösung hätte, würde mich das freuen!
Das funktioniert nicht, weil Dein Accessor nur die selben Rechte hat wie die abgeleitete Klasse Woman. Ich habe hierzu neulich einen feinen Blog-Eintrag gelesen.
Danke, dann muss ich mir da was anderes ausdenken. Aber der Beitrag hat mich auch noch mal darauf gestossen, dass die Accessor Klassen deprecated sind ... http://msdn.microsoft.com/en-us/library/bb385974.aspx Schade, so waren bestimmte Dinge mit wenig Overhead zu testen ...
Die Erklärung, warum das nicht geht, hat judgy ja schon geliefert. Und ähnlich wie Hendrik in dem verlinkten Blog-Beitrag würde ich ein Fragezeichen hinter den Ansatz setzen, in einem Unit-Test so tief in die Innereien des Testobjekts hineinzuschauen. Aber wenn es denn sein soll / muß, hätte ich einen Vorschlag:
Teste mit der normalen Woman-Klasse statt mit dem Accessor. Definiere ein Interface
internal interface IPersonTestAccess { int LengthOfName { get; } }
das von Person explizit implementiert wird:
#region IPersonTestAccess Member
int IPersonTestAccess.LengthOfName { get { return m_LengthOfName; } }
#endregion
Erlaube Deinem Test-Projekt mit dem InternalsVisibleTo-Attribute Zugriff auf die internal-Member Deines Projektes (das halte ich sowieso für eine gute Idee). Der Test sieht dann ganz einfach so aus:
[TestMethod()] public void LastNameTestPrivateField() { Woman target = new Woman();
string lastName = "Meier"; int expected = 5;
target.LastName = lastName; int actual = (target as IPersonTestAccess).LengthOfName;
Assert.AreEqual(expected, actual); } }
Ein Interface nur zum Testen mag am Anfang befremdlich erscheinen. Ging mir zunächst auch so. Überzeugt hat mich das Argument, dass auch jedes etwas kompliziertere technische Gerät, vom Auto bis zur Waschmaschine, eine Service-Schnittstelle hat, über die jemand, der sich damit auskennt, an den inneren Zustand des Geräts herankommt. Durch die explizite Implementierung bekommt IPersonTestAccess den Charakter einer solchen Service-Schnittstelle: die darin definierten Methoden sind nur in der Assembly selbst und im Testprojekt verfügbar und auch dann nur "vorsätzlich", also wenn explizit gecastet wird. Das sollte genug Schutz gegen Mißbrauch sein. (Mal abgesehen davon, dass ohnehin immer noch die Möglichkeit des Zugriffs via Reflection besteht.) Nebenbei dokumentierst Du auf diese Weise für den fremden Leser Deines Codes, welche Member von Person so wichtig für die Implementierung sind, dass sie für Unit-Tests zugänglich sein sollen.
Hi Matthias, danke für den Tipp. Das Argument mit einer Service Schnittstelle klingt durchaus plausibel. Wenn ich daran gehe, die Verwendung des abgekündigten Accessors in den Test zu eliminieren, benötige ich das InternalsVisibleTo ja sowieso.
Schade, dass in der MSDN bei den Beispielen zum Testen von privaten Methoden (VS2010) der Accessor immer noch verwendet wird und kein Hinweis auf den deprecated Status vorhanden ist.
Da ich bei neuen Tests nun grundsätzlich ohne die deprecated Accessor Klassen auskommen möchte, mit den PrivateObjects aber so meine Schwierigkeiten hatte, bin ich auf diesen Blogbeitrag gestossen:
So wie's aussieht, ist der verlinkte Kollege da ein gutes Jahr vor mir drauf gekommen (siehe meinen Kommentar in Hendriks Blog und meinen Blogeintrag http://blog.mycupof.net/2012/04/21/testing-private-and-internal-members-add-a-little-dynamic-sugar/ ). Egal, Hauptsache die Ideen nützen schlussendlich irgendjemandem.
http://msdn.microsoft.com/en-us/library/bb385974.aspx
Schade, so waren bestimmte Dinge mit wenig Overhead zu testen ...