《软件测试自动化之道》读书笔记 之 基于Windows的UI测试

2014-09-25

测试自动化程序的任务
待测程序
测试程序
  启动待测程序
  获得待测程序主窗体的句柄
  获得有名字控件的句柄
  获得无名字控件的句柄
  发送字符给控件
  鼠标单击一个控件
  处理消息对话框
  处理菜单
  检查应用程序状态
  示例程序
参考

本章主要讲述如何使用底层的Windows自动化技术通过用户界面来测试应用程序。这些技术涉及Win32 API的调用(比如FindWindow()函数)以及想待测程序发送Windows消息(比如WM_LBUTTONUP)。

测试自动化程序的任务


返回

基于Windows的UI测试,逍遥完成的工作主要有以下三类:

  • 找到目标窗体/控件的句柄
  • 操作这个窗体/控件
  • 检测这个窗体/控件

待测程序


返回

待测程序是一个用来做颜色混合的应用程序,关键代码如下:

 1 using System;
2 using System.Windows.Forms;
3
4 namespace WinApp
5 {
6 public partial class Form1 : Form
7 {
8 public Form1()
9 {
10 InitializeComponent();
11 }
12
13 private void button1_Click(object sender, EventArgs e)
14 {
15 string tb = textBox1.Text;
16 string cb = comboBox1.Text;
17
18 if (tb == "<enter color>" || cb == "<pick>")
19 MessageBox.Show("You need 2 colors", "Error");
20 else
21 {
22 if (tb == cb)
23 listBox1.Items.Add("Result is " + tb);
24 else if (tb == "red" && cb == "blue" || tb == "blue" && cb == "red")
25 listBox1.Items.Add("Result is purple");
26 else
27 listBox1.Items.Add("Result is black");
28 }
29 }
30
31 private void exitToolStripMenuItem1_Click(object sender, EventArgs e)
32 {
33 this.Close();
34 }
35 }
36 }

 图1 AUT 

测试程序


返回

启动待测程序

 using System.Diagnostics;

         static void Main(string[] args)
{
//...
string path = "..\\..\\..\\WinApp\\bin\\Debug\\WinApp.exe";
Process p = Process.Start(path);
//...
}

获得待测程序主窗体的句柄

要获得待测程序主窗体的句柄,可使用FindWindow() Win32 API函数来解决这个问题。

FindWindow()的函数签名用C++来描述是:

HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName); 

Win32 API函数FindWindow()是Windows操作系统的一部分,是由传统的C++程序而不是用受控代码(managed code)编写的。它返回HWND(Handle of Window), 是一个窗体的句柄

