Laden...

[XUNIT 2.1] Thread-Konflikt im GUI Storyboard Tests debuggen

Erstellt von Blaster vor 7 Jahren Letzter Beitrag vor 7 Jahren 2.959 Views
B
Blaster Themenstarter:in
66 Beiträge seit 2013
vor 7 Jahren
[XUNIT 2.1] Thread-Konflikt im GUI Storyboard Tests debuggen

Hallo!

Ich habe eine App geschrieben, die unzählige Word- und RTF-Dokumente lädt und je nach Bereich und Art der Matkierungen unterschiedlichen Dialoge aufruft, die je nach Operationen weitere Dialoge aufrufen usw.

Für die Testautomation habe ich diverse Storyboards entwickelt, die bestimmte Files laden und die Auswahleinstellungen für den Dialog vorgeben, um die Resultate in XUnit Tests zu vergleichen.

Einzeln funktioneren die XUnit Tests hervorragend, aber wenn ich die gesamte TestSuite durchballern will, fallen Tests durch, wegen einer Verweigerung des Threadzugriffs:> Fehlermeldung:

Ergebnis Meldung: System.InvalidOperationException : Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da sich das Objekt im Besitz eines anderen Threads befindet.

Aufgrund der Stack-Trace ist die Ursache wahrscheinlich, dass ich eine Static Canvas nutze,


        public static Canvas canvas1 = new Canvas();

Daraus ergibt sch folgender Stack Trace für den Fehler:> Fehlermeldung:

bei System.Windows.Threading.Dispatcher.VerifyAccess()
bei System.Windows.DependencyObject.GetValue(DependencyProperty dp)
bei System.Windows.Controls.Panel.get_IsItemsHost()
bei System.Windows.Controls.ItemsControl.GetItemsOwnerInternal(DependencyObject element, ItemsControl& itemsControl)
bei System.Windows.Controls.Panel.VerifyBoundState()
bei System.Windows.Controls.Panel.get_InternalChildren()
bei System.Windows.Controls.Panel.get_Children()

Mein Xunit Test Code für nur zwei Dialogaufrufe sieht so aus:


using System.Threading;
using Xunit;
using Xunit.Abstractions;
[assembly: CollectionBehavior(DisableTestParallelization = false, MaxParallelThreads = 2)]

namespace XXXXX.Tests
{
    [Collection("Storyboards")]
    public class StoryBoardTests
    {
        public StoryBoardTests(ITestOutputHelper output)
        {
            this.output = output;
            this.affFileTest = new AffText_File_Test(output);
            this.bcm = new YYYYY();
        }

        private readonly ITestOutputHelper output;
        private AffText_File_Test affFileTest;
        private YYYYY bcm;

        [WpfTheory]
        // Marking an area and display
        [InlineData("Storyboard1_Test.rtf", 374, 819)]
        // pop upp MaessageBox for no Selection
        [InlineData("Storyboard1_Test.rtf", 374, 374)]
        public void LoadStoryboard_MarkSelection(string storyfile, int markStart, int markEnd)
        {
            /* To close test about GUI Dialog - Orginialtext Markierung!
            *
            */
            affFileTest.Load_Test(storyfile);
            affFileTest.affText.SetSelection(markStart, markEnd);

            bcm.AnalyseAroundMarkedText(affFileTest.affText.GetCurrentTextSelection());
            affFileTest.DisplayOutputCapture(affFileTest.affText.GetCurrentTextSelection().Text);

            Thread.Sleep(10000);
        }
    }
}

Die Testvektoren kann ich einzeln erfolgreich testen, aber nicht beide gleichzeitig.

Hat hier jemand eine Idee wie ich das lösen kann ? 🙁

[Artikel] Multi-Threaded Programmierung

16.807 Beiträge seit 2008
vor 7 Jahren

XUnit ermöglicht eigentlich keine parallelen Tests, sondern nur über Collections.
Das machst Du mit InlineData; durch InlineData werden diese beiden Test-Theorien parallel ausgeführt.

Du hast also sehr wahrscheinlich eine Thread-Verletzung der Felder zB. Deine "bcm" Variable.
Das, was hinter "bcm" steckt, ist wohl nicht Thread-Safe.

B
Blaster Themenstarter:in
66 Beiträge seit 2013
vor 7 Jahren

Hi Abt!

XUnit ermöglicht eigentlich keine parallelen Tests, sondern nur über Collections.

Öhh ... eigentlich schon! Ab V2.0+.
http://xunit.github.io/docs/running-tests-in-parallel.html

Das machst Du mit InlineData; durch InlineData werden diese beiden Test-Theorien parallel ausgeführt.

