- 使用指南
- 数字绘画基础知识
- 参考手册
- 实例教程
- 常见问题解答
- 参与者手册
- 扩展包和第三方教程
- 其他
- 显示设置
- 日志查看器
- 数位板设置
- Automated Krita builds on CI matrix
- Brush GUI Design with Lager
- Building Krita from Source
- CMake Settings for Developers
- Enable static analyzer
- How to patch Qt
- Introduction to Hacking Krita
- The Krita Palette format KPL
- Krita SVG Extensions
- Modern C++ usage guidelines for the Krita codebase
- Developing Features
- Optimize Image Processing with XSIMD
- Optimizing tips and tools for Krita
- Google Summer of Code
- Advanced Merge Request Guide
- Python Developer Tools
- Introduction to Quality Assurance
- Making a release
- Reporting Bugs
- Strokes queue
- Testing Strategy
- Triaging Bugs
- Unittests in Krita
- 矢量图层
- 常规设置
- 颜料图层
- 图层组
- 克隆图层
- 文件图层
- 填充图层
- 滤镜图层
- 笔刷引擎
- 透明度蒙版
- 滤镜笔刷引擎
- 滤镜蒙版
- 裁剪工具
- 移动工具
- 变形工具
- 变形笔刷引擎
- 变形蒙版
- 网格与参考线
- 工作区
- 笔刷预设
- 色板
- 键盘快捷键
- 设置菜单
- 性能设置
- 笔尖
- 不透明度和流量
- 常用笔刷选项
- 多路笔刷工具
- 手绘笔刷工具
- 直线工具
- 曲线工具
- 辅助尺工具
- 图层
- 矩形选区工具
- 椭圆选区工具
- 多边形选区工具
- 手绘轮廓选区工具
- 相似颜色选区工具
- 相连颜色选区工具
- 曲线选区工具
- 磁性选区工具
- 形状选择工具
- 锚点编辑工具
- 工具菜单
- 动画时间轴
- 绘图纸外观
- 动画曲线
- 分镜头脚本
- 颜色
- 色域蒙版
- 美术拾色器
- 多功能拾色器
- 智能填色蒙版工具
- *.gih
- 像素笔刷引擎
- *.kra
- SeExpr
- SeExpr 脚本
- 渐变
- 颜色涂抹笔刷引擎
- 纹理
- 拾色器工具
- LUT 色彩管理
- 小型拾色器
- 有损和无损图像压缩
- *.bmp
- *.csv
- *.exr
- *.gbr
- *.gif
- *.heif 和 *.avif
- *.jpg
- *.jxl
- *.kpl
- *.ora
- .pbm、.pgm 和 *.ppm
- *.png
- *.psd
- *.svg
- *.tiff
- *.webp
- 数学运算
- 变暗
- HSX
- 变亮
- 颜色混合
- 负片
- 其他
- 二元逻辑
- 取模运算
- 二次方
- 鬃毛笔刷引擎
- 粉笔笔刷引擎
- 克隆笔刷引擎
- 曲线笔刷引擎
- 力学笔刷引擎
- 网格笔刷引擎
- 排线笔刷引擎
- MyPaint 笔刷引擎
- 粒子轨迹笔刷引擎
- 快速笔刷引擎
- 形状笔刷引擎
- 草图笔刷引擎
- 喷雾笔刷引擎
- 切线空间法线笔刷引擎
- 笔刷选项
- 锁定笔刷选项
- 蒙版笔刷
- 传感器
- 添加形状
- 动画
- 矢量图形排列
- 笔刷预设历史
- 色彩通道
- 颜色滑动条
- 图层显示方案
- 过渡色调混合器
- 直方图
- 导航器
- 图案
- 录像工具
- 参考图像
- 形状属性
- 图像版本快照
- 量化拾色器
- 操作流程
- 触摸屏辅助按钮
- 撤销历史
- 矢量图形库
- 宽色域拾色器
- 调整颜色/图像
- 艺术效果
- 模糊
- 边缘检测
- 浮雕
- 图像增强
- 映射
- 其他
- 小波分解
- 图层样式
- 选区蒙版
- 拆分透明度通道到蒙版
- 编辑菜单
- 文件菜单
- 帮助菜单
- 图像菜单
- 图层菜单
- 选择菜单
- 视图菜单
- 窗口菜单
- 作者档案设置
- 画布快捷键设置
- 隐藏面板模式设置
- 色彩管理设置
- 拾色器设置
- G’Mic 设置
- 弹出面板设置
- Python 插件管理器
- 笔尖
- 笔刷预设
- 图案
- 文字工具
- 渐变编辑工具
- 图案编辑工具
- 西文书法工具
- 矩形工具
- 椭圆工具
- 多边形工具
- 折线工具
- 手绘路径工具
- 力学笔刷工具
- 填充工具
- 闭合填充工具/圈涂
- 渐变工具
- 智能补丁工具
- 参考图像工具
- 测量工具
- 缩放工具
- 平移工具
- Building Krita with Docker on Linux
- Building krita on host Linux system (unsupported)
- Building Krita for Android
- Working on Krita Dependencies
- 渐变
- 多维网格
- 图案
- 网点
- 单纯形法噪点
Unittests in Krita
What is a unit test is and why is it needed?
- Wiki:
A unit test is a piece of code that automatically checks if your class or subsystem works correctly. The goal of unit testing is to isolate each part of the program and show that the individual parts are correct. A unit test provides a strict, written contract that the piece of code must satisfy. As a result, it affords several benefits [1].
- Comment:
In other words unit testing allows the developer to verify if his initial design decisions has been implemented correctly and all the corner-cases are handled correctly.
In Krita Project we use unit tests for several purposes. Not all of them work equally good, but all together they help developing a lot.
Debugging of new code
- Wiki:
Unit testing finds problems early in the development cycle. This includes both bugs in the programmer’s implementation and flaws or missing parts of the specification for the unit. The process of writing a thorough set of tests forces the author to think through inputs, outputs, and error conditions, and thus more crisply define the unit’s desired behavior. The cost of finding a bug before coding begins or when the code is first written is considerably lower than the cost of detecting, identifying, and correcting the bug later; bugs may also cause problems for the end-users of the software [1].
- Comment:
Krita is a big project and has numerous subsystems that communicate with each other in complicated ways. It makes debugging and testing new code in the application itself difficult. What is more, just compiling and running the entire application to check a one-line change in a small class is very time-consuming. So when writing a new subsystem we usually split it into smaller parts (classes) and test each of them individually. Testing a single class in isolation helps to catch all the corner-cases in the class public interface, e.g. “what happens if we pass 0 here instead of a valid pointer?” or “what if the index we just gave to this method is invalid?”
Changing/refactoring existing code
- Wiki:
Unit testing allows the programmer to refactor code or upgrade system libraries at a later date, and make sure the module still works correctly (e.g., in regression testing). The procedure is to write test cases for all functions and methods so that whenever a change causes a fault, it can be quickly identified. Unit tests detect changes which may break a design contract [1].
- Comment:
Imagine someone decides to refactor the code you wrote a year ago. How would he know whether his changes didn’t break anything in the internal class structure? Even if he/she asks you, how would you know if the changes to a year-old class, whose details are already forgotten, are valid?
Automated regression testing
Most of our unit tests are run nightly on the CI. You can see the results and coverage reports at https://build.kde.org/job/Extragear/job/krita/.
However, some of the unit tests are not stable enough to be run automatically and therefore are disabled. (They do straightforward QImage comparisons, so the test results can depend no only on version of the libraries installed, but also on build options and even type of CPU the tests are run on.) While the overall coverage is decent, this issue limits the ability of the unit test suite to catch regressions in several parts of the codebase. (More information on that in the respective Phabricator task: https://phabricator.kde.org/T11904.)
How to build and run tests?
Building the tests
To enable unit tests, build Krita with an additional cmake flag: -DBUILD_TESTING=ON
.
# example build command you@yourcomputer:~/kritadev/build>cmake ../krita \ -DCMAKE_INSTALL_PREFIX=$HOME/kritadev/install \ -DCMAKE_BUILD_TYPE=Debug \ -DKRITA_DEVS=ON \ -DBUILD_TESTING=ON
参见
If you need help with building from source, see Building Krita from Source
For more information about cmake options, please refer to CMake Settings for Developers
Once built, the tests are run from the build directory. There you can either run the whole suite at once or you can run a single test (or even a single test with a single data row for data-driven tests).
Run all the tests
# change to the build directory you@yourcomputer:~/> cd kritadev/build # run the whole suite you@yourcomputer:~/kritadev/build> make test
Run a single test
Every test class is built into a separate executable file. This executable file resides in the build directory tree. The relative path is the same as the path in source directory.
To run all tests in a single test class, run the executable:
# running all tests in a test class you@yourcomputer:~/kritadev/build>./libs/ui/tests/KisSpinBoxSplineUnitConverterTest
You can also run a single test method from the class or invoke the test method with a single test data row, if you have a data-driven test. Add the test method name (and optionally the test data row name) as an argument to the test class executable:
# the syntax for running single tests: # you@yourcomputer:~/kritadev/build>./test-class-executable "test method name":"data row name" # run a single method in a test class you@yourcomputer:~/kritadev/build>./libs/ui/tests/KisSpinBoxSplineUnitConverterTest testCurveCalculationTwoWay # run a single method in a test class with the selected test data row you@yourcomputer:~/kritadev/build>./libs/ui/tests/KisSpinBoxSplineUnitConverterTest testCurveCalculationTwoWay:"0.5 in (0, 10) = 5"
Environment variables for running tests
Prior to running the tests, you can set several environment variables to change the behavior of the tests.
Suppress safe assert dialogs:
you@yourcomputer:~/kritadev/build> export KRITA_NO_ASSERT_MSG=1
Set source directory for QImage-based test data
you@yourcomputer:~/kritadev/build> export KRITA_UNITTESTS_DATA_DIR=<directory>
Create reference images for QImage-based tests
you@yourcomputer:~/kritadev/build> export KRITA_WRITE_UNITTESTS=1
When to write a unit test?
Ideally a unit test should be written for any new class that implements some logic and provides any kind of public interface. It is especially true if this public interface is going to be used more that one client-class.
What should unit test include?
corner cases. E.g. what happens if we request merging of two layers, one of which has Inherit Alpha option enabled? What properties and composition mode the final layer should have? Answers to these questions should be given and tested in the unit test.
non-obvious design decisions. E.g. if a paint device has a non-transparent default pixel, then its
`exactBounds()`
returns the rect, not smaller that the size of the image, even though technically the device might be empty.
How to write a unittest?
Suppose you want to write a unittest for kritaimage library. You need to perform just a few steps:
Add files for the test class into
./image/tests/
directory:kis_some_class_test.h
#ifndef __KIS_SOME_CLASS_TEST_H #define __KIS_SOME_CLASS_TEST_H #include <QtTest/QtTest> class KisSomeClassTest : public QObject { Q_OBJECT private Q_SLOTS: void test(); }; #endif /* __KIS_SOME_CLASS_TEST_H */</syntaxhighlight>
kis_some_class_test.cpp
#include "kis_some_class_test.h" #include <QTest> void KisSomeClassTest::test() { } QTEST_MAIN(KisSomeClassTest, GUI)</syntaxhighlight>
Modify ./image/tests/CMakeLists.txt to include your new test class:
# ... ########### next target ############### set(kis_some_class_test_SRCS kis_some_class_test.cpp ) ecm_add_tests(${kis_some_class_test_SRCS} NAME_PREFIX "libs-somelib-" LINK_LIBRARIES kritaimage Qt5::Test) # ...
Write your test. You can use any macro commands provided by Qt (QVERIFY, QCOMPARE or QBENCHMARK).
void KisSomeClassTest::test() { QString cat("cat"); QString dog("dog"); QVERIFY(cat != dog); QCOMPARE(cat, "cat"); }
Run your test by running an executable in ./image/test/ folder
Krita-specific testing utils
Fetching reference images
All the testing files/images are usually stored in the test’s data folder (e.g. ./krita/image/tests/data/). But there are some files which are used throughout all the unit tests. These files are stored in the global folder ./krita/sdk/tests/data/. If you want to access any file, just use TestUtil::fetchDataFileLazy
. It first searches the file in the local test’s folder and if nothing is found checks the global folder.
Example:
QImage refImage(TestUtil::fetchDataFileLazy("lena.png")); QVERIFY(!refImage.isNull());
Compare test result against a reference QImage
There are two helper functions to compare a given QImage against an image saved in the data folder.
bool TestUtil::checkQImage(const QImage &image, const QString &testName, const QString &prefix, const QString &name, int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0); bool TestUtil::checkQImageExternal(const QImage &image, const QString &testName, const QString &prefix, const QString &name, int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0);
The functions search for a PNG file with path
./tests/data/<testName>/<prefix>/<prefix>_<name>.png # or without a subfolder ./tests/data/<testName>/<prefix>_<name>.png
The supplied QImage is compared against the saved PNG, and the result is returned to the caller. If the images do not coincide, two images are dumped into the current directory: one with actual result and another with what is expected.
The second version of the function is different. It searches the image in “an external repository”. The point is that PNG images occupy quite a lot of space and bloat the repository size. So we decided to put all the images that are big enough (>10KiB) into an external SVN repository. To configure an external test files repository on your computer, please do the following:
Checkout the data repository:
# create the tests data folder and enter it mkdir ~/testsdata cd ~/testsdata # checkout the extra repository svn checkout svn+ssh://svn@svn.kde.org/home/kde/trunk/tests/kritatests
Add environment variable pointing to your repository to your ~/.bashrc
export KRITA_UNITTESTS_DATA_DIR= ~/testsdata/kritatests/unittests
Use
TestUtil::checkQImageExternal
in your unittest and it will fetch data from the external source. If an external repository is not found then the test is considered “passed”.
QImageBasedTest for complex actions
Sometimes you need to test some complex actions like cropping or transforming the whole image. The main problem of such action is that it should work correctly with any kind of layer or mask, e.g. KisCloneLayer, KisGroupLayer or even KisSelectionMask. To facilitate such complex testing conditions, Krita provides a special class QImageBasedTest
. It helps you to create a really complex image and check the contents of its layers. You can find the best example of its usage in KisProcessingsTest
. Basically, to use this class, one should derive its own testing class from it, and call a set of callbacks, which do all the work. Let’s consider the code from KisProcessingsTest:
// override QImageBasedTest class class BaseProcessingTest : public TestUtil::QImageBasedTest { public: BaseProcessingTest() : QImageBasedTest("processings") { } // The method is called by test cases. If the test fails, a set of PNG images // is saved into working directory void test(const QString &testname, KisProcessingVisitorSP visitor) { // create an image and regenerate its projection KisSurrogateUndoStore *undoStore = new KisSurrogateUndoStore(); KisImageSP image = createImage(undoStore); image->initialRefreshGraph(); // check if the image is correct before testing anything QVERIFY(checkLayersInitial(image)); // do the action we are trying to test KisProcessingApplicator applicator(image, image->root(), KisProcessingApplicator::RECURSIVE); applicator.applyVisitor(visitor); applicator.end(); image->waitForDone(); // check the result, and dump images if something went wrong QVERIFY(checkLayers(image, testname)); // Check if undo(!) works correctly undoStore->undo(); image->waitForDone(); if (!checkLayersInitial(image)) { qWarning() << "NOTE: undo is not completely identical " << "to the original image. Falling back to " <<"projection comparison"; QVERIFY(checkLayersInitialRootOnly(image)); } } };
MaskParent object
TestUtil::MaskParent
is a simple class that, in its constructor, creates an RGB8 image with a single paint layer, which you can use for further testing. The image and the layer can be accessed as simple member variables.
Example:
void KisMaskTest::testCreation() { // create an image and a simple layer TestUtil::MaskParent p; // create a mask and attach its selection to the created layer TestMaskSP mask = new TestMask; mask->initSelection(p.layer); QCOMPARE(mask->extent(), QRect(0,0,512,512)); QCOMPARE(mask->exactBounds(), QRect(0,0,512,512)); }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论