Explorar el Código

test: 统一测试目标和回归脚本

main
suyu hace 1 mes
padre
commit
ba6249bd4a
Se han modificado 28 ficheros con 643 adiciones y 503 borrados
  1. +3
    -2
      .gitignore
  2. +3
    -21
      app/tests/alarm_service_tests.cpp
  3. +18
    -29
      app/tests/alarm_service_tests.pro
  4. +2
    -7
      app/tests/domain_tests.cpp
  5. +8
    -23
      app/tests/domain_tests.pro
  6. +3
    -22
      app/tests/hmi_editor_service_tests.cpp
  7. +16
    -30
      app/tests/hmi_editor_service_tests.pro
  8. +25
    -21
      app/tests/logic_editor_service_tests.cpp
  9. +18
    -26
      app/tests/logic_editor_service_tests.pro
  10. +3
    -21
      app/tests/main_window_tests.cpp
  11. +24
    -93
      app/tests/main_window_tests.pro
  12. +2
    -7
      app/tests/offline_simulation_service_tests.cpp
  13. +13
    -22
      app/tests/offline_simulation_service_tests.pro
  14. +97
    -0
      app/tests/performance_tests.cpp
  15. +16
    -0
      app/tests/performance_tests.pro
  16. +3
    -20
      app/tests/plc_runtime_tests.cpp
  17. +15
    -42
      app/tests/plc_runtime_tests.pro
  18. +8
    -0
      app/tests/pri/test_defaults.pri
  19. +175
    -0
      app/tests/pri/test_layers.pri
  20. +2
    -7
      app/tests/project_management_tests.cpp
  21. +13
    -31
      app/tests/project_management_tests.pro
  22. +2
    -7
      app/tests/register_monitor_service_tests.cpp
  23. +9
    -16
      app/tests/register_monitor_service_tests.pro
  24. +3
    -20
      app/tests/runtime_mode_service_tests.cpp
  25. +12
    -36
      app/tests/runtime_mode_service_tests.pro
  26. +34
    -0
      app/tests/support/test_support.h
  27. +28
    -0
      app/tests/tests.pro
  28. +88
    -0
      scripts/run_qt_tests.ps1

+ 3
- 2
.gitignore Ver fichero

@@ -55,10 +55,11 @@ tmp/
Thumbs.db
Desktop.ini