C#要使用Win32 API函数FindWindow(),可通过.Net平台的invoke(P/Invoke)机制,P/Invoke相关特性位于System.Runtime.InteropServices命名空间内。

        [DllImport("user32.dll", EntryPoint = "FindWindow", CharSet = CharSet.Auto)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

DllImport是用来将特性化方法由非托管动态链接库 (DLL) 作为静态入口点公开。

说明[1]
  1、DllImport只能放置在方法声明上。
  2、DllImport具有单个定位参数:指定包含被导入方法的 dll 名称的 dllName 参数。
  3、DllImport具有五个命名参数:
   a、CallingConvention 参数指示入口点的调用约定。如果未指定 CallingConvention,则使用默认值 CallingConvention.Winapi。
   b、CharSet 参数指示用在入口点中的字符集。如果未指定 CharSet,则使用默认值 CharSet.Auto。
   c、EntryPoint 参数给出 dll 中入口点的名称。如果未指定 EntryPoint,则使用方法本身的名称。
   d、ExactSpelling 参数指示 EntryPoint 是否必须与指示的入口点的拼写完全匹配。如果未指定 ExactSpelling,则使用默认值 false。
   e、PreserveSig 参数指示方法的签名应当被保留还是被转换。当签名被转换时,它被转换为一个具有 HRESULT 返回值和该返回值的一个名为 retval 的附加输出参数的签名。如果未指定 PreserveSig,则使用默认值 true。
   f、SetLastError 参数指示方法是否保留 Win32"上一错误"。如果未指定 SetLastError,则使用默认值 false。
  4、它是一次性属性类。
  5、此外,用 DllImport 属性修饰的方法必须具有 extern 修饰符。

注意:.NET平台大大简化了数据类型的模型,从而提高了程序开发的效率。要使用P/Invoke机制,必须为Win32数据类型找到相应的 C#数据类型。

此示例中,在.NET坏境中,窗体句柄的类型是System.IntPtr,它是一个平台相关的类型,用来代表指针(内存地址)或者句柄。它对应于Win32的数据类型HWND。String对应于LPCTSTR。

C#与win32 api数据类型对照

参数:

  • lpClassName:是窗体类名称,更OOP中类无任何关系。是由系统生成的一个字符串,用来把相应的窗体注册到操作系统。因为窗体/控件类的名称并不具有唯一性,对查找一个窗体/控件并没有太大帮助。因此,在本示例中传给它null。
  • lpWindowName:是窗体的名称。也被叫做window title或者window caption。在windows form程序中,这个值通常称为form name。

获得待测程序主窗体的句柄示例代码如下:

 using System.Runtime.InteropServices;

         static void Main(string[] args)
{
//...
IntPtr mwh = FindMainWindowHandle("Form1", , );
//...
} [DllImport("user32.dll", EntryPoint = "FindWindow", CharSet = CharSet.Auto)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName); static IntPtr FindMainWindowHandle(string caption, int delay, int maxTries)
{
return FindTopLevelWindow(caption, delay, maxTries);
} static IntPtr FindTopLevelWindow(string caption, int delay, int maxTries)
{
IntPtr mwh = IntPtr.Zero;
bool formFound = false;
int attempts = ; do
{
//FindWindow
mwh = FindWindow(null, caption);
if (mwh == IntPtr.Zero)
{
Console.WriteLine("Form not yet found");
Thread.Sleep(delay);
++attempts;
}
else
{
Console.WriteLine("Form has been found");
formFound = true;
}
} while (!formFound && attempts < maxTries); if (mwh != IntPtr.Zero)
return mwh;
else
throw new Exception("Could not find Main Window");
}

获得有名字控件的句柄

         static void Main(string[] args)
{
//...
IntPtr tb = FindWindowEx(mwh, IntPtr.Zero, null, "<enter color>");
//...
} [DllImport("user32.dll", EntryPoint = "FindWindowEx",CharSet = CharSet.Auto)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

参数:

  • hwndParent:控件目标的父窗体句柄
  • hwndChildAfter:从哪个控件开始找,即从下一个控件开始找
  • lpszClass:class name(如上参数lpClassName)
  • lpszWindow:目标控件的window name/title/caption

图2 Spy++捕获控件button1  

获得无名字控件的句柄

如何获得一个没有名字的空间的句柄,可通过隐含索引来查找相应控件

         static void Main(string[] args)
{
//...
IntPtr cb = FindWindowByIndex(mwh, );
//...
} [DllImport("user32.dll", EntryPoint = "FindWindowEx",CharSet = CharSet.Auto)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); static IntPtr FindWindowByIndex(IntPtr hwndParent, int index)
{
if (index == )
return hwndParent;
else
{
int ct = ;
IntPtr result = IntPtr.Zero;
do
{
//FindWindowEx
result = FindWindowEx(hwndParent, result, null, null);
if (result != IntPtr.Zero)
++ct;
} while (ct < index && result != IntPtr.Zero); return result;
}
}

注意:这里索引的顺序是加入主窗体的顺序。见如下代码,主窗体的索引值为0,先后加入的控件button1的索引为1,comboBox1的索引为2,...

         private void InitializeComponent()
{
//...
this.Controls.Add(this.button1);
this.Controls.Add(this.comboBox1);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.menuStrip1);
this.Controls.Add(this.listBox1);
//...
}

你也可以通过工具“spy++”来查找先后。

发送字符给控件

SendMessage()的函数签名用C++签名如下:

LRESULT SendMessage(HWND HwND, UINT Msg, WPARAM wParam, LPARAM lParam); 
  • HwND:目标窗体/控件的句柄
  • Msg:要发给该控件的Window消息
  • wParam, lParam:它们的意思和数据类型取决于相应的Windows消息

本例中,我们要发送一个VM_CHAR消息。当按键按下时,VM_CHAR消息会发送给拥有键盘焦点的那个控件。实际上,VM_CHAR是一个Windows的常量符号,它定义为0x0102。wParam参数指定的是被按下按键的字符代码。lParam参数指定的是不同的按键状态码,比如重复次数、扫描码等。有了这些信息,就可以创建相应的C#签名:

        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern void SendMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);

