【问题标题】:Testing Custom Control derived from ComboBox测试从 ComboBox 派生的自定义控件
【发布时间】:2009-01-26 12:09:23
【问题描述】:

我创建了一个从 ComboBox 派生的控件,并希望对其行为进行单元测试。

但是,它在我的单元测试中的行为似乎与它在实际应用程序中的行为不同。

在实际应用程序中,Combobox.DataSource 属性和 .Items 同步 - 换句话说,当我更改 Combobox.DataSource 时,.Items 列表会立即自动更新以显示 DataSource 的每个元素的项目。

在我的测试中,我构造了一个 ComboBox,为其分配了一个数据源,但 .Items 列表根本没有更新,仍为 0 个项目。因此,当我尝试在测试中将 .SelectedIndex 更新为 0 以选择第一项时,我会收到 ArgumentOutOfRangeException。

这是因为我的单元测试中没有 Application.Run 来启动事件循环,还是这有点牵强附会?

编辑:第一次测试的更多细节:

    [SetUp]
    public void SetUp()
    {
        mECB = new EnhancedComboBox();

        mECB.FormattingEnabled = true;
        mECB.Location = new System.Drawing.Point( 45, 4 );
        mECB.Name = "cboFind";
        mECB.Size = new System.Drawing.Size( 121, 21 );
        mECB.TabIndex = 3;

        mECB.AddObserver( this );

        mTestItems = new List<TestItem>();
        mTestItems.Add( new TestItem() { Value = "Billy" } );
        mTestItems.Add( new TestItem() { Value = "Bob" } );
        mTestItems.Add( new TestItem() { Value = "Blues" } );

        mECB.DataSource = mTestItems;
        mECB.Reset();

        mObservedValue = null;
    }

    [Test]
    public void Test01_UpdateObserver()
    {
        mECB.SelectedIndex = 0;
        Assert.AreEqual( "Billy", mObservedValue.Value );
    }

在尝试将 SelectedIndex 设置为 0 时,第一行的测试失败。在调试时,这似乎是因为当 .DataSource 更改时,.Items 集合没有更新以反映这一点。但是,在调试实际应用程序时,.Items 集合总是在 .DataSource 更改时更新。

当然,我不必在测试中实际渲染 ComboBox,我什至没有设置任何绘图表面来渲染!也许我需要的唯一答案是“在我实际上不需要绘制框的单元测试场景中,如何以与绘制时相同的方式更新组合框?”

