Тестирование пользовательского элемента управления, полученного из ComboBox
Я создал элемент управления, полученный из 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 в тесте, у меня даже не было настроено рисование поверхностей для рендеринга! Возможно, единственный ответ, который мне нужен, - "Как сделать обновление ComboBox таким же образом, как при рисовании, в сценарии модульного тестирования, где на самом деле мне не нужно рисовать прямоугольник?"
5 ответов
Поскольку вы просто вызываете конструктор, многие функциональные возможности комбинированного списка не будут работать. Например, элементы будут заполнены, когда ComboBox нарисован на экране, в форме. Этого не происходит при построении в модульном тесте.
Почему вы хотите написать модульный тест для этого списка?
Не можете ли вы отделить логику, которая сейчас находится в пользовательском элементе управления? Например, положить это в контроллере, и проверить это?
Почему бы вам не проверить свойство DataSource вместо коллекции Items?
Это решает некоторые проблемы, если целью является ComboBox или любой другой элемент управления:
target.CreateControl ();
но я не смог установить SelectedValue, он имеет нулевое значение, мой тест работал с двумя источниками данных для поля со списком, один в качестве источника данных, а второй привязан к выбранному значению. С другими элементами управления все работает нормально. В начале я также создавал форму в тестах, но есть проблема, когда форма создается на нашем сервере сборки во время выполнения тестов.
У меня та же проблема с полем со списком, где элементы привязаны к данным. Мое текущее решение - создать форму в тесте, добавить поле со списком в коллекцию 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);
}
Я уверен в том что Application.Run
отсутствие не может повлиять на поведение какого-либо контроля
Я сделал небольшой хак, чтобы разрешить это в своем собственном производном выпадающем списке:
public class EnhancedComboBox : ComboBox
{
[... the implementation]
public void DoRefreshItems()
{
SetItemsCore(DataSource as IList);
}
}
SetItemsCore
Функция указывает базовому списку для загрузки внутренних элементов с предоставленным списком, это то, что используется внутри после изменения источника данных.
Эта функция никогда не вызывается, когда элемент управления не находится в форме, потому что есть много проверок для CurrencyManager
с и BindingContext
s, которые терпят неудачу, потому что эти компоненты, я полагаю, предоставляются родительской формой так или иначе
Во всяком случае, в тесте, вы должны позвонить mECB.DoRefreshItems()
сразу после mECB.DataSource = mTestItems
и все должно быть хорошо, если вы зависите только от SelectedIndex
и Items
имущество. Любое другое поведение, такое как привязка данных, вероятно, все еще не работает.