ワンクリックで
qtpass-testing
Comprehensive guide for QtPass unit testing with Qt Test
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Comprehensive guide for QtPass unit testing with Qt Test
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Documentation guide for QtPass - README, FAQ, localization
QtPass CI/CD workflow - run GitHub Actions locally with act, linters, formatters
QtPass localization workflow - translation files, updating, adding languages
QtPass localization audit - structural checks on .ts files (placeholders, HTML balance, mnemonics, mixed-script artifacts)
QtPass GitHub interaction - PRs, issues, branches, merging
Bug fixing workflow for QtPass - find, fix, test, PR
| name | qtpass-testing |
| description | Comprehensive guide for QtPass unit testing with Qt Test |
| license | GPL-3.0-or-later |
| metadata | {"audience":"developers","workflow":"testing"} |
QtPass is a Qt6/C++ password manager GUI for pass. Tests use Qt Test framework.
# Build and run all tests
make check
# Build with coverage
qmake6 -r CONFIG+=coverage
make -j4
# Generate coverage report
make lcov
The suites below are representative, not exhaustive —
tests/auto/auto.proSUBDIRSis the source of truth. Newer suites includesettings,configdialog,mainwindow,importkeydialog,exportpublickeydialog,keygendialog,trayicon,userinfo,profileinit,grepsearchcontrollerandpassworddisplaypanel.
Tests for src/gpgkeystate.cpp:
parseMultiKeyPublic() - Multiple public keys parsingparseSecretKeys() - Secret key detection (have_secret flag)parseSingleKey() - Single key with and without fingerprintparseKeyRollover() - Multiple keys in sequenceclassifyRecordTypes() - GPG record type classification (pub, sec, uid, fpr, etc.)Store sample test data in tests/fixtures/:
ls tests/fixtures/
# gpg-colons-multi-key.txt
# gpg-colons-public.txt
# gpg-colons-secret.txt
These contain real GPG --with-colons output for deterministic testing.
Tests for src/util.cpp:
normalizeFolderPath() - Path normalizationfileContent() / fileContentEdgeCases() - FileContent parsingregexPatterns() / regexPatternEdgeCases() - URL detection regular expressiontotpHiddenFromDisplay() - OTP field hidinguserInfoValidity() - User key validationpasswordConfigurationCharacters() - Password character setssimpleTransaction*() - SimpleTransaction testsTests for src/filecontent.h:
parsePlainPassword() - Single-line passwordparsePasswordWithNamedFields() - Password with key:valueparseWithTemplateFields() - Template field parsingparseWithAllFields() - All fields modegetRemainingData() - Non-template fieldsgetRemainingDataForDisplay() - Hides otpauth://namedValuesTakeValue() / namedValuesTakeValueNotFound()Tests for src/passwordconfiguration.h:
passwordConfigurationDefaults() - Default valuespasswordConfigurationSetters() - Setter methodspasswordConfigurationCharacterSets() - Character set configTests for src/executor.h:
executeBlockingEcho() - Basic executionexecuteBlockingWithArgs() - Arguments handlingexecuteBlockingExitCode() - Exit code checking (Unix only)executeBlockingStderr() - Error output capture (Unix only)Tests for src/storemodel.h:
dataRemovesGpgExtension() - Display name filteringflagsWithValidIndex() / flagsWithInvalidIndex() - Item flagsmimeTypes() - Drag/drop MIME typeslessThan() - Sorting comparisonsupportedDropActions() / supportedDragActions()filterAcceptsRowHidden() / filterAcceptsRowVisible()Tests for src/qtpasssettings.h:
UI tests:
contentRemainsSame() - Password content integrityemptyPassword() - Empty password handlingmultilineRemainingData() - Multiline field handling// SPDX-FileCopyrightText: YYYY Your Name
// SPDX-License-Identifier: GPL-3.0-or-later
#include <QtTest>
#include "../../../src/mymodule.h"
class tst_mymodule : public QObject {
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testBasicFunction();
void testEdgeCase();
void cleanupTestCase();
};
void tst_mymodule::initTestCase() {}
void tst_mymodule::testBasicFunction() {
// Use set+get pattern or direct input/output
QString result = MyModule::process("input");
QVERIFY2(result == "expected", "Should return expected output");
}
void tst_mymodule::testEdgeCase() {
// Test empty, null, boundary conditions
QVERIFY(MyModule::process("").isEmpty());
}
void tst_mymodule::cleanupTestCase() {}
QTEST_MAIN(tst_mymodule)
#include "tst_mymodule.moc"
!include(../auto.pri) { error("Couldn't find the auto.pri file!") }
SOURCES += tst_mymodule.cpp
LIBS = -L"$$OUT_PWD/../../../src/$(OBJECTS_DIR)" -lqtpass $$LIBS
clang|gcc:PRE_TARGETDEPS += "$$OUT_PWD/../../../src/$(OBJECTS_DIR)/libqtpass.a"
HEADERS += mymodule.h
OBJ_PATH += ../../../src/$(OBJECTS_DIR)
VPATH += ../../../src
INCLUDEPATH += ../../../src
win32 {
RC_FILE = ../../../windows.rc
QMAKE_LINK_OBJECT_MAX=24
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>QMTestSpecification</key>
<dict>
<key>Type</key>
<string>Bundle</string>
<key>UIElement</key>
<dict>
<key>Modified</key>
<false/>
<key>SystemEntity</key>
<string>Test</string>
</dict>
</dict>
</dict>
</plist>
tests/auto/<name>/<name>.pro file (copy pattern above)qtpass.plist (copy from model/)tst_<name>.cpp test filetests/auto/auto.pro: SUBDIRS += <name>qmake6 -r && make -j4make checktst_<classname>.cpptst_<classname>testMethodName() or methodDoesWhat()initTestCase() - Setup (runs once before all tests)init() - Setup before each testcleanupTestCase() - Teardown (runs once after all tests)// Basic
QVERIFY(condition);
QVERIFY2(condition, "failure message");
// Equality
QCOMPARE(actual, expected);
QCOMPARE2(actual, expected, "message");
// String matching
QVERIFY2(output.contains("needle"), "should contain needle");
QCOMPARE(QString("hello").toUpper(), QString("HELLO"));
|| in assertionsBad (tautology or ambiguous):
QVERIFY(result == expected || result == INVALID); // unclear intent
QVERIFY(result != INVALID || result == INVALID); // ALWAYS TRUE - tautology!
Good - just call the method to verify it doesn't crash:
simpleTransaction trans;
trans.transactionAdd(Enums::PASS_INSERT);
trans.transactionIsOver(Enums::PASS_INSERT); // verify it runs without crash
Or use deterministic setup with QCOMPARE:
simpleTransaction st;
st.transactionAdd(Enums::PASS_INSERT);
Enums::PROCESS result = st.transactionIsOver(Enums::PASS_INSERT);
QCOMPARE(result, Enums::PASS_INSERT); // deterministic
QFAIL("message") - Fail with messageQSKIP("message") - Skip testQCOMPARE(a, b) - Assert equalityQVERIFY(a) - Assert trueQVERIFY2(a, msg) - Assert with messagevoid tst_executor::unixOnlyTest() {
#ifndef Q_OS_WIN
// Test code here
#endif
}
Windows uses backslashes (\) while Unix uses forward slashes (/). When comparing paths, use QDir::cleanPath() to normalize:
void tst_util::testPathComparison() {
QString path = Pass::getGpgIdPath(passStore);
QString expected = passStore + "/.gpg-id";
// Use cleanPath to normalize for cross-platform compatibility
QVERIFY2(QDir::cleanPath(path) == QDir::cleanPath(expected),
qPrintable(QString("Expected %1, got %2")
.arg(QDir::cleanPath(expected), QDir::cleanPath(path))));
}
Tests that modify QtPass settings can pollute the user's live config. This is especially problematic on Windows where settings use the registry.
Solution: Backup and restore settings in tests
// In tst_settings::initTestCase()
void tst_settings::initTestCase() {
// Check for portable mode (qtpass.ini in app directory)
QString portable_ini = QCoreApplication::applicationDirPath() +
QDir::separator() + "qtpass.ini";
bool isPortableMode = QFile::exists(portable_ini);
if (isPortableMode) {
// Backup settings file
QtPassSettings::getInstance()->sync();
QString settingsFile = QtPassSettings::getInstance()->fileName();
m_settingsBackupPath = settingsFile + ".bak";
QFile::remove(m_settingsBackupPath);
QFile::copy(settingsFile, m_settingsBackupPath);
} else {
// Warn on non-portable mode (registry on Windows)
qWarning() << "Non-portable mode detected. Tests may modify registry settings.";
}
}
// In tst_settings::cleanupTestCase()
void tst_settings::cleanupTestCase() {
// Restore original settings after all tests
if (isPortableMode && !m_settingsBackupPath.isEmpty()) {
QString settingsFile = QtPassSettings::getInstance()->fileName();
QFile::remove(settingsFile);
QFile::copy(m_settingsBackupPath, settingsFile);
QFile::remove(m_settingsBackupPath);
}
}
Key points:
When checking variant types, prefer canConvert<T>() over metaType().id() for broader compatibility:
// Qt6-only (fails on Qt5)
QVERIFY(displayData.metaType().id() == QMetaType::QString);
// Qt5/Qt6 compatible
QVERIFY(displayData.canConvert<QString>());
void tst_mymodule::testWithTempFile() {
QTemporaryDir tempDir;
QString filePath = tempDir.path() + "/test.txt";
QFile file(filePath);
QVERIFY(file.open(QIODevice::WriteOnly));
file.write("test data");
file.close();
// Test reads/modifies file
}
When testing settings that have getters with default parameters, pass a different default value to verify persistence:
// Bad - returns default if persistence fails
setter(testValue);
QCOMPARE(getter(testValue), testValue);
// Good - uses different default, must return stored value
setter(testValue);
QCOMPARE(getter(!testValue), testValue); // bool: use negation
QCOMPARE(getter(-1), testValue); // int: use sentinel
QCOMPARE(getter(QString()), testValue); // string: use empty
Data-driven tests work well for simple bool/int/string settings. Compound types like PasswordConfiguration often don't fit:
Always use the proper Qt Test macros:
// Good
QCOMPARE(actual, expected);
QVERIFY(condition);
QVERIFY2(condition, "failure message");
// Bad - won't compile or is confusing
COMPARE(actual, expected); // missing Q
QQCOMPARE(actual, expected); // extra Q
normalizeFolderPath() - Path normalizationprotocolRegex() - URL detectionendsWithGpg() - Extension matchingnewLinesRegex() - Newline detectionFileContent::parse() - Parse password filegetPassword() - Get main passwordgetNamedValues() - Get key:value fieldsgetRemainingData() - Non-template fieldsgetRemainingDataForDisplay() - Display-safe (hides OTP)NamedValues::takeValue() - Extract and remove valuePasswordConfiguration classExecutor::executeBlocking() - Run command synchronouslyStoreModel extends QFileSystemModelsetModelAndStore() - Initialize with model and pathdata() - Returns display name (removes .gpg)flags() - Item flagslessThan() - SortingTests run via make check in CI. Coverage reported with make lcov.
See qtpass-linting skill for full CI workflow. Pattern:
# Run linter locally BEFORE pushing
act push -W .github/workflows/linter.yml -j build
Formats: .md, .yml, .html, .css, .js, .json, etc.
npx prettier --write <file>
npx prettier --write .github/workflows/*.yml
npx prettier --write ".opencode/skills/*/SKILL.md"
# Check formatting
clang-format --style=file --dry-run src/main.cpp
# Apply formatting
clang-format --style=file -i src/main.cpp
make check # Runs tests and builds
# Run single test
./tests/auto/util/tst_util testName
# Verbose output
./tests/auto/util/tst_util -v2
# Detailed timing
./tests/auto/util/tst_util -vs