首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WPF 專案單元測試實戰:解決 STA 線程難題

     編輯:關於.NET

Visual Studio 內建測試工具對 WPF 專案支援有限(VS2005 不支援,VS2008 亦有限制),導致開發者難以對 UI 組件編寫單元測試。本文介紹如何使用 NUnit 搭配自定義 CrossThreadTestRunner 解決「呼叫線程必須為 STA」的經典問題,實現 WPF UI 組件的有效測試。

測試場景分類

1. 普通類別測試(非 UI 組件)

與 .NET 2.0 時代無異,直接使用 NUnit 標準寫法即可,無任何阻礙。

[TestFixture]
public class ClassTest {
    [Test]
    public void TestRun() {
        var obj = new ClassLibrary1.Class1();
        double expected = 9;
        double result = obj.GetSomeValue(3);
        Assert.AreEqual(expected, result);
    }
}

2. WPF UI 組件測試(核心難點)

直接實例化 Window 或 UserControl 會拋出異常:
"The calling thread must be STA, because many UI components require this"

解決方案:跨線程 STA 執行器

核心思路:創建一個獨立的 STA 線程,在該線程中執行測試代理,並正確傳遞異常堆疊資訊。

輔助類 CrossThreadTestRunner 完整實現

using System;
using System.Threading;
using System.Security.Permissions;
using System.Reflection;

namespace TestUnit {
    public class CrossThreadTestRunner {
        private Exception lastException;

        public void RunInMTA(ThreadStart userDelegate) {
            Run(userDelegate, ApartmentState.MTA);
        }

        public void RunInSTA(ThreadStart userDelegate) {
            Run(userDelegate, ApartmentState.STA);
        }

        private void Run(ThreadStart userDelegate, ApartmentState apartmentState) {
            lastException = null;
            Thread thread = new Thread(delegate() {
                try {
                    userDelegate.Invoke();
                } catch (Exception e) {
                    lastException = e;
                }
            });
            thread.SetApartmentState(apartmentState);
            thread.Start();
            thread.Join();

            if (ExceptionWasThrown())
                ThrowExceptionPreservingStack(lastException);
        }

        private bool ExceptionWasThrown() {
            return lastException != null;
        }

        [ReflectionPermission(SecurityAction.Demand)]
        private static void ThrowExceptionPreservingStack(Exception exception) {
            FieldInfo remoteStackTraceString = typeof(Exception).GetField(
                "_remoteStackTraceString",
                BindingFlags.Instance | BindingFlags.NonPublic);
            remoteStackTraceString.SetValue(exception, exception.StackTrace + Environment.NewLine);
            throw exception;
        }
    }
}

正確的測試代碼寫法

[TestFixture]
public class ClassTest {
    [Test]
    public void TestRun() {
        CrossThreadTestRunner runner = new CrossThreadTestRunner();
        runner.RunInSTA(delegate {
            Console.WriteLine(Thread.CurrentThread.GetApartmentState());
            var obj = new WindowsApplication1.Window1();
            double expected = 9;
            double result = obj.GetSomeValue(3);
            Assert.AreEqual(expected, result);
        });
    }
}

環境配置提醒

  1. 專案需引用 nunit.framework.dll。
  2. 測試類標註 [TestFixture],測試方法標註 [Test]。
  3. 編譯產出的組件使用 nunit.exe (或 nunit-console.exe) 執行。
  4. NUnit 詳細用法請參考官方文檔。

原理解析

WPF UI 組件依賴單執行緒單元 (STA) 模型。NUnit 預設在 MTA 線程中運行測試。CrossThreadTestRunner 通過顯式創建 STA 線程並同步等待 (Join),確保 UI 物件在正確的線程模型中建構與銷毀。反射修改 _remoteStackTraceString 私有欄位可保留原始堆疊追蹤,方便除錯定位。

此方案雖屬「權宜之計」,但在缺乏原生 WPF 測試支援的早期版本中極具實用價值。現代 .NET Core/.NET 5+ 專案建議改用 Microsoft.VisualStudio.TestPlatform 或 xUnit 搭配 WpfTestRunner 等原生支援方案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved