2010-03-04 94 views
12

問題如何以編程方式從單元測試啓動WPF應用程序?

VS2010和TFS2010支持創建所謂Coded UI Tests。我已經發現的所有演示,從編碼UI測試開始時已經在後臺運行的WPF應用程序開始,或者使用絕對路徑啓動EXE。

但是,我想從單元測試代碼開始測試我的WPF應用程序。這樣它也可以在構建服務器和我的同行的工作副本上工作。

我該如何做到這一點?

我發現至今

一)這篇文章顯示how to start a XAML window。但那不是我想要的。我想啓動App.xaml,因爲它包含XAML資源,並且在代碼隱藏文件中有應用程序邏輯。

b)關於this post第二個屏幕截圖顯示開始

ApplicationUnterTest calculatorWindow = ApplicationUnderTest.Launch(...); 

一行在概念上是差不多就是我又找,但本例中使用絕對路徑的可執行文件。

c)A Google search for "Programmatically start WPF"也沒有幫助。

回答

1

我結束了使用ApplicationUnderTest.Launch(...)(MSDN)記錄與Microsoft測試管理自動化測試時自動創建。

4
MyProject.App myApp = new MyProject.App(); 
myApp.InitializeComponent(); 
myApp.Run(); 
4

我做在VS2008相似,手動創建使用UI間諜幫我找出控制和一些輔助方法測試的東西,沒有顯示,觸發按鈕點擊和屏幕上的驗證值。我使用Process對象啓動我在TestInitialize方法中測試的應用程序,並在TestCleanup方法中關閉該過程。我有很多方法可以確保清理過程完全關閉。至於絕對路徑問題,我只是編程查找當前路徑並追加我的應用程序的可執行文件。由於我不知道應用程序啓動需要多長時間,因此我在主窗口中放置了一個AutomationId,並將其設置爲「UserApplicationWindow」並等待它可見,當然,您可能還有其他可以等待的東西。最後,我使用MyTestClass作爲基類,併爲不同的測試擴展類。

[TestClass] 
public class MyTestClass 
{ 
    private Process _userAppProcess; 
    private AutomationElement _userApplicationElement ; 

    /// <summary> 
    /// Gets the current directory where the executables are located. 
    /// </summary> 
    /// <returns>The current directory of the executables.</returns> 
    private static String GetCurrentDirectory() 
    { 
     return Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase).AbsolutePath).Replace("%20", " "); 
    } 

    [TestInitialize] 
    public void SetUp() 
    { 
     Thread appThread = new Thread(delegate() 
     { 
      _userAppProcess = new Process(); 
      _userAppProcess.StartInfo.FileName =GetCurrentDirectory() + "\\UserApplication.exe"; 
      _userAppProcess.StartInfo.WorkingDirectory = DirectoryUtils.GetCurrentDirectory(); 
      _userAppProcess.StartInfo.UseShellExecute = false; 
      _userAppProcess.Start(); 
     }); 
     appThread.SetApartmentState(ApartmentState.STA); 
     appThread.Start(); 

     WaitForApplication(); 
    } 

    private void WaitForApplication() 
    { 
     AutomationElement aeDesktop = AutomationElement.RootElement; 
     if (aeDesktop == null) 
     { 
      throw new Exception("Unable to get Desktop"); 
     } 

     _userApplicationElement = null; 
     do 
     { 
      _userApplicationElement = aeDesktop.FindFirst(TreeScope.Children, 
       new PropertyCondition(AutomationElement.AutomationIdProperty, "UserApplicationWindow")); 
      Thread.Sleep(200); 
     } while ((_userApplicationElement == null || _userApplicationElement.Current.IsOffscreen)); 

    } 

    [TestCleanup] 
    public void CleanUp() 
    { 
     try 
     { 
      // Tell the application's main window to close. 
      WindowPattern window = _userApplicationElement.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern ; 
      window.Close(); 
      if (!_userAppProcess.WaitForExit(3000)) 
      { 
       // We waited 3 seconds for the User Application to close on its own. 
       // Send a close request again through the process class. 
       _userAppProcess.CloseMainWindow(); 
      } 

      // All done trying to close the window, terminate the process 
      _userAppProcess.Close(); 
      _userAppProcess = null; 
     } 
     catch (Exception ex) 
     { 
      // I know this is bad, but catching the world is better than letting it fail. 
     } 
    } 
} 
+0

謝謝!我喜歡WaitForApplication()部分。 – Lernkurve 2010-03-14 14:29:32

0