/scripts
/scripts/*
!/scripts/run_qt_tests.ps1
/docs/images
/docs/类图Mermaid代码.md
/other
设计方案书.doc
/学习
/json
/json

+ 3
- 21
app/tests/alarm_service_tests.cpp Ver fichero

@@ -3,6 +3,7 @@
#include "services/alarm_editor_service.h"
#include "services/alarm_service.h"
#include "services/project_service.h"
#include "support/test_support.h"

#include <algorithm>
#include <exception>
@@ -12,27 +13,8 @@

namespace {

class TestProjectStorage final : public ProjectStorage
{
public:
ProjectSaveResult save(const Project &, const std::string &) override
{
return {true, ProjectStorageError::None, {}};
}

ProjectLoadResult load(const std::string &) override
{
return {false, {}, ProjectStorageError::FileReadFailed, {}};
}
};

void require(bool condition, const std::string &message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
using TestProjectStorage = TestSupport::InMemoryProjectStorage;
using TestSupport::require;

const AlarmRecord *findRecord(
const AlarmService &service, const std::string &definition_id)


+ 18
- 29
app/tests/alarm_service_tests.pro Ver fichero

@@ -1,35 +1,24 @@
TEMPLATE = app
TARGET = alarm_service_tests

CONFIG += console c++17 testcase warn_on
CONFIG -= app_bundle qt
include(pri/test_defaults.pri)
include(pri/test_layers.pri)

INCLUDEPATH += ../src
TARGET = alarm_service_tests
QT -= gui
CONFIG += testcase

SOURCES += \
alarm_service_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/alarm_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/hmi_control_registry.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
../src/services/project_service.cpp \
../src/services/alarm_editor_service.cpp \
../src/services/alarm_service.cpp
$$DOMAIN_REGISTER_SOURCES \
$$DOMAIN_HMI_SOURCES \
$$DOMAIN_LOGIC_SOURCES \
$$DOMAIN_PROJECT_SOURCES \
$$SERVICE_PROJECT_SOURCES \
$$SERVICE_ALARM_SOURCES

HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/alarm_model.h \
../src/domain/hmi_model.h \
../src/domain/hmi_control_registry.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \
../src/domain/project_storage.h \
../src/services/project_service.h \
../src/services/alarm_editor_service.h \
../src/services/alarm_service.h

QT -= gui
$$DOMAIN_REGISTER_HEADERS \
$$DOMAIN_HMI_HEADERS \
$$DOMAIN_LOGIC_HEADERS \
$$DOMAIN_PROJECT_HEADERS \
$$SERVICE_PROJECT_HEADERS \
$$SERVICE_ALARM_HEADERS \
$$TEST_SUPPORT_HEADERS

+ 2
- 7
app/tests/domain_tests.cpp Ver fichero

@@ -6,6 +6,7 @@
#include "domain/register_address.h"
#include "domain/register_repository.h"
#include "domain/runtime_state.h"
#include "support/test_support.h"

#include <cstdint>
#include <exception>
@@ -17,13 +18,7 @@

namespace {

void require(bool condition, const std::string &message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
using TestSupport::require;

void testRegisterAddressBoundaries()
{


+ 8
- 23
app/tests/domain_tests.pro Ver fichero

@@ -1,29 +1,14 @@
TEMPLATE = app
TARGET = domain_tests

CONFIG += console c++17 testcase warn_on
CONFIG -= app_bundle qt
include(pri/test_defaults.pri)
include(pri/test_layers.pri)

INCLUDEPATH += ../src
TARGET = domain_tests
QT -= gui
CONFIG += testcase

SOURCES += \
domain_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/alarm_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/hmi_control_registry.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
../src/domain/runtime_state.cpp
$$DOMAIN_ALL_SOURCES

HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/alarm_model.h \
../src/domain/hmi_model.h \
../src/domain/hmi_control_registry.h \
../src/domain/control_logic_model.h \
../src/domain/project_limits.h \
../src/domain/project_model.h \
../src/domain/runtime_state.h
$$DOMAIN_ALL_HEADERS \
$$TEST_SUPPORT_HEADERS

+ 3
- 22
app/tests/hmi_editor_service_tests.cpp Ver fichero

@@ -4,6 +4,7 @@
#include "services/hmi_runtime_service.h"
#include "services/hmi_navigation_service.h"
#include "services/project_service.h"
#include "support/test_support.h"

#include <exception>
#include <iostream>
@@ -12,28 +13,8 @@

namespace {

class TestProjectStorage final : public ProjectStorage
{
public:
// 编辑服务测试只关注内存模型变更,不依赖真实文件系统
ProjectSaveResult save(const Project &, const std::string &) override
{
return {true, ProjectStorageError::None, {}};
}

ProjectLoadResult load(const std::string &) override
{
return {false, {}, ProjectStorageError::FileReadFailed, {}};
}
};

void require(bool condition, const std::string &message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
using TestProjectStorage = TestSupport::InMemoryProjectStorage;
using TestSupport::require;

void testControlEditing()
{


+ 16
- 30
app/tests/hmi_editor_service_tests.pro Ver fichero

@@ -1,36 +1,22 @@
TEMPLATE = app
TARGET = hmi_editor_service_tests

CONFIG += console c++17 warn_on
CONFIG -= app_bundle
include(pri/test_defaults.pri)
include(pri/test_layers.pri)

INCLUDEPATH += ../src
TARGET = hmi_editor_service_tests

SOURCES += \
hmi_editor_service_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/alarm_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/hmi_control_registry.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
../src/services/project_service.cpp \
../src/services/hmi_editor_service.cpp \
../src/services/hmi_navigation_service.cpp \
../src/services/hmi_runtime_service.cpp
$$DOMAIN_REGISTER_SOURCES \
$$DOMAIN_HMI_SOURCES \
$$DOMAIN_LOGIC_SOURCES \
$$DOMAIN_PROJECT_SOURCES \
$$SERVICE_PROJECT_SOURCES \
$$SERVICE_HMI_SOURCES

HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/alarm_model.h \
../src/domain/hmi_model.h \
../src/domain/hmi_control_registry.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \
../src/domain/project_storage.h \
../src/services/project_service.h \
../src/services/editor_history.h \
../src/services/hmi_editor_service.h \
../src/services/hmi_navigation_service.h \
../src/services/hmi_runtime_service.h
$$DOMAIN_REGISTER_HEADERS \
$$DOMAIN_HMI_HEADERS \
$$DOMAIN_LOGIC_HEADERS \
$$DOMAIN_PROJECT_HEADERS \
$$SERVICE_PROJECT_HEADERS \
$$SERVICE_HMI_HEADERS \
$$TEST_SUPPORT_HEADERS

+ 25
- 21
app/tests/logic_editor_service_tests.cpp Ver fichero

@@ -2,6 +2,7 @@
#include "domain/project_limits.h"
#include "services/logic_editor_service.h"
#include "services/project_service.h"
#include "support/test_support.h"

#include <algorithm>
#include <iostream>
@@ -9,27 +10,8 @@

namespace {

class TestProjectStorage final : public ProjectStorage
{
public:
ProjectSaveResult save(const Project &, const std::string &) override
{
return {true, ProjectStorageError::None, {}};
}

ProjectLoadResult load(const std::string &) override
{
return {false, {}, ProjectStorageError::FileReadFailed, {}};
}
};

void require(bool condition, const std::string &message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
using TestProjectStorage = TestSupport::InMemoryProjectStorage;
using TestSupport::require;

ContactNodeConfig contact(int address)
{
@@ -299,11 +281,17 @@ void testConditionColumnLimit()
require(service.appendCondition(logic_id, rung_id, contact(column)).succeeded,
"the first ten condition columns must be editable");
}
require(project_service.saveAs("logic-editor-condition-limit.json").succeeded,
"the ten-column network must be saveable before testing overflow");
require(!project_service.isModified(),
"saving the ten-column network must clear the modified state");
const LogicEditorResult overflow = service.appendCondition(
logic_id, rung_id, contact(ProjectLimits::kMaximumConditionColumns));
require(!overflow.succeeded
&& overflow.error == LogicEditorError::InvalidOperation,
"the eleventh condition column must be rejected by the editor service");
require(!project_service.isModified(),
"a failed eleventh-column edit must preserve the saved state");
const LadderRung *rung = service.findRung(logic_id, rung_id);
require(rung != nullptr && rung->condition.has_value()
&& rung->condition->kind == ConditionExpressionKind::Series
@@ -311,6 +299,22 @@ void testConditionColumnLimit()
== static_cast<std::size_t>(
ProjectLimits::kMaximumConditionColumns),
"a rejected eleventh column must leave the ten-column network unchanged");

require(service.setOutput(
logic_id,
rung_id,
CoilNodeConfig{
RegisterAddress{RegisterArea::M, 20}, CoilMode::Normal},
true)
.succeeded
&& project_service.isModified(),
"a successful edit must make the project modified again");
require(!service.appendCondition(
logic_id, rung_id,
contact(ProjectLimits::kMaximumConditionColumns))
.succeeded
&& project_service.isModified(),
"a failed edit must preserve an existing modified state");
}

void testUnconditionalOutputEditing()


+ 18
- 26
app/tests/logic_editor_service_tests.pro Ver fichero

@@ -1,32 +1,24 @@
TEMPLATE = app
TARGET = logic_editor_service_tests

CONFIG += console c++17 testcase warn_on
CONFIG -= app_bundle qt
include(pri/test_defaults.pri)
include(pri/test_layers.pri)

INCLUDEPATH += ../src
TARGET = logic_editor_service_tests
QT -= gui
CONFIG += testcase

SOURCES += \
logic_editor_service_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/hmi_control_registry.cpp \
../src/domain/register_repository.cpp \
../src/domain/alarm_model.cpp \
../src/services/project_service.cpp \
../src/services/logic_editor_service.cpp
$$DOMAIN_REGISTER_SOURCES \
$$DOMAIN_HMI_SOURCES \
$$DOMAIN_LOGIC_SOURCES \
$$DOMAIN_PROJECT_SOURCES \
$$SERVICE_PROJECT_SOURCES \
$$SERVICE_LOGIC_SOURCES

HEADERS += \
../src/domain/register_address.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \
../src/domain/hmi_model.h \
../src/domain/hmi_control_registry.h \
../src/domain/register_repository.h \
../src/domain/alarm_model.h \
../src/domain/project_storage.h \
../src/services/project_service.h \
../src/services/editor_history.h \
../src/services/logic_editor_service.h
$$DOMAIN_REGISTER_HEADERS \
$$DOMAIN_HMI_HEADERS \
$$DOMAIN_LOGIC_HEADERS \
$$DOMAIN_PROJECT_HEADERS \
$$SERVICE_PROJECT_HEADERS \
$$SERVICE_LOGIC_HEADERS \
$$TEST_SUPPORT_HEADERS

+ 3
- 21
app/tests/main_window_tests.cpp Ver fichero

@@ -21,6 +21,7 @@
#include "ui/plc_connection_dialog.h"
#include "ui/runtime_monitor_widget.h"
#include "ui/runtime_monitor_window.h"
#include "support/test_support.h"

#include <QAction>
#include <QApplication>
@@ -55,20 +56,7 @@

namespace {

class TestProjectStorage final : public ProjectStorage
{
public:
// 主窗口测试使用无副作用存储实现,隔离文件对 UI 行为的影响
ProjectSaveResult save(const Project &, const std::string &) override
{
return {true, ProjectStorageError::None, {}};
}

ProjectLoadResult load(const std::string &) override
{
return {false, {}, ProjectStorageError::FileReadFailed, {}};
}
};
using TestProjectStorage = TestSupport::InMemoryProjectStorage;

class RepeatedStatusGateway final : public PlcCommunicationGateway
{
@@ -265,13 +253,7 @@ public:
std::function<void(const std::string &)> error_reported;
};

void require(bool condition, const std::string &message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
using TestSupport::require;

template<typename ObjectType>
ObjectType *requiredChild(MainWindow &window, const char *name)


+ 24
- 93
app/tests/main_window_tests.pro Ver fichero

@@ -1,101 +1,32 @@
QT += widgets serialport testlib
include(pri/test_defaults.pri)
include(pri/test_layers.pri)

TEMPLATE = app
TARGET = main_window_tests

CONFIG += console c++17 warn_on
CONFIG -= app_bundle

INCLUDEPATH += ../src
QT += widgets serialport testlib

SOURCES += \
main_window_tests.cpp \
../src/ui/main_window.cpp \
../src/ui/alarm_configuration_dialog.cpp \
../src/ui/register_comment_dialog.cpp \
../src/ui/plc_connection_dialog.cpp \
../src/ui/free_monitor_widget.cpp \
../src/ui/runtime_monitor_window.cpp \
../src/ui/runtime_monitor_widget.cpp \
../src/ui/toolbar_icon_factory.cpp \
../src/ui/project_workspace_controller.cpp \
../src/ui/property_panel_controller.cpp \
../src/ui/runtime_panel_controller.cpp \
../src/ui/hmi_editor_widget.cpp \
../src/ui/logic_editor_widget.cpp \
../src/ui/logic_instruction_dialog.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/active_register_repository.cpp \
../src/domain/register_monitor_model.cpp \
../src/domain/alarm_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/hmi_control_registry.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
../src/domain/runtime_state.cpp \
../src/services/project_service.cpp \
../src/services/alarm_editor_service.cpp \
../src/services/alarm_service.cpp \
../src/services/hmi_editor_service.cpp \
../src/services/hmi_navigation_service.cpp \
../src/services/logic_editor_service.cpp \
../src/services/hmi_runtime_service.cpp \
../src/services/software_logic_executor.cpp \
../src/services/offline_simulation_service.cpp \
../src/services/runtime_mode_service.cpp \
../src/services/register_monitor_service.cpp \
../src/services/register_comment_service.cpp

HEADERS += \
../src/ui/main_window.h \
../src/ui/alarm_configuration_dialog.h \
../src/ui/register_comment_dialog.h \
../src/ui/plc_connection_dialog.h \
../src/ui/free_monitor_widget.h \
../src/ui/runtime_monitor_window.h \
../src/ui/runtime_monitor_widget.h \
../src/ui/toolbar_icon_factory.h \
../src/ui/project_workspace_controller.h \
../src/ui/property_panel_controller.h \
../src/ui/runtime_panel_controller.h \
../src/ui/hmi_editor_widget.h \
../src/ui/logic_editor_widget.h \
../src/ui/logic_instruction_dialog.h \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/active_register_repository.h \
../src/domain/register_monitor_model.h \
../src/domain/alarm_model.h \
../src/domain/hmi_model.h \
../src/domain/hmi_control_registry.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \
../src/domain/project_storage.h \
../src/domain/runtime_state.h \
../src/services/project_service.h \
../src/services/editor_history.h \
../src/services/alarm_editor_service.h \
../src/services/alarm_service.h \
../src/services/hmi_editor_service.h \
../src/services/hmi_navigation_service.h \
../src/services/logic_editor_service.h \
../src/services/hmi_runtime_service.h \
../src/services/software_logic_executor.h \
../src/services/offline_simulation_service.h \
../src/services/runtime_mode_service.h \
../src/services/register_monitor_service.h \
../src/services/register_comment_service.h
$$UI_MAIN_SOURCES \
$$DOMAIN_ALL_SOURCES \
$$SERVICE_PROJECT_SOURCES \
$$SERVICE_ALARM_SOURCES \
$$SERVICE_HMI_SOURCES \
$$SERVICE_LOGIC_SOURCES \
$$SERVICE_OFFLINE_SOURCES \
$$SERVICE_RUNTIME_SOURCES \
$$SERVICE_MONITOR_SOURCES

HEADERS += \
../src/services/plc_communication_gateway.h
$$UI_MAIN_HEADERS \
$$DOMAIN_ALL_HEADERS \
$$SERVICE_PROJECT_HEADERS \
$$SERVICE_ALARM_HEADERS \
$$SERVICE_HMI_HEADERS \
$$SERVICE_LOGIC_HEADERS \
$$SERVICE_OFFLINE_HEADERS \
$$SERVICE_RUNTIME_HEADERS \
$$SERVICE_MONITOR_HEADERS \
../src/services/plc_communication_gateway.h \
$$TEST_SUPPORT_HEADERS

FORMS += \
../src/ui/main_window.ui \
../src/ui/alarm_configuration_dialog.ui \
../src/ui/register_comment_dialog.ui \
../src/ui/plc_connection_dialog.ui \
../src/ui/free_monitor_widget.ui \
../src/ui/runtime_monitor_window.ui \
../src/ui/runtime_monitor_widget.ui \
../src/ui/logic_instruction_dialog.ui
FORMS += $$UI_MAIN_FORMS

+ 2
- 7
app/tests/offline_simulation_service_tests.cpp Ver fichero

@@ -3,6 +3,7 @@
#include "services/hmi_runtime_service.h"
#include "services/offline_simulation_service.h"
#include "services/software_logic_executor.h"
#include "support/test_support.h"

#include <QCoreApplication>

@@ -14,13 +15,7 @@

namespace {

void require(bool condition, const std::string &message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
using TestSupport::require;

LogicNode contact(const std::string &id, int address,
ContactMode mode = ContactMode::NormallyOpen)


+ 13
- 22
app/tests/offline_simulation_service_tests.pro Ver fichero

@@ -1,31 +1,22 @@
QT += core
QT -= gui
include(pri/test_defaults.pri)
include(pri/test_layers.pri)

TEMPLATE = app
TARGET = offline_simulation_service_tests

CONFIG += console c++17 warn_on
CONFIG -= app_bundle

INCLUDEPATH += ../src
QT += core
QT -= gui

SOURCES += \
offline_simulation_service_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/hmi_model.cpp \
../src/domain/hmi_control_registry.cpp \
../src/domain/control_logic_model.cpp \
$$DOMAIN_REGISTER_SOURCES \
$$DOMAIN_HMI_SOURCES \
$$DOMAIN_LOGIC_SOURCES \
../src/services/hmi_runtime_service.cpp \
../src/services/software_logic_executor.cpp \
../src/services/offline_simulation_service.cpp
$$SERVICE_OFFLINE_SOURCES

HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/hmi_model.h \
../src/domain/hmi_control_registry.h \
../src/domain/control_logic_model.h \
$$DOMAIN_REGISTER_HEADERS \
$$DOMAIN_HMI_HEADERS \
$$DOMAIN_LOGIC_HEADERS \
../src/services/hmi_runtime_service.h \
../src/services/software_logic_executor.h \
../src/services/offline_simulation_service.h
$$SERVICE_OFFLINE_HEADERS \
$$TEST_SUPPORT_HEADERS

+ 97
- 0
app/tests/performance_tests.cpp Ver fichero

@@ -0,0 +1,97 @@
#include "domain/control_logic_model.h"
#include "domain/register_repository.h"
#include "services/software_logic_executor.h"

#include <QTest>

#include <string>
#include <vector>

namespace
{

LogicNode contact(const std::string &id, int address)
{
return {id,
ContactNodeConfig{
RegisterAddress{RegisterArea::M, address},
ContactMode::NormallyOpen},
true};
}

LogicNode coil(const std::string &id, int address)
{
return {id,
CoilNodeConfig{
RegisterAddress{RegisterArea::M, address},
CoilMode::Normal},
true};
}

ControlLogic makeLogic(int index)
{
LadderRung rung;
rung.id = "rung-" + std::to_string(index);
rung.name = rung.id;
rung.condition = ConditionExpression::fromNode(
contact("contact-" + std::to_string(index), index));
rung.output = coil("coil-" + std::to_string(index), index + 100);
return {"logic-" + std::to_string(index),
"logic-" + std::to_string(index),
{rung},
true};
}

class PerformanceTests final : public QObject
{
Q_OBJECT

private slots:
void softwareExecutor_scansRepresentativeProject()
{
std::vector<ControlLogic> logics;
logics.reserve(100);
for (int index = 0; index < 100; ++index)
{
logics.push_back(makeLogic(index));
}

SoftwareLogicExecutor executor;
VirtualRegisterRepository repository;
QVERIFY(executor.validate(logics).succeeded);
QVERIFY(executor.executeScan(logics, repository).succeeded);

QBENCHMARK
{
executor.executeScan(logics, repository);
}
}

void virtualRepository_handlesBoundaryWorkload()
{
VirtualRegisterRepository repository;
for (int index = 0; index <= RegisterAddress::kMaximumIndex; ++index)
{
QVERIFY(repository.writeBit(
{RegisterArea::M, index}, (index % 2) == 0)
.succeeded);
QVERIFY(repository.readBit({RegisterArea::M, index}).succeeded);
}

QBENCHMARK
{
for (int index = 0; index <= RegisterAddress::kMaximumIndex; ++index)
{
repository.writeBit(
{RegisterArea::M, index}, (index % 2) == 0);
repository.readBit({RegisterArea::M, index});
}
}
}
};

} // namespace

QTEST_APPLESS_MAIN(PerformanceTests)

#include "performance_tests.moc"

+ 16
- 0
app/tests/performance_tests.pro Ver fichero

@@ -0,0 +1,16 @@
include(pri/test_defaults.pri)
include(pri/test_layers.pri)

TARGET = performance_tests
QT += testlib

SOURCES += \
performance_tests.cpp \
$$DOMAIN_REGISTER_SOURCES \
$$DOMAIN_LOGIC_SOURCES \
../src/services/software_logic_executor.cpp

HEADERS += \
$$DOMAIN_REGISTER_HEADERS \
$$DOMAIN_LOGIC_HEADERS \
../src/services/software_logic_executor.h

+ 3
- 20
app/tests/plc_runtime_tests.cpp Ver fichero

@@ -3,6 +3,7 @@
#include "domain/project_storage.h"
#include "infrastructure/plc_communication_error_classifier.h"
#include "infrastructure/plc_communication_service.h"
#include "support/test_support.h"
#include "infrastructure/plc_register_repository.h"
#include "services/offline_simulation_service.h"
#include "services/plc_communication_gateway.h"
@@ -16,19 +17,7 @@

namespace {

class TestProjectStorage final : public ProjectStorage
{
public:
ProjectSaveResult save(const Project &, const std::string &) override
{
return {true, ProjectStorageError::None, {}};
}

ProjectLoadResult load(const std::string &) override
{
return {false, {}, ProjectStorageError::FileReadFailed, {}};
}
};
using TestProjectStorage = TestSupport::InMemoryProjectStorage;

class FakePlcGateway final : public PlcCommunicationGateway
{
@@ -130,13 +119,7 @@ public:
std::function<void(const std::string &)> error_reported;
};

void require(bool condition, const std::string &message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
using TestSupport::require;

void testPlcCacheAndWriteForwarding()
{


+ 15
- 42
app/tests/plc_runtime_tests.pro Ver fichero

@@ -1,51 +1,24 @@
QT += core serialbus serialport
include(pri/test_defaults.pri)
include(pri/test_layers.pri)

TEMPLATE = app
TARGET = plc_runtime_tests
QT += core serialbus serialport

CONFIG += console c++17 testcase warn_on
CONFIG -= app_bundle

INCLUDEPATH += ../src
CONFIG += testcase

SOURCES += \
plc_runtime_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/active_register_repository.cpp \
../src/domain/register_monitor_model.cpp \
../src/domain/alarm_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/hmi_control_registry.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
../src/domain/runtime_state.cpp \
../src/services/project_service.cpp \
../src/services/software_logic_executor.cpp \
../src/services/offline_simulation_service.cpp \
../src/services/runtime_mode_service.cpp \
../src/infrastructure/plc_communication_error_classifier.cpp \
../src/infrastructure/plc_register_repository.cpp \
../src/infrastructure/plc_communication_service.cpp
$$DOMAIN_ALL_SOURCES \
$$SERVICE_PROJECT_SOURCES \
$$SERVICE_OFFLINE_SOURCES \
$$SERVICE_RUNTIME_SOURCES \
$$INFRASTRUCTURE_PLC_SOURCES

HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/active_register_repository.h \
../src/domain/register_monitor_model.h \
../src/domain/alarm_model.h \
../src/domain/hmi_model.h \
../src/domain/hmi_control_registry.h \
../src/domain/control_logic_model.h \
../src/domain/project_limits.h \
../src/domain/project_model.h \
../src/domain/project_storage.h \
../src/domain/runtime_state.h \
../src/services/project_service.h \
../src/services/software_logic_executor.h \
../src/services/offline_simulation_service.h \
../src/services/runtime_mode_service.h \
$$DOMAIN_ALL_HEADERS \
$$SERVICE_PROJECT_HEADERS \
$$SERVICE_OFFLINE_HEADERS \
$$SERVICE_RUNTIME_HEADERS \
$$INFRASTRUCTURE_PLC_HEADERS \
../src/services/plc_communication_gateway.h \
../src/infrastructure/plc_communication_error_classifier.h \
../src/infrastructure/plc_register_repository.h \
../src/infrastructure/plc_communication_service.h
$$TEST_SUPPORT_HEADERS

+ 8
- 0
app/tests/pri/test_defaults.pri Ver fichero

@@ -0,0 +1,8 @@
# 所有测试目标共享的 qmake 基础配置
TEMPLATE = app
CONFIG += console c++17 warn_on
CONFIG -= app_bundle

INCLUDEPATH += $$PWD/../../src $$PWD/..

TEST_SUPPORT_HEADERS = $$PWD/../support/test_support.h

+ 175
- 0
app/tests/pri/test_layers.pri Ver fichero

@@ -0,0 +1,175 @@
# 测试按生产分层选择源码,避免每个 .pro 重复维护同一批路径

DOMAIN_REGISTER_SOURCES = \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp

DOMAIN_REGISTER_HEADERS = \
../src/domain/register_address.h \
../src/domain/register_repository.h

DOMAIN_HMI_SOURCES = \
../src/domain/hmi_model.cpp \
../src/domain/hmi_control_registry.cpp

DOMAIN_HMI_HEADERS = \
../src/domain/hmi_model.h \
../src/domain/hmi_control_registry.h

DOMAIN_LOGIC_SOURCES = \
../src/domain/control_logic_model.cpp

DOMAIN_LOGIC_HEADERS = \
../src/domain/control_logic_model.h

DOMAIN_PROJECT_SOURCES = \
../src/domain/alarm_model.cpp \
../src/domain/project_model.cpp

DOMAIN_PROJECT_HEADERS = \
../src/domain/alarm_model.h \
../src/domain/project_model.h \
../src/domain/project_limits.h \
../src/domain/project_storage.h

DOMAIN_RUNTIME_SOURCES = \
../src/domain/runtime_state.cpp

DOMAIN_RUNTIME_HEADERS = \
../src/domain/runtime_state.h

DOMAIN_MONITOR_SOURCES = \
../src/domain/active_register_repository.cpp \
../src/domain/register_monitor_model.cpp

DOMAIN_MONITOR_HEADERS = \
../src/domain/active_register_repository.h \
../src/domain/register_monitor_model.h

DOMAIN_ALL_SOURCES = \
$$DOMAIN_REGISTER_SOURCES \
$$DOMAIN_HMI_SOURCES \
$$DOMAIN_LOGIC_SOURCES \
$$DOMAIN_PROJECT_SOURCES \
$$DOMAIN_RUNTIME_SOURCES \
$$DOMAIN_MONITOR_SOURCES

DOMAIN_ALL_HEADERS = \
$$DOMAIN_REGISTER_HEADERS \
$$DOMAIN_HMI_HEADERS \
$$DOMAIN_LOGIC_HEADERS \
$$DOMAIN_PROJECT_HEADERS \
$$DOMAIN_RUNTIME_HEADERS \
$$DOMAIN_MONITOR_HEADERS

SERVICE_PROJECT_SOURCES = \
../src/services/project_service.cpp

SERVICE_PROJECT_HEADERS = \
../src/services/project_service.h

SERVICE_ALARM_SOURCES = \
../src/services/alarm_editor_service.cpp \
../src/services/alarm_service.cpp

SERVICE_ALARM_HEADERS = \
../src/services/alarm_editor_service.h \
../src/services/alarm_service.h

SERVICE_HMI_SOURCES = \
../src/services/hmi_editor_service.cpp \
../src/services/hmi_navigation_service.cpp \
../src/services/hmi_runtime_service.cpp

SERVICE_HMI_HEADERS = \
../src/services/editor_history.h \
../src/services/hmi_editor_service.h \
../src/services/hmi_navigation_service.h \
../src/services/hmi_runtime_service.h

SERVICE_LOGIC_SOURCES = \
../src/services/logic_editor_service.cpp

SERVICE_LOGIC_HEADERS = \
../src/services/editor_history.h \
../src/services/logic_editor_service.h

SERVICE_OFFLINE_SOURCES = \
../src/services/software_logic_executor.cpp \
../src/services/offline_simulation_service.cpp

SERVICE_OFFLINE_HEADERS = \
../src/services/software_logic_executor.h \
../src/services/offline_simulation_service.h

SERVICE_RUNTIME_SOURCES = \
../src/services/runtime_mode_service.cpp

SERVICE_RUNTIME_HEADERS = \
../src/services/runtime_mode_service.h

SERVICE_MONITOR_SOURCES = \
../src/services/register_monitor_service.cpp \
../src/services/register_comment_service.cpp

SERVICE_MONITOR_HEADERS = \
../src/services/register_monitor_service.h \
../src/services/register_comment_service.h

INFRASTRUCTURE_JSON_SOURCES = \
../src/infrastructure/json_project_storage.cpp

INFRASTRUCTURE_JSON_HEADERS = \
../src/infrastructure/json_project_storage.h

INFRASTRUCTURE_PLC_SOURCES = \
../src/infrastructure/plc_communication_error_classifier.cpp \
../src/infrastructure/plc_register_repository.cpp \
../src/infrastructure/plc_communication_service.cpp

INFRASTRUCTURE_PLC_HEADERS = \
../src/infrastructure/plc_communication_error_classifier.h \
../src/infrastructure/plc_register_repository.h \
../src/infrastructure/plc_communication_service.h

UI_MAIN_SOURCES = \
../src/ui/main_window.cpp \
../src/ui/logic_instruction_dialog.cpp \
../src/ui/alarm_configuration_dialog.cpp \
../src/ui/register_comment_dialog.cpp \
../src/ui/plc_connection_dialog.cpp \
../src/ui/free_monitor_widget.cpp \
../src/ui/runtime_monitor_window.cpp \
../src/ui/runtime_monitor_widget.cpp \
../src/ui/toolbar_icon_factory.cpp \
../src/ui/project_workspace_controller.cpp \
../src/ui/property_panel_controller.cpp \
../src/ui/runtime_panel_controller.cpp \
../src/ui/hmi_editor_widget.cpp \
../src/ui/logic_editor_widget.cpp

UI_MAIN_HEADERS = \
../src/ui/main_window.h \
../src/ui/logic_instruction_dialog.h \
../src/ui/alarm_configuration_dialog.h \
../src/ui/register_comment_dialog.h \
../src/ui/plc_connection_dialog.h \
../src/ui/free_monitor_widget.h \
../src/ui/runtime_monitor_window.h \
../src/ui/runtime_monitor_widget.h \
../src/ui/toolbar_icon_factory.h \
../src/ui/project_workspace_controller.h \
../src/ui/property_panel_controller.h \
../src/ui/runtime_panel_controller.h \
../src/ui/hmi_editor_widget.h \
../src/ui/logic_editor_widget.h

UI_MAIN_FORMS = \
../src/ui/main_window.ui \
../src/ui/logic_instruction_dialog.ui \
../src/ui/alarm_configuration_dialog.ui \
../src/ui/register_comment_dialog.ui \
../src/ui/plc_connection_dialog.ui \
../src/ui/free_monitor_widget.ui \
../src/ui/runtime_monitor_window.ui \
../src/ui/runtime_monitor_widget.ui

+ 2
- 7
app/tests/project_management_tests.cpp Ver fichero

@@ -1,6 +1,7 @@
#include "domain/project_storage.h"
#include "domain/hmi_model.h"
#include "infrastructure/json_project_storage.h"
#include "support/test_support.h"
#include "services/register_comment_service.h"
#include "services/project_service.h"
#include "domain/project_limits.h"
@@ -19,13 +20,7 @@

namespace {

void require(bool condition, const std::string &message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
using TestSupport::require;

Project makeExampleProject()
{


+ 13
- 31
app/tests/project_management_tests.pro Ver fichero

@@ -1,38 +1,20 @@
TEMPLATE = app
TARGET = project_management_tests

CONFIG += console c++17 testcase warn_on
CONFIG -= app_bundle
include(pri/test_defaults.pri)
include(pri/test_layers.pri)

TARGET = project_management_tests
QT += core

INCLUDEPATH += ../src
CONFIG += testcase

SOURCES += \
project_management_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/alarm_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/hmi_control_registry.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
../src/domain/runtime_state.cpp \
../src/services/project_service.cpp \
../src/services/register_comment_service.cpp \
../src/infrastructure/json_project_storage.cpp
$$DOMAIN_ALL_SOURCES \
$$SERVICE_PROJECT_SOURCES \
$$SERVICE_MONITOR_SOURCES \
$$INFRASTRUCTURE_JSON_SOURCES

HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/alarm_model.h \
../src/domain/hmi_model.h \
../src/domain/hmi_control_registry.h \
../src/domain/control_logic_model.h \
../src/domain/project_limits.h \
../src/domain/project_model.h \
../src/domain/runtime_state.h \
../src/domain/project_storage.h \
../src/services/project_service.h \
../src/services/register_comment_service.h \
../src/infrastructure/json_project_storage.h
$$DOMAIN_ALL_HEADERS \
$$SERVICE_PROJECT_HEADERS \
$$SERVICE_MONITOR_HEADERS \
$$INFRASTRUCTURE_JSON_HEADERS \
$$TEST_SUPPORT_HEADERS

+ 2
- 7
app/tests/register_monitor_service_tests.cpp Ver fichero

@@ -2,6 +2,7 @@
#include "domain/register_monitor_model.h"
#include "domain/register_repository.h"
#include "services/register_monitor_service.h"
#include "support/test_support.h"

#include <iostream>
#include <stdexcept>
@@ -9,13 +10,7 @@

namespace {

void require(bool condition, const std::string &message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
using TestSupport::require;

void testRangeManagement()
{


+ 9
- 16
app/tests/register_monitor_service_tests.pro Ver fichero

@@ -1,24 +1,17 @@
QT -= gui
include(pri/test_defaults.pri)
include(pri/test_layers.pri)

TEMPLATE = app
TARGET = register_monitor_service_tests

CONFIG += console c++17 warn_on
CONFIG -= app_bundle

INCLUDEPATH += ../src
QT -= gui

SOURCES += \
register_monitor_service_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/active_register_repository.cpp \
../src/domain/register_monitor_model.cpp \
$$DOMAIN_REGISTER_SOURCES \
$$DOMAIN_MONITOR_SOURCES \
../src/services/register_monitor_service.cpp

HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/active_register_repository.h \
../src/domain/register_monitor_model.h \
../src/services/register_monitor_service.h
$$DOMAIN_REGISTER_HEADERS \
$$DOMAIN_MONITOR_HEADERS \
../src/services/register_monitor_service.h \
$$TEST_SUPPORT_HEADERS

+ 3
- 20
app/tests/runtime_mode_service_tests.cpp Ver fichero

@@ -4,6 +4,7 @@
#include "domain/active_register_repository.h"
#include "domain/project_storage.h"
#include "domain/register_repository.h"
#include "support/test_support.h"

#include <functional>
#include <iostream>
@@ -13,19 +14,7 @@

namespace {

class TestProjectStorage final : public ProjectStorage
{
public:
ProjectSaveResult save(const Project &, const std::string &) override
{
return {true, ProjectStorageError::None, {}};
}

ProjectLoadResult load(const std::string &) override
{
return {false, {}, ProjectStorageError::FileReadFailed, {}};
}
};
using TestProjectStorage = TestSupport::InMemoryProjectStorage;

class ReadyPlcGateway final : public PlcCommunicationGateway
{
@@ -97,13 +86,7 @@ private:
std::vector<RegisterAddress> poll_addresses;
};

void require(bool condition, const std::string &message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
using TestSupport::require;

void testModeTransitions()
{


+ 12
- 36
app/tests/runtime_mode_service_tests.pro Ver fichero

@@ -1,43 +1,19 @@
QT -= gui
include(pri/test_defaults.pri)
include(pri/test_layers.pri)

TEMPLATE = app
TARGET = runtime_mode_service_tests

CONFIG += console c++17 warn_on
CONFIG -= app_bundle

INCLUDEPATH += ../src
QT -= gui

SOURCES += \
runtime_mode_service_tests.cpp \
../src/domain/register_address.cpp \
../src/domain/register_repository.cpp \
../src/domain/active_register_repository.cpp \
../src/domain/register_monitor_model.cpp \
../src/domain/alarm_model.cpp \
../src/domain/hmi_model.cpp \
../src/domain/hmi_control_registry.cpp \
../src/domain/control_logic_model.cpp \
../src/domain/project_model.cpp \
../src/domain/runtime_state.cpp \
../src/services/project_service.cpp \
../src/services/software_logic_executor.cpp \
../src/services/offline_simulation_service.cpp \
../src/services/runtime_mode_service.cpp
$$DOMAIN_ALL_SOURCES \
$$SERVICE_PROJECT_SOURCES \
$$SERVICE_OFFLINE_SOURCES \
$$SERVICE_RUNTIME_SOURCES

HEADERS += \
../src/domain/register_address.h \
../src/domain/register_repository.h \
../src/domain/active_register_repository.h \
../src/domain/register_monitor_model.h \
../src/domain/alarm_model.h \
../src/domain/hmi_model.h \
../src/domain/hmi_control_registry.h \
../src/domain/control_logic_model.h \
../src/domain/project_model.h \
../src/domain/project_storage.h \
../src/domain/runtime_state.h \
../src/services/project_service.h \
../src/services/software_logic_executor.h \
../src/services/offline_simulation_service.h \
../src/services/runtime_mode_service.h
$$DOMAIN_ALL_HEADERS \
$$SERVICE_PROJECT_HEADERS \
$$SERVICE_OFFLINE_HEADERS \
$$SERVICE_RUNTIME_HEADERS \
$$TEST_SUPPORT_HEADERS

+ 34
- 0
app/tests/support/test_support.h Ver fichero

@@ -0,0 +1,34 @@
#pragma once

#include "domain/project_storage.h"

#include <stdexcept>
#include <string>

namespace TestSupport
{

inline void require(bool condition, const std::string &message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}

// 编辑服务测试只验证内存模型和服务契约,不应依赖真实文件系统
class InMemoryProjectStorage final : public ProjectStorage
{
public:
ProjectSaveResult save(const Project &, const std::string &) override
{
return {true, ProjectStorageError::None, {}};
}

ProjectLoadResult load(const std::string &) override
{
return {false, {}, ProjectStorageError::FileReadFailed, {}};
}
};

} // namespace TestSupport

+ 28
- 0
app/tests/tests.pro Ver fichero

@@ -0,0 +1,28 @@
# 测试总入口:只负责编排目标,不把不同环境的测试揉成一个可执行文件
TEMPLATE = subdirs
CONFIG += ordered

SUBDIRS += \
domain \
alarm_service \
hmi_editor_service \
logic_editor_service \
offline_simulation \
project_management \
register_monitor \
runtime_mode \
plc_runtime \
main_window \
performance

domain.file = domain_tests.pro
alarm_service.file = alarm_service_tests.pro
hmi_editor_service.file = hmi_editor_service_tests.pro
logic_editor_service.file = logic_editor_service_tests.pro
offline_simulation.file = offline_simulation_service_tests.pro
project_management.file = project_management_tests.pro
register_monitor.file = register_monitor_service_tests.pro
runtime_mode.file = runtime_mode_service_tests.pro
plc_runtime.file = plc_runtime_tests.pro
main_window.file = main_window_tests.pro
performance.file = performance_tests.pro

+ 88
- 0
scripts/run_qt_tests.ps1 Ver fichero

@@ -0,0 +1,88 @@
[CmdletBinding()]
param(
[ValidateSet('Debug', 'Release')]
[string]$Configuration = 'Debug',
[switch]$SkipPerformance
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$projectRoot = Split-Path -Parent $PSScriptRoot
$qtRoot = 'D:\Qt5.15.2\5.15.2\mingw81_64'
$mingwBin = 'D:\Qt5.15.2\Tools\mingw810_64\bin'
$qmakePath = Join-Path $qtRoot 'bin\qmake.exe'
$makePath = Join-Path $mingwBin 'mingw32-make.exe'
$configurationName = $Configuration.ToLowerInvariant()
$testSourceRoot = Join-Path $projectRoot 'app\tests'
$buildRoot = Join-Path $projectRoot "build\tests\$configurationName"

foreach ($requiredPath in @($qmakePath, $makePath, $testSourceRoot)) {
if (-not (Test-Path -LiteralPath $requiredPath)) {
throw "未找到必要文件:$requiredPath"
}
}

$targets = @(
'domain_tests',
'alarm_service_tests',
'hmi_editor_service_tests',
'logic_editor_service_tests',
'offline_simulation_service_tests',
'project_management_tests',
'register_monitor_service_tests',
'runtime_mode_service_tests',
'plc_runtime_tests',
'main_window_tests'
)
if (-not $SkipPerformance) {
$targets += 'performance_tests'
}

$originalPath = $env:Path
$originalPlatform = $env:QT_QPA_PLATFORM
$env:Path = "$qtRoot\bin;$mingwBin;$env:Path"
$env:QT_QPA_PLATFORM = 'offscreen'

try {
foreach ($target in $targets) {
$projectFile = Join-Path $testSourceRoot "$target.pro"
$buildDirectory = Join-Path $buildRoot $target
$executablePath = Join-Path $buildDirectory "$configurationName\$target.exe"
New-Item -ItemType Directory -Path $buildDirectory -Force | Out-Null

Push-Location $buildDirectory
try {
& $qmakePath $projectFile "CONFIG+=$configurationName" "CONFIG-=$(if ($configurationName -eq 'debug') { 'release' } else { 'debug' })"
if ($LASTEXITCODE -ne 0) {
throw "qmake 失败:$target,退出码:$LASTEXITCODE"
}

& $makePath -j2
if ($LASTEXITCODE -ne 0) {
throw "构建失败:$target,退出码:$LASTEXITCODE"
}

if (-not (Test-Path -LiteralPath $executablePath)) {
throw "未生成测试可执行文件:$executablePath"
}

Write-Host "运行 $target"
& $executablePath
if ($LASTEXITCODE -ne 0) {
throw "测试失败:$target,退出码:$LASTEXITCODE"
}
}
finally {
Pop-Location
}
}
}
finally {
$env:Path = $originalPath
if ($null -eq $originalPlatform) {
Remove-Item Env:QT_QPA_PLATFORM -ErrorAction SilentlyContinue
} else {
$env:QT_QPA_PLATFORM = $originalPlatform
}
}

Cargando…
Cancelar
Guardar