代码之家  ›  专栏  ›  技术社区  ›  Lernkurve

如何从单元测试以编程方式启动WPF应用程序?

  •  12
  • Lernkurve  · 技术社区  · 15 年前

    问题

    VS2010和TFS2010支持创建所谓的 Coded UI Tests . 我找到的所有演示,从编码的UI测试开始时WPF应用程序已经在后台运行开始,或者使用指向它的绝对路径启动EXE开始。

    一、 但是,希望从单元测试代码启动正在测试的WPF应用程序。这样它也可以在构建服务器和我的同行的工作副本上工作。

    我该怎么做到?

    到目前为止我的发现

    a) 这篇文章显示 how to start a XAML window . 但那不是我想要的。我想启动App.xaml,因为它包含xaml资源,并且代码隐藏文件中有应用程序逻辑。

    b) 第二张截图 this post 显示以开头的行

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

    这在概念上几乎就是我要找的,除了这个例子再次使用了一个绝对路径可执行文件。

    c) 一个 Google search for "Programmatically start WPF" 也没用。

    5 回复  |  直到 15 年前
        1
  •  5
  •   Lernkurve    15 年前
    MyProject.App myApp = new MyProject.App();
    myApp.InitializeComponent();
    myApp.Run();
    
        2
  •  4
  •   RodKnee    15 年前

    我在VS2008中做了类似的事情,并使用UI-Spy手动创建测试,以帮助我识别控件和一些未显示的助手方法,以触发按钮单击并验证屏幕上的值。我使用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.
            }
        }
    }
    
        3
  •  1
  •   Lernkurve    15 年前

    我最终使用了ApplicationUnderTest.Launch(…)( MSDN )在使用Microsoft测试管理器录制自动测试时自动创建的。

        4
  •  0
  •   Henrik    13 年前

    以下是我刚刚在caliburn micro单元测试中成功破解的内容:

    [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
    {
    }
    
        5
  •  0
  •   Pete Stensønes    11 年前

    这可能不是你想要的,但是我的WPF应用程序和它们的编码UI测试也有类似的问题。在我的例子中,我使用TFS build(通过实验室模板),它的部署接受我们构建的输出;MSI将其安装到目标上,然后对安装的软件运行测试。

    现在因为我们想测试 安装 我们添加了测试初始化方法的软件,通过调用MSI API获取安装程序中产品/组件guid的安装文件夹来启动我们测试的GUI。

    下面是一个代码摘录,请记住从安装程序中替换产品和组件的guid)

        /// <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);