Zum Inhalt springen

[WPF] Unit Testing mit Mocks und Dependency Injection


Schneeherz

Empfohlene Beiträge

Hallo ihr Lieben,

ich bin noch ganz am Anfang meiner Programmierkarriere und stehe vor folgendem Problem:

Ich habe eine WPF-Applikation geschrieben, die ein webbasiertes System überwachen kann.

Welches System, kann per URL angegeben werden, also z.B. "http://localhost:1010/". Durch einen Regex Ausdruck habe ich sicher gestellt, dass die URLs nur einem bestimmten Schema entsprechen können.

Ich habe auch INotifyPropertyChanged verwendet und bekomme im Programm durch OnPropertyChanged mit, wenn sich die URL ändert.

Jetzt habe ich die Aufgabe bekommen Unit Tests für meine Applikation zu schreiben. Mit Mocking und Dependency Injection.

Ich habe viel im Internet gesucht und gelesen, aber so wirklich verstehen, wie man es anwendet, tue ich nicht. Wofür es nützlich ist, ist an sich verständlich.

Kann mir das vielleicht jemand kurz für völlige Anfänger verständlich erklären? :)

Und wie würde ich einen String testen, der durch OnPropertyChanged eben ständig geändert werden kann? Momentan habe ich nur einen Test, der mir diesen bestimmten string "http://localhost:1010/" testet... Mir wurde gesagt, dass ich dafür Mocking brauche?

Ich hoffe, man versteht einigermaßen, was ich meine... Ich habe meinen Code gerade nicht griffbereit, anders würde ich euch ein Beispiel zeigen.

Mir geht es allerdings auch erst einmal darum es zu verstehen, vielleicht komme ich dann selbst darauf.

Danke im Voraus und ich hoffe, ich habe kein Thema übersehen, das dies vielleicht schon behandelt!

Link zu diesem Kommentar
Auf anderen Seiten teilen

Meiner Meinung nach müssen Properties nicht darauf getestet werden, ob das PropertyChangedEvent ausgeführt wird. Eher sollten die Aktionen getestet werden, die ausgeführt werden, wenn eine gültige bzw. ungültige URL eingegeben wird.

Dependency Injection hat indirekt etwas mit Testen zu tun.

Jede Klasse sollte nur ein Belang haben. Somit kann es passieren, dass Klassen Abhängigkeiten zu anderen Klassen haben.

Die Abhängigkeiten werden über Schnittstellen in die jeweiligen Klassen injiziert, welche im Test als Mock / Fake übergeben werden.

Stichwort hier: SOLID => 5 Prinzipen in der Softwareentwicklung.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Bei jedem Entwickler kann es anders aussehen :-)

Beispiel entspricht zwar nicht Deiner Beschreibung, aber es demonstriert vielleicht, wie ein Test ausehen könnte.


Pseudocode:


    public class CreateUserViewModel : BindableBase

    {

        private readonly IUserService _userService;


        private string _firstName;


        private string _lastName;


        public CreateUserViewModel(IUserService userService)

        {

            _userService = userService;


            CreateUserCommand = new DelegateCommand(ExecuteCreateUserCommand);

        }


        public ICommand CreateUserCommand { get; private set; }


        public string FirstName

        {

            get { return _firstName; }

            set { SetProperty(ref _firstName, value); }

        }


        public string LastName

        {

            get { return _lastName; }

            set { SetProperty(ref _lastName, value); }

        }


        private void ExecuteCreateUserCommand()

        {

            var user = new UserModel { FirstName = FirstName, LastName = LastName };


            _userService.Save(user);

        }

    }

So oder so könnte ein Test dafür aussehen.

Pseudocode:


    [TestFixture]

    public class Wenn_ein_neuer_Benutzer_angelegt_wird

    {

        private CreateUserViewModel _createUserViewModel;


        private IUserService _fakeUserService;


        [TestFixtureSetUp]

        public void Initialize()

        {

            _fakeUserService = A.Fake<IUserService>();


            _createUserViewModel = new CreateUserViewModel(_fakeUserService);

            _createUserViewModel.FirstName = "Max";

            _createUserViewModel.LastName = "Mustermann";

        }


        [Test]

        public void Sollte_der_Service_einmal_aufgerufen_werden()

        {

            _createUserViewModel.CreateUserCommand.Execute(null);


            _fakeUserService.CallsTo(s => s.Save(A<UserModel>.Ignored)).MustHaveHappened(Repeated.Exactly.Once);

        }

    }

Testframework => NUnit

Zum Faken => FakeItEasy

Prism 5

Bearbeitet von lbm1305
Link zu diesem Kommentar
Auf anderen Seiten teilen

Danke für die Antwort!

Anhand deines Beispiels, verstehe ich es, aber lässt es sich denn auch auf folgende Properties übertragen?

public string CompleteString

{

get { return ConfigurationManager.AppSettings["String"]; }

set { }

}

public string PartString

{

get

{

Regex rx = new Regex(@"((\/xyz\/)(.*))?(\/abc\/def)");

string[] part = rx.Split(CompleteString);

return part[0];

}

set{}

}

Mir ist nicht wirklich klar, wie ich etwas testen soll, dass sich ja jederzeit ändern kann.

Muss ich für diese Properties eine Klasse und ein Interface erstellen und sie dann testen?

Meine momentanen Tests sehen so aus (funktionieren allerdings nur, wenn es wirklich die angegebenen strings sind):

[TestMethod]

public void CompleteString_PropertyTest()

{

var target = new MainViewModel();

var expected = @"http://localhost:1010/abc/def'>http://localhost:1010/abc/def";

var actual = target.CompleteString;

Assert.AreEqual(expected, actual);

}

