发布新日志

  • 图片比较算法

    2008-12-31 11:11:32

    前几天其他项目组接了个项目,客户那边要求顺便给做个图片比较的工具,因为他们要看看同样的网页在IE7和IE8上显示有什么不同,不同的地方用用户指定的某种颜色标示出来,生成第三张图片。因为他们组比较忙,就帮他们做了做,UI界面就不用说了。

    起初的想法是这样,先比较两幅图片的hash散列,如果相同就不比较了,下面就是这个算法:

            /// <summary>
            /// compare the hash value of two image files.
            /// </summary>
            /// <param name="leftImage"></param>
            /// <param name="rightImage"></param>
            /// <returns></returns>
            private static bool HashCompare(Bitmap leftImage, Bitmap rightImage)
            {
                if (leftImage.Size != rightImage.Size)
                {
                    return false;
                }

                byte[] buffer = new byte[1];
                byte[] buffer2 = new byte[1];
                ImageConverter converter = new ImageConverter();
                buffer = (byte[])converter.ConvertTo(leftImage, buffer.GetType());
                buffer2 = (byte[])converter.ConvertTo(rightImage, buffer2.GetType());
                SHA256Managed managed = new SHA256Managed();
                byte[] buffer3 = managed.ComputeHash(buffer);
                byte[] buffer4 = managed.ComputeHash(buffer2);

                for (int i = 0; (i < buffer3.Length) && (i < buffer4.Length); i++)
                {
                    if (buffer3[i] != buffer4[i])
                    {
                        return false;
                    }
                }

                return true;
            }

    如果hash散列不同,就挨个像素比较,可这样有个问题,就是两幅图片有时候其实我们看起来是一样的,但是实际上你取他们像素的时候他们是不同的,如果我们将用户都不能分辨出不同的地方都标示出来意义不大,于是最后决定只比较像素的R.G.B,只要他们相同就行(这里的相同也做了妥协,就是两个像素的R.G.B值也允许有一定的误差),最后算法如下:

       /// <summary>
            /// compare two bitmaps.
            /// </summary>
            /// <param name="leftImage">the first bitmap</param>
            /// <param name="rightImage">the second bitmap</param>
            /// <param name="resultImageFullPath">the result bitmap</param>
            /// <returns>return the comparision result</returns>
            public static bool Compare(Bitmap leftImage, Bitmap rightImage, string resultImageFullPath)
            {
                bool matched = true;

                //if the two bitmaps are not the same size,then resize the large one.
                if (leftImage.Height > rightImage.Height)
                {
                    leftImage = BitMapAdapter.ResizeImage(leftImage, rightImage.Width, rightImage.Height);
                }
                else
                {
                    rightImage = BitMapAdapter.ResizeImage(rightImage, leftImage.Width, leftImage.Height);
                }

                if (!HashCompare(leftImage, rightImage))
                {
                    BitmapData leftBitmapdata = leftImage.LockBits(new Rectangle(0, 0, leftImage.Width, leftImage.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
                    BitmapData rightBitmapdata = rightImage.LockBits(new Rectangle(0, 0, rightImage.Width, rightImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

                    unsafe
                    {
                        byte* leftImageDataPointer = (byte*)(leftBitmapdata.Scan0.ToPointer());
                        byte* rightImageDataPointer = (byte*)(rightBitmapdata.Scan0.ToPointer());

                        for (int y = 0; y < leftBitmapdata.Height; y++)
                        {
                            for (int x = 0; x < leftBitmapdata.Width; x++)
                            {
                                if (Math.Abs((int)leftImageDataPointer[0] - (int)rightImageDataPointer[0]) > RGBDifference &&
                                    Math.Abs((int)leftImageDataPointer[1] - (int)rightImageDataPointer[1]) > RGBDifference &&
                                    Math.Abs((int)leftImageDataPointer[2] - (int)rightImageDataPointer[2]) > RGBDifference)
                                {
                                    matched = false;
                                    leftImageDataPointer[0] = (byte)mismatchedColor.B;
                                    leftImageDataPointer[1] = (byte)mismatchedColor.G;
                                    leftImageDataPointer[2] = (byte)mismatchedColor.R;
                                }

                                leftImageDataPointer += 3;
                                rightImageDataPointer += 3;
                            }

                            leftImageDataPointer += leftBitmapdata.Stride - leftBitmapdata.Width * 3;
                            rightImageDataPointer += rightBitmapdata.Stride - rightBitmapdata.Width * 3;
                        }
                    }

                    leftImage.UnlockBits(leftBitmapdata);
                    rightImage.UnlockBits(rightBitmapdata);

                    if (!matched)
                    {
                        leftImage.Save(resultImageFullPath, ImageFormat.Jpeg);
                    }

                    return matched;
                }
                else
                {
                    return true;
                }
            }

    但是最后又出来个问题,因为对同一个网页,IE7跟IE8上显示的竟然不是同一个尺寸,最后就又加了一个调整尺寸的函数(上面红色的就是后来加的),调整函数如下:

            /// <summary>
            /// Resize bitmap
            /// </summary>
            /// <param name="bmp">original Bitmap</param>
            /// <param name="newW">new width</param>
            /// <param name="newH">new height</param>
            /// <returns>worked bitmap</returns>
            public static Bitmap ResizeImage(Bitmap bmp, int newW, int newH)
            {
                try
                {
                    Bitmap bmap = new Bitmap(newW, newH);
                    Graphics graph = Graphics.FromImage(bmap);
                    graph.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    graph.DrawImage(bmp, new Rectangle(0, 0, newW, newH), new Rectangle(0, 0, bmp.Width, bmp.Height), GraphicsUnit.Pixel);
                    graph.Dispose();

                    return bmap;
                }
                catch
                {
                    return null;
                }
            }

  • 关于DLL

    2008-11-06 19:46:35

      -----转载    #pragma data_seg()

    1#pragma data_seg()一般用于DLL中。也就是说,在DLL中定义一个共享的,有名字的数据段。最关键的是:这个数据段中的全局变量可以被多个进程共享。否则多个进程之间无法共享DLL中的全局变量。

    2共享数据必须初始化,否则微软编译器会把没有初始化的数据放到.BSS段中,从而导致多个进程之间的共享行为失败。

    3,你所谓的结果正确是一种错觉。如果你在一个DLL中这么写:

    #pragma data_seg("MyData")

    int g_Value; // Note that the global is not initialized.

    #pragma data_seg()

    DLL
    提供两个接口函数:


    int GetValue()
    {
         return g_Value;
    }

    void SetValue(int n)
    {
         g_Value = n;
    }

    然后启动两个进程ABAB都调用了这个DLL,假如A调用了SetValue(5); B接着调用int m = GetValue(); 那么m的值不一定是5,而是一个未定义的值。因为DLL中的全局数据对于每一个调用它的进程而言,是私有的,不能共享的。假如你对g_Value进行了初始化,那么g_Value就一定会被放进MyData段中。换句话说,如果A调用了SetValue(5); B接着调用int m = GetValue(); 那么m的值就一定是5!这就实现了跨进程之间的数据通信!

  • c#的局部钩子编程

    2008-11-05 17:27:48

      自动化工具的录制是采用了钩子原理,来捕获用户的动作。钩子本质是一段消息处理程序,它又分全局钩子和局部钩子,局部钩子只能捕获放松到本程序的消息,而全局钩子则可以捕获所有的消息。不过在c#中,实现局部钩子倒是挺简单,但FrameWork不支持全局钩子,只能通过DLL实现。

      今天说说局部钩子:钩子程序的关键是其处理程序,它是一个回调函数,在c#中就是代理delegate。意思就是所有我想要截获的消息都要先发送到钩子的处理程序,再由钩子处理程序决定如何处理本消息,在处理函数中我可以取得消息类型,发送消息的来源,直接终止掉这个消息等。

    delegate int HookProc(int nCode,int wParam,IntPtr lParam)

    大部分的钩子程序只要3个API函数:SetWindowsHookEx(),UnHookWindowsHookEx(),CallNextHookEx()

     

  • ControlType.Document类型控件的取值

    2008-11-04 15:25:43

      昨天晚上看了看Text与Edit的区别,今天又查了查发现还有一个Document类。发现自己对Text的理解不够全面:Text只代表单行文本,如果允许多行的,像RichBox或允许多行的TextBox,都是Document。Document也没有ValuePattern属性,他用TextPattern属性取值,貌似还巨麻烦。 

      今天在自己写的小框架上加上了Document类,为了调试方便还加上了一个Highlight类(高亮显示某个控件),结果发现AutomationElement.Current.BoundingRectangle竟然是System.Windows.Rect类型的,而不是以前一直认为的System.Drawing.Rectangle类型的,这个类型在WindowsBase.dll中。

      另外一点就是Document既然代表了可以有滚动条的文本框,他的取值跟其他的完全不一样。

  • ControlType.Text与ControlType.Edit

    2008-11-03 19:39:40

      自从接触自动化库几天以来,我就一直对ControlType中的Text与Edit十分的疑惑,几天终于弄懂了他们的区别:

      他们相似的地方:他们都是既可以作为单独的控件,也可以作为其他控件的子控件。

      不同的地方:Text没有ValuePattern,因此,所有可编辑的编辑控件都是Edit。故而,像label类型的是属于Text的,而文本框一般都应该是Edit的。

  • 实现一个小自动化框架之一

    2008-11-03 13:49:06

     

    学自动化框架有一段时间了,这几天准备自己动手设计一个很小的框架。这个框架基本上算是模仿而已,为的只是实践一下所学的东西。而且出于对泄露商业机密的恐惧,所有的东西都是自己写的,因此,功能比较少目前的实现方式肯定也有问题。不过这是第一步,以后有可能再完善。

      由于框架很小,而且只是第一步,所以这个阶段期望能达到的结果是可以对下面这个暂时只有一个界面的程序实现自动化:

    (貌似不能插图片,这个程序很简单,就是一个实现相加运算的界面,3个文本框,一个按钮‘=’,一个标签‘+)

     

    一、初步框架结构

    暂时先分成两层:LitFoundationLitShell 

     

     Test Automating Project

     LitShell

     LitFoundation


                                                                       

      LitFoundation这层,先分成2个命名空间:LitFoundation.Core,它主要包含抽象控件类;LitFoundation.Utilities,目前包含鼠标、键盘、抓屏、读写文件以及高亮显示类。针对要测试的程序,LitFoundation.Core目前只准备实现3个类:基类LitWindow,以及他的两个派生类LitButtonLitEdit

      LitShellLitFoundation做简单的封装,只不过在控件类的操作方法中增加异常的捕获和写日志等操作。

  • 微软UI自动化库纵览之二

    2008-10-31 17:46:03

      控件模式提供方法、属性、事件以及关系,他用这些说明一个控件可以获得的功能。关系:描述了控件在控件树上的位置;方法:被Client用来操纵控件;属性与事件:提供关于控件模式的功能以及控件状态的信息。

    自动化Provider实现控件模式并暴露被控件支持的功能。自动化Client获取自动化控件模式类的属性和方法,并用其获得控件的相关信息以操纵控件。你可以在Automation命名空间下找到这些控件模式(像InvokePattern,SelectionPattern)。

      按照微软的说法,控件模式是动态的,他们举例如下:一个multiline的编辑框在行数比较少的时候,他的Scrolling是disable的,但是当行数超过可显示行数的时候,Scrolling属性就是enable的,所以呢,控件模式是动态的(貌似很NB的)。

     

      ok,说说自己对控件模式的理解。用控件模式的方式给一个文本框赋值。我们加入已经有了AutomationElement aeTextBox;如果我们想输入文本,要这样做:

     

       //----------------------------

      ValuePattern vpTextBox=(ValuePattern)aeTextBox.GetCurrentPattern (ValuePattern.Pattern);

      vpTextBox.SetValue("string");

       //-----------------------------

     

      我们知道,在通常情况下,我们如果对一个文本框赋值,一般是这样textbox1.text="",只不过到了自动化库中,他不允许你这样的做了,它加入了一个控件模式类,你想给文本框赋值?ok,先取得对象的ValuePattern对象,用ValuePattern的方法赋值;想选取对象?先取得他的SelectionPattern对象。就是说,控件模式抽象了控件的方法,成了操作控件的工具。

  • 微软UI自动化库纵览

    2008-10-31 14:55:51

      微软自动化类库指的是.net3.0以后的新加入FrameWork的System.Windows.Automation命名空间下的各个类。这个类库的出现为windows的用户界面自动化测试提供了一种新的途径。当然这种方法的限制是你的系统必须支持WPF.

      自动化库遮盖了控件在不同framework中的差异,例如:WPF按钮的Content属性、win32按钮的Caption属性、Html图片的ALT属性,在自动化库中都用Name属性来映射了。自动化库的体系结构使用Privider-Clients形式,也就是说,当你自动化测试的时候,你的测试程序在向被测试程序请求信息,类似与客户端-服务器模型。

      自动化库由4个组成部分,MSDN上介绍如下:

    Component Descrīption

    Provider API (UIAutomationProvider.dll and UIAutomationTypes.dll)

    A set of interface definitions that are implemented by UI Automation providers, objects that provide information about UI elements and respond to programmatic input.

    Client API (UIAutomationClient.dll and UIAutomationTypes.dll)

    A set of types for managed code that enables UI Automation client applications to obtain information about the UI and to send input to controls.

    UiAutomationCore.dll

    The underlying code (sometimes called the UI Automation core) that handles communication between providers and clients.

    UIAutomationClientsideProviders.dll

    A set of UI Automation providers for standard legacy controls. (WPF controls have native support for UI Automation.) This support is automatically available to client applications.

      作为自动化测试来说,我们应该只是与ClientAPI打交道(我猜得,^_^)。

    自动化控件模式(Pattern)

      控件模式(Control Pattern)提供了一种不依赖与控件的类型和外观来区分和展示控件功能的方法。自动化库使用控件模式(Control Pattern)来代表控件行为。比如:对具有滚动条的控件(可能是下拉框、列表框...)你可以使用Scroll控件模式,每一种控件模式代表了一种功能,因此我们可以用空间模式组合来代表一个具体的控件(如Combobox至少有弹出和选择这两种控件模式)。

      现在有点事,一会在另一篇中继续。。。

  • 一篇03年关于自动化框架的文章

    2008-10-31 13:31:30

      ---转载别人的一篇2003年的文章,使用Reflection进行的自动化。看了比较有感触,这应该是所谓的第二代测试框架,确实层次上挺混乱的,维护难度也较大,可用性不如现在的框架。不过至少是一种解决方法。

     

    VS .NET建立UI自动测试工具

     

    摘要:.NET框架组件为简单快速地建立用户界面测试自动化程序提供了一条令人惊奇的新途径。通过使用System.ReflectionSystem.Threading名字空间中的对象,你可以在几分钟内写好自动化测试程序。本文介绍了一个典型的基于Windows的应用程序,它将作为测试对象。接着建立了基于C#的测试工具,它模拟点击测试应用程序的UI控件并检查应用程序的状态。在工具建立后,仔细解释了它是怎样工作的,这样你就能在自己的应用中修改并扩充它。

      如果你试图写一个用户界面自动化测试程序,你会发现要化很长的时间并且很棘手。通过使用Reflection.NET框架组件中的ThreadPool对象,你能简单快速地编写强大的用户界面测试自动化程序。本文将介绍一个小的简单的基于Windows的应用程序的建立,强大的测试工具将演示这些.NET特性。

      问题

      假设你正在开发的Windows应用程序有标准的用户界面。Visual Studio .NET.NET框架组件使建立按钮、菜单项和所有其它控件很简单。当然,在开发工作中你会通过检测代码中的基本功能对用户界面执行隐含的手工测试。但是假定你希望建立一个自动化的测试,它将把检查用户界面做得更全面。如果你的产品设计是稳定的,并且你有很多时间和资源,解决的方法可能是购买专门的用户界面测试软件。虽然目前有几款好的工具,但是它们都有缺点。它们相对较贵,通常使用专有的脚本语言,并且如果产品变化很大将需要花费很长时间重作脚本。由于这些原因,该方法在很多开发环境中是不适用的,必须有更好的作UI测试自动化的途径。

      有了这种想法后,我开始建立一个UI测试自动化工具,它允许你在15分钟内、使用少于1页的代码建立一个测试脚本,可给新测试员使用,并且仅仅使用了.NET的功能,没有外部依赖。

      在做了一些试验后我发现.NET实际上提供了建立用户界面测试自动化程序的资源,它符合所有的三个设计目标。由于解决方案实现快速,你能在产品设计经常变化的情况下建立测试自动化程序;由于解决方案容易理解,与你一起工作的人能够使用很少的时间维护测试自动化代码;由于解决方案只使用了.NET代码,没有其它的外部依赖打断测试自动化过程。

      应用程序

      我们将建立一个简单的应用程序作为测试的基础。启动Visual Studio .NET并建立一个叫做MyWinAppC# Windows项目。从工具箱中添加三个按钮控件,一个文本框和一个列表框。所有控件的属性都不修改(见图1)。

      

      图1.一个简单的应用程序

      双击button1为该按钮记录一个事件处理程序并添加下面的代码,它在textBox1中显示"Hello World"

    private void button1_Click(object sender, System.EventArgs e)

    {

      textBox1.Text = "Hello World";

    }

    你可以看到,button1_Click有两个参数。这些参数很重要,在我编写测试自动化它们模拟点击按钮。Sender参数是产生相关事件的对象--在这种情况下是button1EventArgs参数是相关事件的附加信息。在按钮点击的情况下,没有必要的附加信息。

    双击button2并添加下面的代码,它在listBox1中显示两行消息:

    private void button2_Click(object sender,

      System.EventArgs e)

    {

      listBox1.Items.Add("Goodbye World");

      listBox1.Items.Add("Come back again!");

    }

    观察操作listBox1字段包含调用一个属性(Items)上的方法(Add)。当自动化时,我需要访问这些字段、属性和方法。

    最后,双击button3并添加下面的代码,它删除textBox1listBox1中的任何消息:

    private void button3_Click(object sender,

      System.EventArgs e)

    {

      textBox1.Text = "";

      listBox1.Items.Clear();

    }

    建立并运行该应用程序。该应用程序有不同的状态。初始状态是{textBox1 = "textBox1", listBox1 = (empty)},图2中的状态是{textBox1 = "Hello World", listBox1 = "Goodbye World / Come back again"}。当我编写测试自动化时,我必须能够检查状态。

      

      图2. 点击button2的状态

      解决方案

    现在我们建立一个简单但是强大的自动化测试工具,它模拟点击MyWinApp上的按钮,接着检查按钮的状态。启动Visual Studio .NET的一个新的实例,建立一个叫做MyWinAppTesterC# Windows应用程序。使用工具箱,添加三个按钮控件和两个文本框控件。把button1button2button3text属性相应设置为LaunchAppInvoke MethodRun Test

    这个用用户界面测试自动化程序的关键在System.ReflectionSystem.Threading名字空间。在代码视图中添加下面两条语句(前六条是Visual Studio .NET生成的):

    using System;

    using System.Drawing;

    using System.Collections;

    using System.ComponentModel;

    using System.Windows.Forms;

    using System.Data;

     

    using System.Reflection;

    using System.Threading;

    下面实现测试工具的核心。在项目的Main方法中添加图3所示的代码。在我解释这段代码前,先使测试自动化程序工作起来。双击LaunchApp按钮控件进入它的点击事件,并且双击Invoke MethodRun Test按钮。把图4中的代码添加到点击事件中。你需要在button1_Click中把MyWinApp.exe的路径修改为本机的路径。

    Assembly testAssembly = null;

    Form testForm = null;

    BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic |

                         BindingFlags.Static | BindingFlags.Instance;

     

    static void RunApp(object state) // 需要这个函数传递进来WaitCallBack()

    {

      Application.Run((Form)state);

    }

     

    private void InvokeMethod(Form form, string methodName,

                              params object[] parms)

    {

      EventHandler eh = (EventHandler)Delegate.CreateDelegate(typeof

                        (EventHandler), form, methodName);

      if (eh != null)

      {

        form.Invoke(eh, parms);

      }

    }

     

    private object GetField(object obj, string fieldName)

    {

      Type t = obj.GetType();

      FieldInfo fi = t.GetField(fieldName, flags);

      return fi.GetValue(obj);

    }

     

    private object GetProperty(object obj, string propertyName)

    {

      Type t = obj.GetType();

      PropertyInfo pi = t.GetProperty(propertyName, flags);

      return pi.GetValue(obj, new object[0]);

    }

      图3.测试工具核心的方法和对象

    // 载入MyWinApp

    private void button1_Click(object sender, System.EventArgs e)

    {

      testAssembly = Assembly.LoadFrom("C:\\MSDN\\MyWinApp\\bin\\Debug\\MyWinApp.exe");

      Type t = testAssembly.GetType("MyWinApp.Form1");

      testForm = (Form)testAssembly.CreateInstance(t.FullName);

      ThreadPool.QueueUserWorkItem(new WaitCallback(RunApp), testForm);

    }

     

    // 调用方法

    private void button2_Click(object sender, System.EventArgs e)

    {

      object[] p = {this, new EventArgs()};

      string meth = this.textBox1.Text.ToString();

      InvokeMethod(testForm, meth, p);

    }

     

    查看(601) 评论(0) 收藏 分享 管理

  • System.Windows.Automation

    2008-10-30 13:29:12

      今天看到了一篇介绍自动化框架的博客文章,基本思想是基于System.Windows.Automation自动化库可以做一个自动化框架,由于这几天事情比较多,准备11月中旬后开始看看这个库,如果项目不紧的话,要在年前看完,并实现一个小的框架。先写下来,省的以后忘了。