這裏就是我剛剛砍死在一起,在單元測試卡利微成功一點點:

[TestFixture] 
public class when_running_bootstrapper 
{ 
    [Test] 
    public void it_should_request_its_view_model() 
    { 
     TestFactory.PerformRun(b => 
      CollectionAssert.Contains(b.Requested, typeof(SampleViewModel).FullName)); 
    } 

    [Test] 
    public void it_should_request_a_window_manager_on_dotnet() 
    { 
     TestFactory.PerformRun(b => 
      CollectionAssert.Contains(b.Requested, typeof(IWindowManager).FullName)); 
    } 

    [Test] 
    public void it_should_release_the_window_manager_once() 
    { 
     TestFactory.PerformRun(b => 
      Assert.That(b.ReleasesFor<IWindowManager>(), Is.EqualTo(1))); 
    } 

    [Test] 
    public void it_should_release_the_root_view_model_once() 
    { 
     TestFactory.PerformRun(b => 
      Assert.That(b.ReleasesFor<SampleViewModel>(), Is.EqualTo(1))); 
    } 
} 

static class TestFactory 
{ 
    public static void PerformRun(Action<TestBootStrapper> testLogic) 
    { 
     var stackTrace = new StackTrace(); 
     var name = stackTrace.GetFrames().First(x => x.GetMethod().Name.StartsWith("it_should")).GetMethod().Name; 
     var tmpDomain = AppDomain.CreateDomain(name, 
      AppDomain.CurrentDomain.Evidence, 
      AppDomain.CurrentDomain.BaseDirectory, 
      AppDomain.CurrentDomain.RelativeSearchPath, 
      AppDomain.CurrentDomain.ShadowCopyFiles); 
     var proxy = (Wrapper)tmpDomain.CreateInstanceAndUnwrap(typeof (TestFactory).Assembly.FullName, typeof (Wrapper).FullName); 

     try 
     { 
      testLogic(proxy.Bootstrapper); 
     } 
     finally 
     { 
      AppDomain.Unload(tmpDomain); 
     } 
    } 
} 

[Serializable] 
public class Wrapper 
    : MarshalByRefObject 
{ 
    TestBootStrapper _bootstrapper; 

    public Wrapper() 
    { 
     var t = new Thread(() => 
      { 
       var app = new Application(); 
       _bootstrapper = new TestBootStrapper(app); 
       app.Run(); 
      }); 
     t.SetApartmentState(ApartmentState.STA); 
     t.Start(); 
     t.Join(); 
    } 

    public TestBootStrapper Bootstrapper 
    { 
     get { return _bootstrapper; } 
    } 
} 

[Serializable] 
public class TestBootStrapper 
    : Bootstrapper<SampleViewModel> 
{ 
    [NonSerialized] 
    readonly Application _application; 

    [NonSerialized] 
    readonly Dictionary<Type, object> _defaults = new Dictionary<Type, object> 
     { 
      { typeof(IWindowManager), new WindowManager() } 
     }; 

    readonly Dictionary<string, uint> _releases = new Dictionary<string, uint>(); 
    readonly List<string> _requested = new List<string>(); 

    public TestBootStrapper(Application application) 
    { 
     _application = application; 
    } 

    protected override object GetInstance(Type service, string key) 
    { 
     _requested.Add(service.FullName); 

     if (_defaults.ContainsKey(service)) 
      return _defaults[service]; 

     return new SampleViewModel(); 
    } 

    protected override void ReleaseInstance(object instance) 
    { 
     var type = instance.GetType(); 
     var t = (type.GetInterfaces().FirstOrDefault() ?? type).FullName; 

     if (!_releases.ContainsKey(t)) 
      _releases[t] = 1; 
     else 
      _releases[t] = _releases[t] + 1; 
    } 

    protected override IEnumerable<object> GetAllInstances(Type service) 
    { 
     throw new NotSupportedException("Not in this test"); 
    } 

    protected override void BuildUp(object instance) 
    { 
     throw new NotSupportedException("Not in this test"); 
    } 

    protected override void Configure() 
    { 
     base.Configure(); 
    } 

    protected override void OnExit(object sender, EventArgs e) 
    { 
     base.OnExit(sender, e); 
    } 

    protected override void OnStartup(object sender, System.Windows.StartupEventArgs e) 
    { 
     base.OnStartup(sender, e); 

     _application.Shutdown(0); 
    } 

    protected override IEnumerable<System.Reflection.Assembly> SelectAssemblies() 
    { 
     return new[] { typeof(TestBootStrapper).Assembly }; 
    } 

    public IEnumerable<string> Requested 
    { 
     get { return _requested; } 
    } 

    public uint ReleasesFor<T>() 
    { 
     if (_releases.ContainsKey(typeof(T).FullName)) 
      return _releases[typeof (T).FullName]; 
     return 0u; 
    } 
} 