[TestMethod]

public void PartString_PropertyTest()

{

var target = new MainViewModel();

var expected = @"http://localhost:1010";

var actual = target.PartString;

Assert.AreEqual(expected, actual);

}

Link zu diesem Kommentar
Auf anderen Seiten teilen

Optimal wäre es wenn du ConfigurationManager.AppSettings["String"]; mocken könntest und somit innerhalb der Tests einen festen Wert hast, alternativ könnte es auch möglich sein eine eigene AppSettings Datei extra für Tests zu haben, das wäre dann noch besser. Aber ich hab keine Erfahrungen mit UnitTests unter .Net.

Bei dem PartString Test müsstest du dann am Besten verschiedene CompleteString Werte mocken bei denen das RegEx dann korrekt läuft und welche bei denen es erwartet fehlschlägt damit du möglichst sicher sein kannst das es in verschiedenen Situationen wie erwartet funktioniert.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Persönlich versuche ich Teile, die aus dem .NET Framework kommen, zu wrappen.

Für den ConfigurationManager würde es dann eine "Setingsklasse" geben, die intern auf den ConfigurationManager zugreift.

Der Zugriff auf die .config muss in meine Aufgen nicht getestet werden, da MS dies (hoffentlich) bereits gemacht hat.

In den Test kannt dann das jeweilige Property konfiguriert werden.


    public class CustomViewModel

    {

        private readonly IApplicationSettings _applicationSettings;


        /// <summary>

        /// Initialisiert eine neue Instanz der <see cref="T:System.Object"/>-Klasse.

        /// </summary>

        public CustomViewModel(IApplicationSettings applicationSettings)

        {

            _applicationSettings = applicationSettings;

        }


        public string CompleteString

        {

            get { return _applicationSettings.Custom; }

        }



        public string PartString

        {

            get { return "Deine Ergebnis aus Regex"; }

        }

    }



        [Test]

        public void Test1()

        {

            string expected = "";


            var applicationSettingsFake = A.Fake<IApplicationSettings>();

            applicationSettingsFake.CallsTo(settings => settings.Custom).Returns("Der Rückgabewert");


            var customViewModel = new CustomViewModel(applicationSettingsFake);

            var actual = customViewModel.PartString;


            Assert.That(expected, Is.EqualTo(actual));

        }

Ggf. kann man auch mehrere Testfälle je TestMethode abdecken. Dafür kann Du das TestCase Attribute verwenden.

Hier müsstest Du die aktuelle Url übergeben und das erwartete Resultat.

EDIT: Das Mock bzw. Fake-Objekt sollte, wenn benötigt, immer Daten für den jeweiligen Testfall liefern.

Bearbeitet von lbm1305
Link zu diesem Kommentar
Auf anderen Seiten teilen

Persönlich versuche ich Teile, die aus dem .NET Framework kommen, zu wrappen.

Wie meinst du das? Es macht sicherlich sinn, eine abstraktion auf den etwas unzulänglichen ConfigurationManager zu legen, aber grundsätzlich würde ich nicht alles wrappen was aus dem Framework kommt. Dazu kommt, dass man das Framework nicht testen muss ;)

Ggf. kann man auch mehrere Testfälle je TestMethode abdecken. Dafür kann Du das TestCase Attribute verwenden.

Das würde ich tunlichst vermeiden, bei komplexeren Problemen führt das zu unter umständen unklaren ergebnissen und verwirrung.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Wie meinst du das? Es macht sicherlich sinn, eine abstraktion auf den etwas unzulänglichen ConfigurationManager zu legen, aber grundsätzlich würde ich nicht alles wrappen was aus dem Framework kommt. Dazu kommt, dass man das Framework nicht testen muss ;)

Es war sicherlich etwas ungünstig ausgedrückt. Sicherlich gehe ich nicht hin und wrappe alles, was das .NET Framework bietet.

Da Settings nicht nur aus einer *.config Datei kommen können, mach es hier Sinn, die Klasse ConfigurationManager wegzukapseln.

Das würde ich tunlichst vermeiden, bei komplexeren Problemen führt das zu unter umständen unklaren ergebnissen und verwirrung.

Kommt darauf an, wie man Tests organisiert. Ich persönlich nutzte aber auch nicht das TestCase-Attribute ;-)

Link zu diesem Kommentar
Auf anderen Seiten teilen

Und erneut vielen Danlk für die Antworten :)

Wie sich herausgestellt hat, war das mit dem Mocking scheinbar zu kompliziert gedacht (auch wenn es mit der Version von lbm1305 ebenfalls funktioniert hat).

Ich habe nun einfach in den einzelnen Test die Konfiguration auf den Wert gesetzt, den ich erwarte und führe ein CleanUp der Konfiguration nach jedem Test aus. Funktioniert nun wunderbar.

Vielen Dank für eure Hilfe!

Link zu diesem Kommentar
Auf anderen Seiten teilen

Dein Kommentar

Du kannst jetzt schreiben und Dich später registrieren. Wenn Du ein Konto hast, melde Dich jetzt an, um unter Deinem Benutzernamen zu schreiben.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung wiederherstellen

  Nur 75 Emojis sind erlaubt.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Editor leeren

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

Fachinformatiker.de, 2024 by SE Internet Services

fidelogo_small.png

Schicke uns eine Nachricht!

Fachinformatiker.de ist die größte IT-Community
rund um Ausbildung, Job, Weiterbildung für IT-Fachkräfte.

Fachinformatiker.de App

Download on the App Store
Get it on Google Play

Kontakt

Hier werben?
Oder sende eine E-Mail an

Social media u. feeds

Jobboard für Fachinformatiker und IT-Fachkräfte

×
×
  • Neu erstellen...