发送字符给控件的示例代码:

         static void Main(string[] args)
{
//...
SendChars(tb, "red");
//...
} static void SendChars(IntPtr hControl, string s)
{
foreach (char c in s)
{
SendChar(hControl, c);
}
} static void SendChar(IntPtr hControl, char c)
{
uint WM_CHAR = 0x0102;
SendMessage1(hControl, WM_CHAR, c, );
}
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern void SendMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);

鼠标单击一个控件

PostMessage()的函数签名用C++签名如下:

BOOL PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM LParam);

PostMessage()和SendMessage()的参数列表完全一致,他们的不同是:SendMessage()会等相应的Windows消息之后才会返回;PostMessage()不会。
相应的C#签名:

        [DllImport("user32.dll", EntryPoint = "PostMessage", CharSet = CharSet.Auto)]
static extern bool PostMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);

鼠标单击一个控件的示例代码:

         static void Main(string[] args)
{
//...
ClickOn(okButt);
//...
} static void ClickOn(IntPtr hControl)
{
uint WM_LBUTTONDOWN = 0x0201;
uint WM_LBUTTONUP = 0x0202;
PostMessage1(hControl, WM_LBUTTONDOWN, , );
PostMessage1(hControl, WM_LBUTTONUP, , );
}
[DllImport("user32.dll", EntryPoint = "PostMessage", CharSet = CharSet.Auto)]
static extern bool PostMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);

处理消息对话框

消息对话框是一个上层(top-level)窗体,使用FindWindow()函数捕获它。

处理菜单