[Serializable] 
public class SampleViewModel 
{ 
} 
0

,這可能不是很你想要什麼,但我也有類似的問題,我的WPF應用程序和其編碼的UI測試。在我的情況下,我正在使用TFS構建(通過Lab模板),其部署需要我們構建的輸出; MSI並將其安裝到目標上,然後對照已安裝的軟件運行測試。

現在,因爲我們想測試對我們增加測試安裝軟件初始化通過調用MSI API來獲取安裝文件夾在我們安裝的產品/組件的GUID開始我們測試GUI的方法。

這裏的代碼片段,記得從你的安裝程序替換您的產品和組件GUIDS)

/// <summary> 
    /// Starts the GUI. 
    /// </summary> 
    public void StartGui() 
    { 
     Console.WriteLine("Starting GUI process..."); 
     try 
     { 
      var path = this.DetectInstalledCopy(); 
      var workingDir = path; 
      var exePath = Path.Combine(path, "gui.exe"); 

      //// or ApplicationUnderTest.Launch() ??? 
      Console.Write("Starting new GUI process... "); 
      this.guiProcess = Process.Start(new ProcessStartInfo 
      { 
       WorkingDirectory = workingDir, 
       FileName = exePath, 
       LoadUserProfile = true, 
       UseShellExecute = false 
      }); 
      Console.WriteLine("started GUI process (id:{0})", this.guiProcess.Id); 
     } 
     catch (Win32Exception e) 
     { 
      this.guiProcess = null; 
      Assert.Fail("Unable to start GUI process; exception {0}", e); 
     } 
    } 

    /// <summary> 
    /// Detects the installed copy. 
    /// </summary> 
    /// <returns>The folder in which the MSI installed the GUI feature of the cortex 7 product.</returns> 
    private string DetectInstalledCopy() 
    { 
     Console.WriteLine("Looking for install directory of CORTEX 7 GUI app"); 
     int buffLen = 1024; 
     var buff = new StringBuilder(buffLen); 
     var ret = NativeMethods.MsiGetComponentPath(
      "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}", // YOUR product GUID (see WiX installer) 
      "{YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY}", // The GUI Installer component GUID 
      buff, 
      ref buffLen); 

     if (ret == NativeMethods.InstallstateLocal) 
     { 
      var productInstallRoot = buff.ToString(); 
      Console.WriteLine("Found installation directory for GUI.exe feature at {0}", productInstallRoot); 
      return productInstallRoot; 
     } 

     Assert.Fail("GUI product has not been installed on this PC, or not for this user if it was installed as a per-user product"); 
     return string.Empty; 
    } 

    /// <summary> 
    /// Stops the GUI process. Initially by asking nicely, then chopping its head off if it takes too long to leave. 
    /// </summary> 
    public void StopGui() 
    { 
     if (this.guiProcess != null) 
     { 
      Console.Write("Closing GUI process (id:[{0}])... ", this.guiProcess.Id); 
      if (!this.guiProcess.HasExited) 
      { 
       this.guiProcess.CloseMainWindow(); 
       if (!this.guiProcess.WaitForExit(30.SecondsAsMilliseconds())) 
       { 
        Assert.Fail("Killing GUI process, it failed to close within 30 seconds of being asked to close"); 
        this.guiProcess.Kill(); 
       } 
       else 
       { 
        Console.WriteLine("GUI process closed gracefully"); 
       } 
      } 

      this.guiProcess.Close(); // dispose of resources, were done with the object. 
      this.guiProcess = null; 
     } 
    } 

而這裏的API包裝代碼:

/// <summary> 
    /// Get the component path. 
    /// </summary> 
    /// <param name="product">The product GUI as string with {}.</param> 
    /// <param name="component">The component GUI as string with {}.</param> 
    /// <param name="pathBuf">The path buffer.</param> 
    /// <param name="buff">The buffer to receive the path (use a <see cref="StringBuilder"/>).</param> 
    /// <returns>A obscure Win32 API error code.</returns> 
    [DllImport("MSI.DLL", CharSet = CharSet.Unicode)] 
    internal static extern uint MsiGetComponentPath(
     string product, 
     string component, 
     StringBuilder pathBuf, 
     ref int buff); 
相關問題