具有委托和自定义模型的 QtTest 表视图
QtTest tableview with delegate and custom model
我正在尝试使用自定义模型和多个委托(组合框和旋转框)测试 QTableView
。
初始化测试用例
void TestGuiDelegateWithTableView::initTestCase()
{
user_data_t d{{"foo", 2}, {"bar", 4}, {"baz", 6}};
table = new QTableView;
table->setModel(new Model);
table->setItemDelegateForColumn(0, new DelegateCombobox(std::move(d)));
table->setItemDelegateForColumn(1, new DelegateSpinbox({-10., 10.}));
}
带有用户数据的组合框
Combobox 委托有一个构造函数,它采用 std::vector<std::pair<QString, int>>
显示和用户角色值的集合。
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override
{
auto* box{new QComboBox(parent)};
for (const auto& [text, user] : _data) {
box->addItem(text, user);
}
return box;
}
测试_data
方法完美运行(或者只是巧合):
QTest::addColumn<QTestEventList>("events");
QTest::addColumn<QString>("display");
QTest::addColumn<int>("user");
const auto cell = table->model()->index(0, 0);
const auto center = table->visualRect(cell).center();
{
QTestEventList events;
events.addMouseClick(Qt::LeftButton, Qt::NoModifier, center, 50);
events.addMouseDClick(Qt::LeftButton, Qt::NoModifier, center, 100);
events.addKeyPress(Qt::Key_Down);
events.addKeyPress(Qt::Key_Enter);
QTest::newRow("e1") << events << "foo" << 2;
}
但是当我尝试如下实现第二个或第三个组合框元素时:
{
QTestEventList events;
events.addMouseClick(Qt::LeftButton, Qt::NoModifier, center, 50);
events.addMouseDClick(Qt::LeftButton, Qt::NoModifier, center, 100);
events.addKeyPress(Qt::Key_Down, Qt::NoModifier, 10);
events.addKeyPress(Qt::Key_Down, Qt::NoModifier, 10);
events.addKeyPress(Qt::Key_Enter);
QTest::newRow("e2") << events << "bar" << 4;
}
{
QTestEventList events;
events.addMouseClick(Qt::LeftButton, Qt::NoModifier, center, 50);
events.addMouseDClick(Qt::LeftButton, Qt::NoModifier, center, 100);
events.addKeyPress(Qt::Key_Down);
events.addKeyPress(Qt::Key_Down);
events.addKeyPress(Qt::Key_Down);
events.addKeyPress(Qt::Key_Enter);
QTest::newRow("e3") << events << "baz" << 6;
}
它不起作用。
调用代码:
{
QFETCH(QTestEventList, events);
QFETCH(QString, display);
QFETCH(int, user);
QVERIFY2(table->viewport(), "Should be not empty");
events.simulate(table->viewport());
QCOMPARE(table->model()->index(0, 0).data(Qt::DisplayRole).toString(), display);
QCOMPARE(table->model()->index(0, 0).data(Qt::UserRole).toInt(), user);
}
带范围的 Spinbox
我也在尝试测试 spinbox:
QVERIFY2(table, "Should be not empty");
QVERIFY2(table->model(), "Should be not empty");
const auto cell = table->model()->index(0, 1);
const auto center = table->visualRect(cell).center();
QTest::mouseClick(table->viewport(), Qt::LeftButton, Qt::NoModifier, center);
QTest::mouseDClick(table->viewport(), Qt::LeftButton, Qt::NoModifier, center);
QTest::keyClicks(table->viewport()->focusWidget(),"-9.54");
QTest::keyPress(table->viewport()->focusWidget(), Qt::Key_Enter);
const auto actual = table->model()->index(0, 1).data(Qt::DisplayRole).toString();
QCOMPARE(actual, "-9.54");
型号
设置数据
bool setData(const QModelIndex& index, const QVariant& value, int role) override
{
if (const auto col = index.column(); col == 0) {
if (const auto row = index.row(); role == Qt::DisplayRole) {
_display[row] = value.toString();
return true;
}
else if (role == Qt::UserRole) {
_user[row] = value.toInt();
return true;
}
} else if (col == 1) {
if (role == Qt::DisplayRole) {
_spin[index.row()] = value.toDouble();
return true;
}
}
return false;
}
数据()
[[nodiscard]] QVariant data(const QModelIndex& index, int role) const override
{
if (const auto col = index.column(); col == 0) {
if (const auto row = index.row(); role == Qt::DisplayRole) {
return _display[row];
}
else if (role == Qt::UserRole) {
return _user[row];
}
} else if (col == 1 && role == Qt::DisplayRole) {
return _spin[index.row()];
}
return {};
}
私有存储字段:
private:
std::array<QString, 3> _display{};
std::array<int, 3> _user{};
std::array<double, 3> _spin{};
PS
文档和互联网中的示例未涵盖 Qt 框架的 mvc 模式中的委托。
来自源代码的解决方案
在研究了 qtbase
source code 测试用例后,我发现有必要使用 QCoreApplication::processEvents()
函数。
组合框
测试数据
void TestGuiDelegateWithTableView::not_empty_cell2_data()
{
QTest::addColumn<int>("index");
QTest::addColumn<QString>("display");
QTest::addColumn<int>("user");
QTest::newRow("foo 2") << 0 << "foo" << 2;
QTest::newRow("bar 4") << 1 << "bar" << 4;
QTest::newRow("baz 6") << 2 << "baz" << 6;
}
测试体
void TestGuiDelegateWithTableView::not_empty_cell2()
{
QVERIFY2(table, "Should be not empty");
const auto cell = table->model()->index(0, 0);
const auto center = table->visualRect(cell).center();
// begin common trash
auto* viewport = table->viewport();
QVERIFY2(viewport, "Should be not empty");
QTest::mouseClick(viewport, Qt::LeftButton, Qt::KeyboardModifiers(), center);
QTest::mouseDClick(viewport, Qt::LeftButton, Qt::KeyboardModifiers(), center);
QVERIFY2(viewport->focusWidget(), "Should be not empty");
// end common trash
QFETCH(int, index);
for (auto i = 0; i < index; ++i) QTest::keyPress(viewport->focusWidget(), Qt::Key_Down);
QTest::keyPress(viewport->focusWidget(), Qt::Key_Enter);
QCoreApplication::processEvents(); // <-- here is important
QFETCH(QString, display);
QFETCH(int, user);
const auto actual_display = table->model()->index(0, 0).data(Qt::DisplayRole).toString();
const auto actual_user = table->model()->index(0, 0).data(Qt::UserRole).toInt();
QCOMPARE(display, actual_display);
QCOMPARE(user, actual_user);
}
旋转框
测试数据
void TestGuiDelegateWithTableView::spinbox_data()
{
QTest::addColumn<double>("user_input");
QTest::newRow("-10.0") << -10.;
QTest::newRow("+10.0") << 10.;
QTest::newRow("0.0") << 0.0;
}
测试体
void TestGuiDelegateWithTableView::spinbox()
{
QVERIFY2(table, "Should be not empty");
QVERIFY2(table->model(), "Should be not empty");
QFETCH(double , user_input);
const auto cell = table->model()->index(0, 1);
const auto center = table->visualRect(cell).center();
QTest::mouseClick(table->viewport(), Qt::LeftButton, Qt::NoModifier, center);
QTest::mouseDClick(table->viewport(), Qt::LeftButton, Qt::NoModifier, center);
QTest::keyClicks(table->viewport()->focusWidget(), QString::number(user_input, 'g', 6));
QTest::keyPress(table->viewport()->focusWidget(), Qt::Key_Enter);
QCoreApplication::processEvents(); // here
const auto actual = table->model()->index(0, 1).data(Qt::DisplayRole).toDouble();
QVERIFY(qFuzzyCompare(actual, user_input));
}
注意
在 Qt 源代码中有以下几行:
#if defined(Q_OS_UNIX)
QCoreApplication::processEvents();
#endif
我正在尝试使用自定义模型和多个委托(组合框和旋转框)测试 QTableView
。
初始化测试用例
void TestGuiDelegateWithTableView::initTestCase()
{
user_data_t d{{"foo", 2}, {"bar", 4}, {"baz", 6}};
table = new QTableView;
table->setModel(new Model);
table->setItemDelegateForColumn(0, new DelegateCombobox(std::move(d)));
table->setItemDelegateForColumn(1, new DelegateSpinbox({-10., 10.}));
}
带有用户数据的组合框
Combobox 委托有一个构造函数,它采用 std::vector<std::pair<QString, int>>
显示和用户角色值的集合。
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override
{
auto* box{new QComboBox(parent)};
for (const auto& [text, user] : _data) {
box->addItem(text, user);
}
return box;
}
测试_data
方法完美运行(或者只是巧合):
QTest::addColumn<QTestEventList>("events");
QTest::addColumn<QString>("display");
QTest::addColumn<int>("user");
const auto cell = table->model()->index(0, 0);
const auto center = table->visualRect(cell).center();
{
QTestEventList events;
events.addMouseClick(Qt::LeftButton, Qt::NoModifier, center, 50);
events.addMouseDClick(Qt::LeftButton, Qt::NoModifier, center, 100);
events.addKeyPress(Qt::Key_Down);
events.addKeyPress(Qt::Key_Enter);
QTest::newRow("e1") << events << "foo" << 2;
}
但是当我尝试如下实现第二个或第三个组合框元素时:
{
QTestEventList events;
events.addMouseClick(Qt::LeftButton, Qt::NoModifier, center, 50);
events.addMouseDClick(Qt::LeftButton, Qt::NoModifier, center, 100);
events.addKeyPress(Qt::Key_Down, Qt::NoModifier, 10);
events.addKeyPress(Qt::Key_Down, Qt::NoModifier, 10);
events.addKeyPress(Qt::Key_Enter);
QTest::newRow("e2") << events << "bar" << 4;
}
{
QTestEventList events;
events.addMouseClick(Qt::LeftButton, Qt::NoModifier, center, 50);
events.addMouseDClick(Qt::LeftButton, Qt::NoModifier, center, 100);
events.addKeyPress(Qt::Key_Down);
events.addKeyPress(Qt::Key_Down);
events.addKeyPress(Qt::Key_Down);
events.addKeyPress(Qt::Key_Enter);
QTest::newRow("e3") << events << "baz" << 6;
}
它不起作用。
调用代码:
{
QFETCH(QTestEventList, events);
QFETCH(QString, display);
QFETCH(int, user);
QVERIFY2(table->viewport(), "Should be not empty");
events.simulate(table->viewport());
QCOMPARE(table->model()->index(0, 0).data(Qt::DisplayRole).toString(), display);
QCOMPARE(table->model()->index(0, 0).data(Qt::UserRole).toInt(), user);
}
带范围的 Spinbox
我也在尝试测试 spinbox:
QVERIFY2(table, "Should be not empty");
QVERIFY2(table->model(), "Should be not empty");
const auto cell = table->model()->index(0, 1);
const auto center = table->visualRect(cell).center();
QTest::mouseClick(table->viewport(), Qt::LeftButton, Qt::NoModifier, center);
QTest::mouseDClick(table->viewport(), Qt::LeftButton, Qt::NoModifier, center);
QTest::keyClicks(table->viewport()->focusWidget(),"-9.54");
QTest::keyPress(table->viewport()->focusWidget(), Qt::Key_Enter);
const auto actual = table->model()->index(0, 1).data(Qt::DisplayRole).toString();
QCOMPARE(actual, "-9.54");
型号
设置数据
bool setData(const QModelIndex& index, const QVariant& value, int role) override
{
if (const auto col = index.column(); col == 0) {
if (const auto row = index.row(); role == Qt::DisplayRole) {
_display[row] = value.toString();
return true;
}
else if (role == Qt::UserRole) {
_user[row] = value.toInt();
return true;
}
} else if (col == 1) {
if (role == Qt::DisplayRole) {
_spin[index.row()] = value.toDouble();
return true;
}
}
return false;
}
数据()
[[nodiscard]] QVariant data(const QModelIndex& index, int role) const override
{
if (const auto col = index.column(); col == 0) {
if (const auto row = index.row(); role == Qt::DisplayRole) {
return _display[row];
}
else if (role == Qt::UserRole) {
return _user[row];
}
} else if (col == 1 && role == Qt::DisplayRole) {
return _spin[index.row()];
}
return {};
}
私有存储字段:
private:
std::array<QString, 3> _display{};
std::array<int, 3> _user{};
std::array<double, 3> _spin{};
PS 文档和互联网中的示例未涵盖 Qt 框架的 mvc 模式中的委托。
来自源代码的解决方案
在研究了 qtbase
source code 测试用例后,我发现有必要使用 QCoreApplication::processEvents()
函数。
组合框
测试数据
void TestGuiDelegateWithTableView::not_empty_cell2_data()
{
QTest::addColumn<int>("index");
QTest::addColumn<QString>("display");
QTest::addColumn<int>("user");
QTest::newRow("foo 2") << 0 << "foo" << 2;
QTest::newRow("bar 4") << 1 << "bar" << 4;
QTest::newRow("baz 6") << 2 << "baz" << 6;
}
测试体
void TestGuiDelegateWithTableView::not_empty_cell2()
{
QVERIFY2(table, "Should be not empty");
const auto cell = table->model()->index(0, 0);
const auto center = table->visualRect(cell).center();
// begin common trash
auto* viewport = table->viewport();
QVERIFY2(viewport, "Should be not empty");
QTest::mouseClick(viewport, Qt::LeftButton, Qt::KeyboardModifiers(), center);
QTest::mouseDClick(viewport, Qt::LeftButton, Qt::KeyboardModifiers(), center);
QVERIFY2(viewport->focusWidget(), "Should be not empty");
// end common trash
QFETCH(int, index);
for (auto i = 0; i < index; ++i) QTest::keyPress(viewport->focusWidget(), Qt::Key_Down);
QTest::keyPress(viewport->focusWidget(), Qt::Key_Enter);
QCoreApplication::processEvents(); // <-- here is important
QFETCH(QString, display);
QFETCH(int, user);
const auto actual_display = table->model()->index(0, 0).data(Qt::DisplayRole).toString();
const auto actual_user = table->model()->index(0, 0).data(Qt::UserRole).toInt();
QCOMPARE(display, actual_display);
QCOMPARE(user, actual_user);
}
旋转框
测试数据
void TestGuiDelegateWithTableView::spinbox_data()
{
QTest::addColumn<double>("user_input");
QTest::newRow("-10.0") << -10.;
QTest::newRow("+10.0") << 10.;
QTest::newRow("0.0") << 0.0;
}
测试体
void TestGuiDelegateWithTableView::spinbox()
{
QVERIFY2(table, "Should be not empty");
QVERIFY2(table->model(), "Should be not empty");
QFETCH(double , user_input);
const auto cell = table->model()->index(0, 1);
const auto center = table->visualRect(cell).center();
QTest::mouseClick(table->viewport(), Qt::LeftButton, Qt::NoModifier, center);
QTest::mouseDClick(table->viewport(), Qt::LeftButton, Qt::NoModifier, center);
QTest::keyClicks(table->viewport()->focusWidget(), QString::number(user_input, 'g', 6));
QTest::keyPress(table->viewport()->focusWidget(), Qt::Key_Enter);
QCoreApplication::processEvents(); // here
const auto actual = table->model()->index(0, 1).data(Qt::DisplayRole).toDouble();
QVERIFY(qFuzzyCompare(actual, user_input));
}
注意
在 Qt 源代码中有以下几行:
#if defined(Q_OS_UNIX)
QCoreApplication::processEvents();
#endif