我正在尝试为 MATLAB 应用程序设计一个单元测试,该应用程序可以在窗口模式或无头模式下运行。该测试以无头模式运行程序,并尝试检测过程中是否打开了任何窗口。
groot
属性 CurrentFigure
,并编写一个 PostSet
回调来增加一个计数器,以跟踪打开的窗口数量。最后,测试会确保该值为 0。根据记录,这似乎不起作用。尽管我有 ShowHiddenHandles
到 'on'
,但它似乎没有捕获模态窗口。然而,这不是我的问题。
我的问题是,我在创建侦听器的位置和我
delete
它的位置之间的测试代码中出现了错误。现在,有一个侦听器附加到 groot
,但侦听器句柄变量已被清除,因此每次尝试打开窗口时都会出现非常奇怪的行为。测试对象重新打开,然后在侦听器回调中抛出错误。
既然所有对它的原始引用都已从工作区中删除,我如何找到并删除附加到
groot
的侦听器?到目前为止,唯一有效的方法是重新启动 MATLAB,但这似乎是一种低效的调试方法。
要重现错误,请创建以下测试类:
classdef MCVtest < matlab.unittest.TestCase
% Minimal, Complete, Verifiable example
properties
numOfFiguresCreated = 0;
end
methods
function figureCreatedListener(testCase)
testCase.numOfFiguresCreated = testCase.numOfFiguresCreated + 1;
end
end
methods (Test)
function testFiles(testCase)
%Create Listener for this particular input file.
listener = addlistener(groot, 'CurrentFigure', 'PostSet', @testCase.figureCreatedListener); %#ok<NASGU>
error('Well, this sucks...')
% delete Listener for this input file
delete(listener) %#ok<UNRCH>
% Verify That no graphics objects were created at all.
testCase.verifyEqual(testCase.numOfFiguresCreated, 0);
end
end
end
从命令行:
>> suite = matlab.unittest.TestSuite.fromClass(?MCVtest)
>> results = suite.run
出现错误后:
>> figure
Error using MCVtest/figureCreatedListener
Too many input arguments.
Error in MCVtest>@(varargin)testCase.figureCreatedListener(varargin{:}) (line 17)
listener = addlistener(groot, 'CurrentFigure', 'PostSet', @testCase.figureCreatedListener); %#ok<NASGU>
当然,
listener
已经不存在了,所以我无法删除它。我已经按顺序尝试了clear
、clear all
和clear classes
,但听众仍然坚持。清除它的唯一方法(据我所知)是重新启动 MATLAB。
您应该能够通过访问
'AutoListeners__'
(或任何其他图形)对象的未记录的 Root
属性来找到侦听器,该对象包含侦听器的元胞数组。
例如:
a = addlistener(groot, 'CurrentFigure', 'PostSet', @(s,e)disp('hi'));
tmp = groot;
listeners = tmp.AutoListeners__;
这给了我们:
>> a == listeners{1}
ans =
logical
1
所以我们可以这样做:
for ii = 1:numel(listeners)
delete(listeners{ii});
end
删除所有悬挂的侦听器。
请注意,如果该对象没有侦听器,则
AutoListeners__
不存在。
另请注意,MATLAB 有 2 种不同的侦听器实现:
addlistener
,它将侦听器绑定到对象,并在 object 超出范围时被删除;以及 listener
,我们将其解除绑定并当 listener 超出范围时将被删除。
通过在单元测试中使用
listener
而不是 addlistener
,如果您的清理由于某种原因(在本例中是由于错误)未运行,您可以避免悬空侦听器。
您可能需要考虑使用较低级别的接口来创建和管理侦听器,这样您就不必再次解决此搁浅的侦听器状态问题。您可以将对侦听器的唯一引用绑定到所需的函数或对象生命周期范围,这样您就不必显式删除侦听器,而是可以在最后一个引用被销毁后依靠引用计数来删除侦听器。
https://www.mathworks.com/help/matlab/ref/event.listener.html
或
https://www.mathworks.com/help/matlab/ref/event.proplistener.html
这些接口允许您在创建后接收侦听器对象作为 LHS 参数。然后,您可以决定是否存储侦听器,以便您可以根据需要管理其生命周期。
如果使用此接口,您可能能够避免显式使用删除,因为您可以将唯一的侦听器句柄的生命周期与您希望其存在的范围联系起来。
所以,具体来说,在同一个例子中:
lsnr = event.proplistener(groot,findprop(groot,'CurrentFigure'),'PostSet',@(hobj,evt) disp('fire'));
当 lsnr 超出范围时,对侦听器对象的最后一个引用将丢失,并且 lsnr 将被自动删除。当然,你也可以显式调用delete。
您考虑过使用
onCleanup
吗?该函数创建一个对象,该对象在销毁后立即执行函数。根据您的情况,您可以将代码更改为以下内容:
listener = addlistener(groot, 'CurrentFigure', 'PostSet', @testCase.figureCreatedListener); %#ok<NASGU>
% Create cleanup object to auto-delete listener
cleanObj = onCleanup(@() delete(listener));
error('Well, this sucks...')
% Optional: delete cleanup object, which in turn removes the listener
delete(cleanObj) %#ok<UNRCH>
抛出错误时,
cleanObj
对象超出范围并按预期删除侦听器。
如果没有错误,您可以使用
delete(cleanObj)
手动删除定义位置处的清理对象(和侦听器)。或者,您可以简单地等待,直到 cleanObj
对象超出范围并被自动删除。