处理菜单的的示例代码:

         static void Main(string[] args)
{
//... //mwh: main window handle
IntPtr hMainMenu = GetMenu(mwh);
IntPtr hFile = GetSubMenu(hMainMenu, );
int iExit = GetMenuItemID(hFile, );
uint WM_COMMAND = 0x0111;
SendMessage2(mwh, WM_COMMAND, iExit, IntPtr.Zero);
//...
} [DllImport("user32.dll")] //
static extern IntPtr GetMenu(IntPtr hWnd); [DllImport("user32.dll")] //
static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos); [DllImport("user32.dll")] //
static extern int GetMenuItemID(IntPtr hMenu, int nPos); [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern void SendMessage2(IntPtr hWnd, uint Msg, int wParam, IntPtr lParam);
  • GetMenu():返回程序主菜单的句柄
  • GetSubMenu():返回子菜单的句柄
  • GetSubMenu():返回菜单项的索引值。

在该示例中选择File->Exit,并点击它。

图3 处理菜单

检查应用程序状态

使用VM_GETTEXT和SendMessage()获得控件状态

检查应用程序状态的示例代码

         static void Main(string[] args)
{
//...
uint VM_GETTEXT = 0x000D;
byte[] buffer=new byte[];
string text = null;
int numFetched = SendMessage3(tb, VM_GETTEXT, , buffer);
text = System.Text.Encoding.Unicode.GetString(buffer);
Console.WriteLine("Fetched " + numFetched + " chars");
Console.WriteLine("TextBox1 contains = " + text);
//...
}
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern int SendMessage3(IntPtr hWndControl, uint Msg, int wParam, byte[] lParam);

示例程序

 // Chapter 3 - Windows-Based UI Testing
// Example Program: WindowsUITest using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading; namespace WindowsUITest
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
try
{
Console.WriteLine("\nLaunching application under test"); string path = "..\\..\\..\\WinApp\\bin\\Debug\\WinApp.exe";
Process p = Process.Start(path); Console.WriteLine("\nFinding main window handle");
IntPtr mwh = FindMainWindowHandle("Form1", , );
Console.WriteLine("Main window handle = " + mwh); Console.WriteLine("\nFinding handles to textBox1, comboBox1");
Console.WriteLine(" button1, listBox1"); // you may want to add delays here to make sure Form has rendered
IntPtr tb = FindWindowEx(mwh, IntPtr.Zero, null, "<enter color>");
IntPtr cb = FindWindowByIndex(mwh, );
IntPtr butt = FindWindowEx(mwh, IntPtr.Zero, null, "button1");
IntPtr lb = FindWindowByIndex(mwh, ); if (tb == IntPtr.Zero || cb == IntPtr.Zero ||
butt == IntPtr.Zero || lb == IntPtr.Zero)
throw new Exception("Unable to find all controls");
else
Console.WriteLine("All control handles found"); Console.WriteLine("\nClicking button1");
ClickOn(butt); Console.WriteLine("Clicking away Error message box");
IntPtr mb = FindMessageBox("Error");
if (mb == IntPtr.Zero)
throw new Exception("Unable to find message box");
IntPtr okButt = FindWindowEx(mb, IntPtr.Zero, null, "OK");
if (okButt == IntPtr.Zero)
throw new Exception("Unable to find OK button");
ClickOn(okButt); Console.WriteLine("Typing 'red' and 'blue' to application");
SendChars(tb, "red"); Console.WriteLine("Check for textBox1");
uint VM_GETTEXT = 0x000D;
byte[] buffer = new byte[];
string text = null;
int numFetched = SendMessage3(lb, VM_GETTEXT, , buffer);
text = System.Text.Encoding.Unicode.GetString(buffer);
Console.WriteLine("Fetched " + numFetched + " chars");
Console.WriteLine("TextBox1 contains = " + text); ClickOn(cb);
SendChars(cb, "blue"); Console.WriteLine("Clicking on button1");
ClickOn(butt); Console.WriteLine("\nChecking listBox1 for 'purple'"); uint LB_FINDSTRING = 0x018F;
int result = SendMessage4(lb, LB_FINDSTRING, -, "Result is purple1");
if (result >= )
Console.WriteLine("\nTest scenario result = Pass");
else
Console.WriteLine("\nTest scenario result = *FAIL*"); Console.WriteLine("\nExiting app in 3 seconds . . . ");
//GetMenu not work
Thread.Sleep();
IntPtr hMainMenu = GetMenu(mwh);
IntPtr hFile = GetSubMenu(hMainMenu, );
int iExit = GetMenuItemID(hFile, );
uint WM_COMMAND = 0x0111;
SendMessage2(mwh, WM_COMMAND, iExit, IntPtr.Zero); Console.WriteLine("\nDone");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine("Fatal error: " + ex.Message);
}
} // Main() static IntPtr FindTopLevelWindow(string caption, int delay, int maxTries)
{
IntPtr mwh = IntPtr.Zero;
bool formFound = false;
int attempts = ; do
{
mwh = FindWindow(null, caption);
if (mwh == IntPtr.Zero)
{
Console.WriteLine("Form not yet found");
Thread.Sleep(delay);
++attempts;
}
else
{
Console.WriteLine("Form has been found");
formFound = true;
}
} while (!formFound && attempts < maxTries); if (mwh != IntPtr.Zero)
return mwh;
else
throw new Exception("Could not find Main Window");
} // FindTopLevelWindow() static IntPtr FindMainWindowHandle(string caption, int delay, int maxTries)
{
return FindTopLevelWindow(caption, delay, maxTries);
} static IntPtr FindMessageBox(string caption)
{
int delay = ;
int maxTries = ;
return FindTopLevelWindow(caption, delay, maxTries);
} static IntPtr FindWindowByIndex(IntPtr hwndParent, int index)
{
if (index == )
return hwndParent;
else
{
int ct = ;
IntPtr result = IntPtr.Zero;
do
{
result = FindWindowEx(hwndParent, result, null, null);
if (result != IntPtr.Zero)
++ct;
} while (ct < index && result != IntPtr.Zero); return result;
}
} // FindWindowByIndex() static void ClickOn(IntPtr hControl)
{
uint WM_LBUTTONDOWN = 0x0201;
uint WM_LBUTTONUP = 0x0202;
PostMessage1(hControl, WM_LBUTTONDOWN, , );
PostMessage1(hControl, WM_LBUTTONUP, , );
Thread.Sleep();
} static void SendChar(IntPtr hControl, char c)
{
uint WM_CHAR = 0x0102;
SendMessage1(hControl, WM_CHAR, c, );
} static void SendChars(IntPtr hControl, string s)
{
foreach (char c in s)
{
SendChar(hControl, c);
}
} // P/Invoke Aliases [DllImport("user32.dll", EntryPoint = "FindWindow", CharSet = CharSet.Auto)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll", EntryPoint = "FindWindowEx", CharSet = CharSet.Auto)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); // for WM_CHAR message
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern void SendMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam); // for WM_COMMAND message
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern void SendMessage2(IntPtr hWnd, uint Msg, int wParam, IntPtr lParam); // for WM_LBUTTONDOWN and WM_LBUTTONUP messages
[DllImport("user32.dll", EntryPoint = "PostMessage", CharSet = CharSet.Auto)]
static extern bool PostMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam); // for WM_GETTEXT message
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern int SendMessage3(IntPtr hWndControl, uint Msg, int wParam, byte[] lParam); // for LB_FINDSTRING message
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern int SendMessage4(IntPtr hWnd, uint Msg, int wParam, string lParam); // Menu routines
[DllImport("user32.dll")] //
static extern IntPtr GetMenu(IntPtr hWnd); [DllImport("user32.dll")] //
static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos); [DllImport("user32.dll")] //
static extern int GetMenuItemID(IntPtr hMenu, int nPos); } // class
} // ns

