| 

.NET C# Java Javascript Exception

6
[Visual Studio 10, C#]

Hallo zusammen,

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!

Danke,
Mike
31.05.2012
Xantiva 2,3k 2 9
3 Antworten
2
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.
01.06.2012
judgy 3,0k 1 1 8
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 ...
Xantiva 01.06.2012
3
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.
03.06.2012
Matthias Hlawatsch 13,2k 4 9
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.

Ciao,
Mike
Xantiva 04.06.2012
+1, und noch dazu ein DICKES. Wie Roy (Osherove) schon schreibt:

Auch der Test ist Nutzer einer Klasse.

Seit ich diesen Satz gelesen hab, sind meine Tests um so viel besser geworden. Wieso kompliziert, wenns auch einfach geht. KISS.
ffordermaier 06.06.2012
0
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:

Unit Testing a Private Method of an Abstract Class in C#

Dort wird eine Möglichkeit aufgezeigt, mit System.Dynamics einen eigenen "DynamicAccessor" zu erstellen und damit dann den Test zu schreiben ...
06.06.2012
Xantiva 2,3k 2 9
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.
ffordermaier 06.06.2012
Deine Version ist noch ein wenig umfangreicher, vollständiger! Danke.
Xantiva 11.06.2012

Stelle deine Visual-studio-Frage jetzt!