【问题讨论】:

    标签: c# .net winforms unit-testing custom-controls


    【解决方案1】:

    由于您只是调用构造函数,因此组合框的许多功能将不起作用。例如,当 ComboBox 在屏幕上或表单上绘制时,项目将被填充。在单元测试中构造它时不会发生这种情况。

    为什么要在该组合框上编写单元测试?

    你不能分离现在在自定义控件中的逻辑吗?例如把它放在一个控制器中,然后测试一下?

    为什么不对 DataSource 属性而不是 Items 集合进行测试?

    【讨论】:

    • 我很困惑“分离现在在自定义控件中的逻辑?自定义控件组合框。我要做的就是测试我的功能”已通过子类化添加,但基本组合框在测试中表现不同。如何使组合框表现“正常”?
    • 我的回答说组合框是由在表单上绘制引起的事件填充的。如果您像现在一样为您的组合框编写测试,那么您基本上是在测试它是否正确绘制。那么测试 DataSource 属性还不够吗?
    • 好的,需要更多细节。我的 EnhancedComboBox 维护一个观察者列表,当所选项目更改时它会通知该列表。因此,我让我的单元测试观察组合框,并将 SelectedIndex 设置为 0 以选择框中的第一项。这应该会导致我的测试收到通知
    • 所选项目的更改。它不起作用,因为“.SelectedIndex=0”会引发异常。对此进行调试,虽然 .DataSource 中有 3 个项目,但 .Items 是空的。在实际应用中,当 DataSource 发生变化时,.Items 会自动跟上,所以 ".SelectedIndex=0" 起作用。
    • 好的,我现在明白了。这在单元测试中仍然很难做到,因为它涉及填充由在真实表单上绘制的组合框触发的项目集合。也许你可以研究一下专门的 winforms 单元测试框架,我没有这方面的经验。
    【解决方案2】:

    我确信Application.Run 的缺席不会影响任何控件的行为

    【讨论】:

    • 那么为什么被测ComboBox的行为与实际应用程序中的行为不同?
    • 我认为 Application.Run 只在当前线程中作为主线程启动它。反射器代码: public static void Run() { ThreadContext.FromCurrent().RunMessageLoop(-1, newApplicationContext()); }
    【解决方案3】:

    我遇到了与项目绑定数据的组合框相同的问题。我目前的解决方案是在测试中创建一个表单,将组合框添加到 Controls 集合中,然后在我的测试中显示该表单。有点丑。我所有的组合框真正做的是列出一堆 TimeSpan 对象,排序,并具有 TimeSpan 值的自定义格式。它对按键事件也有特殊行为。我尝试将所有数据和逻辑提取到一个单独的类中,但无法弄清楚。可能有更好的解决方案,但我正在做的似乎令人满意。

    为了简化测试,我在测试代码中创建了这些类:

        class TestCombo : DurationComboBox {
            public void SimulateKeyUp(Keys keys) { base.OnKeyUp(new KeyEventArgs(keys)); }
            public DataView DataView { get { return DataSource as DataView; } }
            public IEnumerable<DataRowView> Rows() { return (DataView as IEnumerable).Cast<DataRowView>(); }
            public IEnumerable<int> Minutes() { return Rows().Select(row => (int)row["Minutes"]); }
        }
    
        class Target {
            public TestCombo Combo { get; private set; }
            public Form Form { get; private set; }
    
            public Target() {
                Combo = new TestCombo();
                Form = new Form();
                Form.Controls.Add(Combo);
                Form.Show();
            }
        }
    

    这是一个示例测试:

               [TestMethod()]
        public void ConstructorCreatesEmptyList() {
            Target t = new Target();
            Assert.AreEqual<int>(0, t.Combo.DataView.Count);
            Assert.AreEqual<int>(-1, t.Combo.SelectedMinutes);
            Assert.IsNull(t.Combo.SelectedItem);
        }
    

    【讨论】:

      【解决方案4】:

      如果目标是 ComboBox 或任何其他控件,这将解决一些问题:

      target.CreateControl();

      但我无法将 SelectedValue 设置为空值,我的测试使用组合框的两个数据源,一个作为数据源,第二个绑定到 selevted 值。使用其他控件一切正常。一开始我也在测试中创建表单,但是在执行测试时在我们的构建服务器上创建表单时出现问题。

      【讨论】:

        【解决方案5】:

        我做了一些小技巧,以便在我的自定义派生组合框中允许这样做:

        public class EnhancedComboBox : ComboBox 
        {
        
            [... the implementation]
        
            public void DoRefreshItems()
            {
                SetItemsCore(DataSource as IList);       
            }
        }
        

        SetItemsCore 函数指示基本组合框使用提供的列表加载内部项目,这是数据源更改后内部使用的内容。

        当控件不在表单上时,这个函数永远不会被调用,因为有很多对 CurrencyManagers 和 BindingContexts 的检查都失败了,因为我相信这个组件是由父表单以某种方式提供的.

        无论如何,在测试中,你必须在mECB.DataSource = mTestItems 之后调用mECB.DoRefreshItems(),如果你只依赖SelectedIndexItems 属性,一切都应该没问题。像数据绑定这样的任何其他行为可能仍然无法正常工作。

        【讨论】:

        • 不允许设置 SelectedValue。它保持为空。
        猜你喜欢
        • 1970-01-01
        • 2012-08-17
        • 1970-01-01
        • 1970-01-01
        • 2012-02-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多