参考

[1] C# DllImport的用法

[2] C#与win32 api数据类型对照

《软件测试自动化之道》读书笔记 之 基于Windows的UI测试的更多相关文章

  1. 《软件测试自动化之道》读书笔记 之 基于反射的UI测试

    <软件测试自动化之道>读书笔记 之 基于反射的UI测试 2014-09-24 测试自动化程序的任务待测程序测试程序  启动待测程序  设置窗体的属性  获取窗体的属性  设置控件的属性  ...

  2. 《软件测试自动化之道》读书笔记 之 底层的Web UI 测试

    <软件测试自动化之道>读书笔记 之 底层的Web UI 测试 2014-09-28 测试自动化程序的任务待测程序测试程序  启动IE并连接到这个实例  如何判断待测web程序完全加载到浏览 ...

  3. 《软件测试自动化之道》读书笔记 之 SQL 存储过程测试

    <软件测试自动化之道>读书笔记 之 SQL 存储过程测试 2014-09-28 待测程序测试程序   创建测试用例以及测试结果存储  执行T-SQL脚本  使用BCP工具导入测试用例数据  ...

  4. 《软件测试自动化之道》读书笔记 之 XML测试

    <软件测试自动化之道>读书笔记 之 XML测试 2014-10-07 待测程序测试程序  通过XmlTextReader解析XML  通过XmlDocument解析XML  通过XmlPa ...

  5. 《Essential C++》读书笔记 之 基于对象编程风格

    <Essential C++>读书笔记 之 基于对象编程风格 2014-07-13 4.1 如何实现一个class 4.2 什么是Constructors(构造函数)和Destructor ...

  6. 【哲学角度看软件测试】要想软件“一想之美”,UI 测试少不了

    摘要:软件测试的最高层次需求是:UI测试,也就是这个软件"长得好不好看". 为了让读者更好地理解测试,我们从最基础的概念开始介绍.以一个软件的"轮回"为例,下图 ...

  7. <<google软件测试之道>>读书笔记

    以前一直从开发的角度来看待测试,看完这本书以后感觉错了,难怪之前公司的测试一直搭建不起来 1.开发人员,开发测试人员,测试人员 * 开发人员负责开发 * 开发测试人员近距离接触代码,负责编写测试用例, ...

  8. 图论——读书笔记(基于BFS广度优先算法的广度优先树)

    广度优先树 对于一个图G=(V,E)在跑过BFS算法的过程中会创建一棵广度优先树. 形式化一点的表示该广度 优先树的形成过程是这样的: 对于图G=(V,E)是有向图或是无向图, 和图中的源结点s, 我 ...

  9. <微软的软件测试之道>读书笔记3

    一.自动化的标准步骤: 1.环境初始化,并检查环境是否处于正确的状态,能否开始测试 2.执行步骤 3.判断结果,并将结果保存到其它地方以供检查分析 4.环境清理,清理本用例产生的垃圾(临时文件.环境变 ...

随机推荐

  1. 洛谷 P1057 传球游戏 【dp】(经典)

    题目链接:https://www.luogu.org/problemnew/show/P1057 题目描述 上体育课的时候,小蛮的老师经常带着同学们一起做游戏.这次,老师带着同学们一起做传球游戏. 游 ...

  2. serialVersionUID 序列化

    http://www.mkyong.com/java-best-practices/understand-the-serialversionuid/ 简单来说,Java的序列化机制是通过在运行时判断类 ...

  3. 618大促微服务、web、redis等的超时时间

    1. 最近因为大促原因线上服务不稳定,不稳定主要是redis经常超时并且数据为定时mGet方式获得 节点一多,所有服务节点同时获取数据访问量变大导致get取数据变慢因mGet会对数据进行锁住操作, 此 ...

  4. 4889: [Tjoi2017]不勤劳的图书管理员 树套树

    国际惯例的题面(Bzoj没有,洛谷找的):动态加权逆序对,一眼树套树.256MB内存,5e4范围,不虚不虚.首先把交换改成两个插入和两个删除.考虑插入和删除的贡献,就是统计前面比这个值大的数的数值和, ...

  5. Bzoj2673 3961: [WF2011]Chips Challenge 费用流

    国际惯例题面:如果我们枚举放几个零件的话,第二个限制很容易解决,但是第一个怎么办?(好的,这么建图不可做)考虑我们枚举每行每列最多放几个零件t,然后计算零件总数sum.这样如果可行的话,则有t*B&l ...

  6. 给Linux系统管理员准备的Nmap命令的29个实用范例

    map即网络映射器对Linux系统/网络管理员来说是一个开源且非常通用的工具.Nmap用于在远程机器上探测网络,执行安全扫描,网络审计和搜寻开放端口.它会扫描远程在线主机,该主机的操作系统,包过滤器和 ...

  7. windows 下重置 mysql 的 root 密码

    今天发现 WordPress 连接不上数据库,登录 window server 服务器查看,所有服务均运行正常. 使用 root 账号登录 mysql 数据库,结果提示密码不匹配.我突然意识到,服务器 ...

  8. Linux进程优先级系统——设置实时进程优先级

    前言 最近研发的产品出了点小bug,最后查到根本原因是,其中一个进程A使用基于FIFO的实时进程优先级,而另一个进程B是使用普通调度的进程优先级,而A和B两个进程是互相通信的,进程B会被饿死,而进程A ...

  9. Object类--equals方法

    equals方法 1.比较的是对象引用的是否指向同一块内存地址 public static void main(String[] args) { HuaWei huawei=new HuaWei(); ...

  10. python面向对象笔记

    一.封装(属性/私有方法/公有方法/静态方法/构造函数...) # 定义一个类 class Animal: # 私有成员(用_开头的约定为私有成员 - 注:仅仅是君子协定) _age = 0 # 构造 ...