我编写了一个静态扩展方法,用于查找驻留在当前屏幕实例左/右/上方/下方的所有Screen实例。
/// <summary>Finds all screens in the specified directions.</summary>
/// <param name="source">The screen to search around.</param>
/// <param name="directions">The directions to search in.</param>
/// <param name="excludeScreens">Any number of screens to exclude. The source screen is always excluded.</param>
/// <returns>A <see cref="T:Collection{T}"/> of <see cref="Screen"/> containing the found screens.</returns>
public static Collection<Screen> FindAll(this Screen source, ScreenSearchDirections directions, params Screen[] excludeScreens) {
if (source == null)
throw new ArgumentNullException("source");
// Always exclude the source screen.
if (excludeScreens == null)
excludeScreens = new[] { source };
else if (!excludeScreens.Contains(source))
excludeScreens = new List<Screen>(excludeScreens) { source }.ToArray();
// No direction is any direction.
if (directions == ScreenSearchDirections.None)
directions = ScreenSearchDirections.Any;
var result = new Collection<Screen>();
foreach (var screen in Screen.AllScreens.Where(screen => !excludeScreens.Contains(screen))) {
// These are "else if" because otherwise we might find the same screen twice if our directions search for example left and above and the screen
// satisfies both those conditions.
if (directions.HasFlag(ScreenSearchDirections.Left) && screen.Bounds.Right <= source.Bounds.Left)
result.Add(screen);
else if (directions.HasFlag(ScreenSearchDirections.Right) && screen.Bounds.Left >= source.Bounds.Right)
result.Add(screen);
else if (directions.HasFlag(ScreenSearchDirections.Above) && screen.Bounds.Bottom <= source.Bounds.Top)
result.Add(screen);
else if (directions.HasFlag(ScreenSearchDirections.Below) && screen.Bounds.Top >= source.Bounds.Bottom)
result.Add(screen);
}
return result;
}
对代码的建设性建议当然是受欢迎的。
我当然是对我的所有代码进行单元测试,在这种情况下我无法进行TDD(测试驱动开发),因为我无法理解应如何测试此操作。所以我编写了实现,希望在编写完成后能够搞清楚。
我仍然无法绕过这一个
由于Screen的.Net实现没有任何构造函数可以采用IScreen
接口,也没有开始的IScreen接口,我将如何进行我的测试设置可以欺骗我有...在我的系统上连接10个以上的屏幕/显示器进行测试?
我查看了Microsoft Fakes个shim示例,但它仍然没有下沉 问题是,如何通过覆盖Screen constructor伪造10多个屏幕?
根据我的实现,我只需要屏幕界限,所以我不认为我需要担心.Net中Screen
类的其他实现。只要我可以替换(shim)屏幕类的构造函数来将bounds字段设置为我将在我的设置中提供的那个我将是金色的,对吧?
当然,禁止某人在我的推理中发现了一个缺陷!
N.B ,虽然我很欣赏这里的一些人有不同的意见和看法,但我会谦卑地要求你保持谦虚,并以建设性的方式提出你的论点。如果我做错了,请告诉我如何解决这个问题 我一次又一次地在SE网络上提问,有人说我错了,却没有暗示我怎么能做对。谢谢你的考虑。
答案 0 :(得分:2)
在查看this blog entry(使用内部构造函数实例化类)后,我终于相信我弄明白了。
我正在挠头,因为没有施工人员可以看到它们是如何内部的。
所以是的,在我意识到/通过反射提醒之前,没有办法创建更多的Screen对象实例。一个人可以创造僵尸。未初始化的类,然后通过反射在实例上设置值。这允许人们直接设置私人成员的值,这正是我所需要的。
无论如何,这张照片让我意识到我正在寻找的是什么。在看到它之前,我感到迷失了另一个关于假货和测试的页面。
嗯,图片和标题是的,你没听错我,没有调用任何构造函数就创建了一个对象。 文字......
在执行的这一点上,僵尸对象将跳跃到生命中,没有灵魂(或状态)。
你应该关注的第一件事是插入私有字段的一些值,这些值将为null并执行构造函数可能具有的任何关键卷。
我强烈建议您在使用Reflector之类的工具中研究目标对象的构造函数,然后再自行初始化。
注意这是一个草稿,我打算稍后重新使用该模型进行其他测试。
我并不需要在实现中更改任何内容以保持不变。
[TestMethod]
public void FindAll() {
// Arrange: Create mock source screen and a bunch of mock screen objects that we will use to override (shim) the Screen.AllScreens property getter.
// TODO: Move this to test class instanciation/setup.
// A collection of 12 rectangles specifying the custom desktop layout to perform testing on. First one representing the primary screen.
// In this list we imagine that all screens have the same DPI and that they are frameless.
// Screens are ordered Primary...Quinternary, those marked ??? have not yet had an 'identifier' assigned to them.
// Screens are named Primary for in front of user, then left of primary, right of primary, above primary and finally below primary. Closest screen to primary is selected.
var screenBounds = new Rectangle[] {
new Rectangle(0, 0, 2560, 1440), // Primary screen. In front of the user.
new Rectangle(-1920, 360, 1920, 1080), // Secondary screen. Immediately left of the Primary screen. Lower edge aligned.
new Rectangle(2560, 0, 2560, 1440), // Tertriary screen. Immediately right of the Primary screen.
new Rectangle(0, -720, 1280, 720), // Quaternary screen. Immediately above the Primary screen, left aligned.
new Rectangle(1280, -720, 1280, 720), // ??? screen. Immediately above the Primary screen, right aligned. (This is side by side with the previous screen)
new Rectangle(0, -2160, 2560, 1440), // ??? screen. Above the Quaternary screen and it's neighbor. Spans both those screens.
new Rectangle(-1920, -920, 960, 1280), // ??? screen. Above the Secondary screen, tilted 90 degrees, left aligned.
new Rectangle(-960, -920, 960, 1280), // ??? screen. Above the Secondary screen, tilted 90 degrees, right aligned. (This is side by side with the previous screen)
new Rectangle(0, 1440, 640, 480), // Quinary screen. Immediately below the Primary screen, left aligned.
new Rectangle(640, 1440, 640, 480), // ??? screen. Immediately right of the Quinary screen and immediately below the Primary screen. (This is side by side with the previous screen)
new Rectangle(1280, 1440, 640, 480), // ??? screen. Immediately below the Primary screen and rigth of the previous screen.
new Rectangle(1920, 1440, 640, 480), // ??? screen. Immediately below the Primary screen and rigth of the previous screen.
};
// Create a bunch of mock Screen objects.
var mockAllScreens = new Screen[12];
var mockScreenBoundsField = typeof(Screen).GetField("bounds", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (mockScreenBoundsField == null)
throw new InvalidOperationException("Couldn't get the 'bounds' field on the 'Screen' class.");
var mockScreenPrimaryField = typeof(Screen).GetField("primary", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (mockScreenPrimaryField == null)
throw new InvalidOperationException("Couldn't get the 'primary' field on the 'Screen' class.");
var mockScreenHMonitorField = typeof(Screen).GetField("hmonitor", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (mockScreenHMonitorField == null)
throw new InvalidOperationException("Couldn't get the 'hmonitor' field on the 'Screen' class.");
// TODO: Currently unused, create a collection of device names to assign from.
var mockScreenDeviceNameField = typeof(Screen).GetField("deviceName", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
if (mockScreenDeviceNameField == null)
throw new InvalidOperationException("Couldn't get the 'deviceName' field on the 'Screen' class.");
for (var mockScreenIndex = 0; mockScreenIndex < mockAllScreens.Length; mockScreenIndex++) {
// Create an uninitialized Screen object.
mockAllScreens[mockScreenIndex] = (Screen)FormatterServices.GetUninitializedObject(typeof(Screen));
// Set the bounds of the Screen object.
mockScreenBoundsField.SetValue(mockAllScreens[mockScreenIndex], screenBounds[mockScreenIndex]);
// Set the hmonitor of the Screen object. We need this for the 'Equals' method to compare properly.
// We don't need this value to be accurate, only different between screens.
mockScreenHMonitorField.SetValue(mockAllScreens[mockScreenIndex], (IntPtr)mockScreenIndex);
// If this is the first screen, it is also the primary screen in our setup.
if (mockScreenIndex == 0)
mockScreenPrimaryField.SetValue(mockAllScreens[mockScreenIndex], true);
}
// Act: Get all screens left of the primary display.
Collection<Screen> result;
using (ShimsContext.Create()) {
ShimScreen.AllScreensGet = () => mockAllScreens;
result = mockAllScreens[0].FindAll(ScreenSearchDirections.Left);
}
// Assert: Compare the result against the picked elements from our mocked screens.
var expected = new Collection<Screen> { mockAllScreens[1], mockAllScreens[6], mockAllScreens[7] };
CollectionAssert.AreEqual(expected, result);
}
像往常一样,我很乐意就我在实施和测试方法(ology)中可以改进的内容提出建议。
哦,作为奖励,这里是虚拟屏幕布局的样子,因为这也需要某种验证。 1/10比例。
将我自己的答案标记为解决方案。到目前为止,它创造奇迹。如果它破裂会告诉你。
答案 1 :(得分:1)
抱歉,由于许可证问题,我无法使用MS Shims编写示例。
我认为如何改进实现的一种方法是包装所有低级API。 使用ScreenFactory,直接使用AllScreens属性:
public class ScreensFactory
{
public List<ScreenBoundsWrapper> GetAllScreens()
{
return Screen.AllScreens
.Select(s => new ScreenBoundsWrapper(s))
.ToList();
}
}
所以你可以通过自定义逻辑模拟传递到处。
直接使用ScreenBoundsWrapper使用屏幕,因此您可以随意为没有真实屏幕的测试用例创建对象。
public class ScreenBoundsWrapper
{
public ScreenBoundsWrapper()
{
}
public ScreenBoundsWrapper(Screen screen)
{
screenInstance = screen;
}
public Screen ScreenInstance
{
get { return screenInstance; }
}
public virtual Rectangle Bounds
{
get { return ScreenInstance.Bounds; }
}
public override bool Equals(object obj)
{
var w = obj as ScreenBoundsWrapper;
if (w != null)
{
return w.ScreenInstance.Equals(screenInstance);
}
return obj.Equals(this);
}
protected bool Equals(ScreenBoundsWrapper other)
{
return Equals(screenInstance, other.screenInstance);
}
public override int GetHashCode()
{
return screenInstance == null ? 0 : screenInstance.GetHashCode();
}
private readonly Screen screenInstance;
}
我更改了您的扩展方法,例如:
public static class Extensions
{
public static Collection<ScreenBoundsWrapper> FindAllScreens(
this ScreenBoundsWrapper source,
ScreensFactory factory,
ScreenSearchDirections directions,
params ScreenBoundsWrapper[] excludeScreens)
{
if (source == null)
throw new ArgumentNullException("source");
// Always exclude the source screen.
if (excludeScreens == null)
excludeScreens = new[] { source };
else if (!excludeScreens.Contains(source))
excludeScreens = new List<ScreenBoundsWrapper>(excludeScreens) { source }.ToArray();
// No direction is any direction.
if (directions == ScreenSearchDirections.None)
directions = ScreenSearchDirections.Any;
var allScreens = factory.GetAllScreens();
allScreens.RemoveAll(excludeScreens.Contains);
var result = new Collection<ScreenBoundsWrapper>();
foreach (var screenWraper in allScreens)
{
// These are "else if" because otherwise we might find the same screen twice if our directions search for example left and above and the screen
// satisfies both those conditions.
if (directions.HasFlag(ScreenSearchDirections.Left) && screenWraper.Bounds.Right <= source.Bounds.Left)
result.Add(screenWraper);
else if (directions.HasFlag(ScreenSearchDirections.Right) && screenWraper.Bounds.Left >= source.Bounds.Right)
result.Add(screenWraper);
else if (directions.HasFlag(ScreenSearchDirections.Above) && screenWraper.Bounds.Bottom >= source.Bounds.Top)
result.Add(screenWraper);
else if (directions.HasFlag(ScreenSearchDirections.Below) && screenWraper.Bounds.Top <= source.Bounds.Bottom)
result.Add(screenWraper);
}
return result;
}
在测试项目中编写此测试方法(我使用moq库):
[TestMethod]
public void TestMethod1()
{
var s1 = MockScreenWraper(1, 1, 1, 1);
var s2 = MockScreenWraper(1, 3, 1, 1);
var list = new List<ScreenBoundsWrapper> { s1, s2 };
var mockScreenFactory = new Mock<ScreensFactory>();
mockScreenFactory
.Setup(m => m.GetAllScreens())
.Returns(() => list);
var factory = mockScreenFactory.Object;
var screenAbove = s1.FindAllScreens(factory, ScreenSearchDirections.Above);
Assert.AreSame(screenAbove.First(), s2);
}
private static ScreenBoundsWrapper MockScreenWraper(int x, int y, int w, int h)
{
var mock = new Mock<ScreenBoundsWrapper>();
mock.SetupGet(m => m.Bounds)
.Returns(() => new Rectangle(x, y, w, h));
mock.Setup(m => m.Equals(It.IsAny<ScreenBoundsWrapper>()))
.Returns<ScreenBoundsWrapper>(
o => ReferenceEquals(mock.Object, o));
return mock.Object;
}
}