Klar! Die Tests laufen paralell bis die Timeouts erreicht sind oder eine Exception generieren. Das ist aber nicht das Problem. Weil du von der selben Assembly ausgehst. Deshalb habe ich im Asemblies oben einen zweiten Thread aufgemacht, um dadurch entstandene Probleme zu lösen.

Du hast also sehr wahrscheinlich eine Thread-Verletzung der Felder zB. Deine "bcm" Variable.Das, was hinter "bcm" steckt, ist wohl nicht Thread-Safe.

Eigentlich habe die statische Canvas festgelegt, damit ich ein Singleton habe.
Singleton (Entwurfsmuster)
Das die Threadsicherheit irgendwo gebrochen ist kann durchaus sein.

Im Moment wäre ich schon dankbar für Tipps, wie ich den Fehler finden kann, weil er nur gemeinsam mit dem zweiten Aufruf auftritt und so die Isolation des Threadkonfliktes schwierig ist.

In Systemen mit parallelen Abläufen (Threads) muss sichergestellt sein, dass nicht durch parallele Initialisierung kurzfristig mehr als eine Instanz existiert; und dass das Singleton-Objekt später auch die Verwendung in vielen parallelen Abläufen erlaubt, also threadsicher ist.

Das Testen eines Singleton kann kompliziert sein. Das Mocken eines Singleton-Objekts ist aufwändig und in manchen Fällen – zum Beispiel, wenn für Testzwecke Fehler erzeugt werden sollen – fast unmöglich. Mit der Java Reflection API ist es jedoch möglich, die Kapselung der Singleton zu verletzen und die Instanziierung zu kontrollieren.

B
Blaster Themenstarter:in
66 Beiträge seit 2013
vor 7 Jahren

Der intelligente User hätte auch auf die Idee kommen können die Parallelisierung einfach auszuschalten!! X( - Oh Mann!


[assembly: CollectionBehavior(DisableTestParallelization = true)]

Und brummt alles... 8)

Btw,
http://stackoverflow.com/questions/12316406/thread-safe-c-sharp-singleton-pattern
http://stackoverflow.com/questions/1408175/execute-unit-tests-serially-rather-than-in-parallel

Trotzdem Danke.

16.807 Beiträge seit 2008
vor 7 Jahren

Okay, Dein Link zeigt auch, dass die Collections parallel sind; aber das mit dem Threads scheint (mir) echt neu zu sein.
Die Ursache Deines Fehlers bleibt aber weiterhin die falsche Handhabung von Nicht-Threadsicheren Elementen.

B
Blaster Themenstarter:in
66 Beiträge seit 2013
vor 7 Jahren

Ich weiß das!
Die Frage ist, ob ich das Refactoring bezahlt bekomme. 😁

Btw,
Liste von Singleton-Implementierungen
https://automatetheplanet.com/singleton-design-pattern/
http://lambda-the-ultimate.org/node/2413
http://blog.bondigeek.com/2011/09/08/a-simple-c-thread-safe-logging-class/
XUnit Test Patterns!!!
http://xunitpatterns.com/index.html

Edit Fehler gefunden:
Beschrieben als beforefieldinit.
http://csharpindepth.com/Articles/General/Beforefieldinit.aspx

The C# specification implies that no types with static constructors should be marked with the beforefieldinit flag. Indeed, this is upheld by the compiler, but with a slightly odd effect. I suspect many programmers believe (as I did for a long time) that the following classes were semantically equivalent:

class Test
{
    static object o = new object();
}

class Test
{
    static object o;

    static Test()
    {
        o = new object();
    }
} 

Guilty as charged! :evil:

Edit:
Abschließend möchte ich noch meine Lösungen posten.
Im zu testenen Code habe ich die Singletonbildung für die Canvas wie folgt gebildet.


using System;
using System.Reflection;
using System.Windows.Controls;

namespace XXXXXX
{
    public abstract class SingletonReflectionConstruct<T>
    {
        public static T Instance
        {
            get
            {
                return SingletonFactory.Instance;
            }
        }

        internal static class SingletonFactory
        {
            internal static T Instance;

            static SingletonFactory()
            {
                CreateInstance(typeof(T));
            }

            public static T CreateInstance(Type type)
            {
                ConstructorInfo[] ctorsPublic = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public);

                if (ctorsPublic.Length > 0)
                {
                    throw new Exception(string.Concat(type.FullName, " has one or more public constructors so the property cannot be enforced."));
                }

                ConstructorInfo nonPublicConstructor =
                    type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[0], new ParameterModifier[0]);

                if (nonPublicConstructor == null)
                {
                    throw new Exception(string.Concat(type.FullName, " does not have a private/protected constructor so the property cannot be enforced."));
                }

