考虑WinApi功能的以下单元测试:
public class WinApiTest
{
[TestMethod]
public void WinApiFindFormTest_SimpleNesting()
{
var form = new Form();
form.Text = @"My form";
var button = new Button();
button.Text = @"My button";
form.Controls.Add(button);
//with below line commented out, the test fails
form.Show();
IntPtr actualParent = WinApiTest.FindParent(button.Handle);
IntPtr expectedParent = form.Handle;
//below 2 lines were added for debugging purposes, they are not part of test
//and they don't affect test results
Debug.WriteLine("Actual: " + WinApi.GetWindowTitle(actualParent));
Debug.WriteLine("Expected: " + WinApi.GetWindowTitle(expectedParent));
Assert.AreEqual(actualParent, expectedParent);
}
//this is a method being tested
//please assume it's located in another class
//I'm not trying to test winapi
public static IntPtr FindParent(IntPtr child)
{
while (true)
{
IntPtr parent = WinApi.GetParent(child);
if (parent == IntPtr.Zero)
{
return child;
}
child = parent;
}
}
}
问题是要使其工作,我必须显示表单,即执行form.Show()
,否则,它会失败并显示此输出:
Actual: WindowsFormsParkingWindow
Expected: My form
Exception thrown: 'Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException' in Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll
我读到了这个神秘的 WindowsFormsParkingWindow ,只有在没有指定父级的情况下它似乎才有意义。所以没有父母的所有控件都存在于此窗口下。但是,在我的情况下,button
被明确指定为form
控件的一部分。
问题:是否有正确的方法让此测试通过?我正在尝试测试FindParent
方法。在真正的单元测试精神中,不应该突然在用户面前弹出任何东西。可以执行Show
和Hide
序列,但我认为这是一种解决问题的方法。
下面提供了WinApi类的代码 - 它没有给问题增加太多价值,但是如果你绝对必须看到它,那么它(主要部分来自this answer on SO):
public class WinApi
{
/// <summary>
/// Get window title for a given IntPtr handle.
/// </summary>
/// <param name="handle">Input handle.</param>
/// <remarks>
/// Major portition of code for below class was used from here:
/// https://stackoverflow.com/questions/4604023/unable-to-read-another-applications-caption
/// </remarks>
public static string GetWindowTitle(IntPtr handle)
{
if (handle == IntPtr.Zero)
{
throw new ArgumentNullException(nameof(handle));
}
int length = WinApi.SendMessageGetTextLength(handle, WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
if (length > 0 && length < int.MaxValue)
{
length++; // room for EOS terminator
StringBuilder windowTitle = new StringBuilder(length);
WinApi.SendMessageGetText(handle, WM_GETTEXT, (IntPtr)windowTitle.Capacity, windowTitle);
return windowTitle.ToString();
}
return String.Empty;
}
const int WM_GETTEXT = 0x000D;
const int WM_GETTEXTLENGTH = 0x000E;
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessageGetTextLength(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessageGetText(IntPtr hWnd, int msg, IntPtr wParam, [Out] StringBuilder lParam);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern IntPtr GetParent(IntPtr hWnd);
}
答案 0 :(得分:2)
当您访问Handle
属性时,需要创建窗口。子窗口需要具有父窗口,并且如果尚未创建父窗口,则创建子窗口,其中停放窗口作为其父窗口。只有在创建父窗口时,子窗口才会重新成为父级。
IntPtr actualParent = WinApiTest.FindParent(button.Handle);
IntPtr expectedParent = form.Handle;
当您访问button.Handle
时,会创建按钮的窗口,但由于尚未创建窗体的窗口,因此停放窗口是父窗口。处理此问题的最简单方法是确保在按钮窗口之前创建窗体的窗口。在按钮的句柄上调用form.Handle
之前,请务必参考GetParent
,例如在测试中,您可以颠倒分配顺序:
IntPtr expectedParent = form.Handle;
IntPtr actualParent = WinApiTest.FindParent(button.Handle);
显然你想要评论这段代码,以便未来的读者知道作业的顺序是至关重要的。
我确实不知道为什么你觉得有必要做这样的测试。我无法想象这种测试揭示了代码中的错误。