                try
                {
                    return Instance = (T)nonPublicConstructor.Invoke(new object[0]);
                }
                catch (Exception e)
                {
                    throw new Exception(
                        string.Concat("The Singleton could not be constructed. Check if ", type.FullName, " has a default constructor."), e);
                }
            }
        }
    }

    public class BuildCanvas_Singleton : SingletonReflectionConstruct<BuildCanvas_Singleton>
    {
        private BuildCanvas_Singleton()
        {
            _canvas1 = new Canvas();
            _count++;
            
        }

        // Instance counter.
        private static int _count = 0;
        private static Canvas _canvas1; 

        public static int Count { get { return _count; } }
        public static Canvas GetCanvas { get { return _canvas1; } }
    }
}

Die Implementierungen des zu testenen Code.


        BuildCanvas_Singleton singleton1 = BuildCanvas_Singleton.Instance;

        public Canvas canvas1 = BuildCanvas_Singleton.GetCanvas;

Im Testcode brauche ich ebenfalls ein Singleton, andernfalls taucht ein** QUANTENBUG auf**.
P:= funktioniert, F:= versagte
Ohne Testsingleton der Testreihe
P 2x, F 1x, P 2x, F 3x, P 2x, F 1x, P 2x, F 2x, P 2x
Mit einem Testsingleton-Test
P 5x, F 1x, P 4x, F 2x, P 1x
Mit drei SingleonTests und prozesstechnisch nach vorn geschoben.
P 6x, F 2x, P 4x, F 1x, P 2x, F 1x, P 12+
Danach funktionierte der Test immer.
Fragt mich nicht wieso??? 8o


namespace XXXXXX.Tests
{
    public class TestSingleton : SingletonReflectionConstruct<TestSingleton>
    {
            private TestSingleton()
            {
                _bcm1 = new YYYYY();
            }

            private static YYYYY _bcm1;
     
            public static YYYYY GetBCM { get { return _bcm1; } }
    }

}

using System.Threading;
using Xunit;
using Xunit.Abstractions;
//[assembly: CollectionBehavior(DisableTestParallelization = false, MaxParallelThreads = 2)]
//[assembly: CollectionBehavior(DisableTestParallelization = true)]

namespace XXXXXX.Tests
{
    [Collection("Storyboards")]
    public class StoryBoardTests
    {
        public StoryBoardTests(ITestOutputHelper output)
        {
            this.output = output;
            testSingleton = TestSingleton.Instance;
            bcm = TestSingleton.GetBCM;
            affFileTest = new AffText_File_Test(output);
        }

        private readonly ITestOutputHelper output;       
        private YYYYY bcm;
        private TestSingleton testSingleton;
        private AffText_File_Test affFileTest;

        [WpfFact]       
        public void SingletonTest()
        {
            BuildCanvas_Singleton singleton1 = BuildCanvas_Singleton.Instance;
            BuildCanvas_Singleton singleton2 = BuildCanvas_Singleton.Instance;

            Assert.Equal(singleton1, singleton2);
        }

        [WpfFact]        
        public void CanvasSingletonTest()
        {           
            Assert.Equal(BuildCanvas_Singleton.GetCanvas, bcm.canvas1);
        }

        [WpfFact]
        public void CanvasTestSingletonTest()
        {
            TestSingleton testsingle1 = TestSingleton.Instance;
            TestSingleton testsingle2 = TestSingleton.Instance;

            Assert.Equal(testsingle1, testsingle2);
        }

        [WpfTheory]
        // Marking an area and display
        [InlineData("Storyboard1_Test.rtf", 374, 819)]
        // pop up MaessageBox for no Selection
        [InlineData("Storyboard1_Test.rtf", 374, 374)]
        public void LoadStoryboard_MarkSelection(string storyfile, int markStart, int markEnd)
        {
            /* To close test about GUI Dialog - Orginialtext Markierung!
            *
            */
            affFileTest.Load_Test(storyfile);
            affFileTest.affText.SetSelection(markStart, markEnd);

            bcm.AddPolygonAroundMarkedText(affFileTest.affText.GetCurrentTextSelection());
            affFileTest.DisplayOutputCapture(affFileTest.affText.GetCurrentTextSelection().Text);

            Thread.Sleep(10000);
        }
    }
}

Weiterhin kann ich die Testautomation Serie von Anton Angelov empfehlen mit allen Literaturverweisen! - Ein wirklich großer Meister. Habe wirklich viel gelernt.!!! 👍https://automatetheplanet.com/category/series/designpatterns/
Ladet den Code vom GITHub runter und studiert ihn in VS. Auch die ergänzenden Verweise sind großartig.