Engee 文档
Notebook

自动化代码验证

我做了很多代码生成,并检查它与模型的工作方式相同。 整个过程需要大量的体力劳动,在某些时候我意识到我厌倦了用双手做同样的工作。 因此,我自动化了它,在这个项目中,我们将看看在循环(SIL)模式下自动测试生成的代码。

这是怎么回事?

循环内验证(SIL测试)的方法是我们比较主机上代码的工作结果相对于使用相同测试向量的模型的工作。 示意性地,测试看起来像这样:

image.png

从算法模型生成代码,然后将算法模型替换为生成的代码的调用。 代码本身是在主机上编译的。 因此,SIL测试允许您检查代码相对于模型的行为,而无需切换到目标计算机(在目标计算机上测试代码是一个单独的测试阶段,称为Processor-in-circuit,PIL)。

例子:

为了展示SIL测试的自动化,我从这个[出版物](https://engee.com/community/ru/catalogs/projects/integratsiia-c-koda-v-modeli-engee )。 为了便于测试,我已经使用链接模型将卡尔曼滤波器模型集成到一个大型模型中。:

image.png

链接模型提供了以下优点:

  1. 在单独的模型中对被测组件进行隔离。 初始化绑定时,链接模型的接口分别进行初始化。
  2. 通过软件控制,使用Si功能单元轻松更换被测组件进行SIL测试。

测试自动化

要编写自动化,我们需要使用软件管理。 与此同时,我想使我的自动化尽可能可重用。 所以我写了我自己的模块,自动化了大部分工作。 此外,该模块的编写考虑到它的功能将用于我将在测试中创建的测试。jl.

让我们连接所有必要的软件包并下载模块!

In [ ]:
import Pkg; Pkg.add("Test")
using Test

include("SILAutomation.jl");
MIL_Harness = "ABfilter_Harness";
SIL_Harness = "ABfilter_Harness_SIL";
CUT = "alphabetafilter";
   Resolving package versions...
   Installed ArrayLayouts ───────────── v1.12.0
   Installed DifferentialEquations ──── v7.16.1
   Installed LazyArrays ─────────────── v2.8.0
   Installed EnzymeCore ─────────────── v0.8.15
   Installed LineSearches ───────────── v7.4.0
   Installed ExponentialUtilities ───── v1.27.0
   Installed Krylov ─────────────────── v0.10.2
   Installed DifferentiationInterface ─ v0.7.10
   Installed RData ──────────────────── v0.8.3
   Installed Optim ──────────────────── v1.13.2
   Installed RDatasets ──────────────── v0.7.7
   Installed JuliaInterpreter ───────── v0.10.6
   Installed BenchmarkTools ─────────── v1.6.2
   Installed BandedMatrices ─────────── v1.10.1
   Installed SparseConnectivityTracer ─ v1.1.2
   Installed TimeZones ──────────────── v1.22.1
  No Changes to `~/.project/Project.toml`
  No Changes to `~/.project/Manifest.toml`

让我们来看看模块代码。:

In [ ]:
;cat SILAutomation.jl
module SILAutomotion

import Main.engee
export runSims,buildCUT,buildSILHarness,compare_signals

using DataFrames

function runSims(MIL_Harness::String,SIL_Harness::String)
    MIL_mdl = engee.load("$MIL_Harness.engee", force = true)
    SIL_mdl = engee.load("$SIL_Harness.engee", force = true)
    SILres = engee.run(SIL_mdl)
    MILres = engee.run(MIL_mdl)
    return (MILres,SILres)
end

function buildCUT(CUT::String)::Bool
    status = false;
    try
        abfilter = engee.load("$(CUT).engee")
        engee.set_param!(engee.gcm(),"CreateCFunction"=>true)
        engee.generate_code("$(CUT).engee", "$(CUT)_cg")
        include("$(CUT)_cg/$(CUT)_verification.jl")
        status = true;
    catch ex
        return status
    end
    return status
end

function buildSILHarness(SIL_Harness::String,CUT::String,MIL_Harness::String)
# Create harness (copy contents)
# Guard for old harnesses
    model_names = [m.name for m in engee.get_all_models()];
    if any(model_names .== SIL_Harness)
        engee.close(SIL_Harness,force=true)
    end
    if isfile("$SIL_Harness.engee")
        rm("$SIL_Harness.engee")
    end

    SIL_mdl = engee.create("$SIL_Harness")
    engee.load("$(MIL_Harness).engee")
    engee.copy_contents(MIL_Harness,SIL_Harness)
    engee.delete_block("$SIL_Harness/$CUT")
    try
        engee.copy_block("$(CUT)_verification/C Function","$SIL_Harness/C Function")
    catch error
        show(error)
    end

    engee.open(SIL_Harness)
    #connect SIL Function And prepare simulation
    engee.add_line("ProcessNoiseVariance/1","C Function/1")
    engee.add_line("MeasurementNoiseVariance/1","C Function/2")
    engee.add_line("Add-1/1","C Function/3")
    engee.add_line("C Function/1","Add/2")
    engee.add_line("C Function/1","SysOut/1")
    engee.set_log("C Function/1") 
    engee.set_param!(SIL_mdl,"FixedStep"=>engee.get_param(MIL_Harness,"FixedStep"))
    engee.arrange_system()
    engee.save("$SIL_Harness.engee")
end

function compare_signals(sig_one,sig_two)
    Ds = collect(sig_one);
    Rs = collect(sig_two);
    Cmp = isapprox.(Ds, Rs)
    issynched = all(Cmp.time)
    issimilar = all(Cmp.value) 
    return (issynched, issimilar)
end

end

生成验证Si函数

要从被测组件生成Si验证函数,必须在模型中启用相应的设置并生成代码。 我的模块中的buildCUT函数负责这一点。:

``'茱莉亚
函数buildCUT(CUT::String)::Bool
状态=错误;
试试
abfilter=engee.负荷("\text{(切)。}engee","(\text{切})_cg/$(CUT)_verification.jl")
status = true;
catch ex
return status
end
return status
end


Для того чтобы эта функция была тестируема, будем возвращать статус как булеву переменную.

Автоматизация сборки SIL-обвязки

Я не хочу изменять обвязку для MIL-теста, но мне надо обеспечить эквивалентность тестовых векторов, поэтому я создам новую модель для SIL-теста. Посмотрим на код функции buildSILHarness:

function buildSILHarness(SIL_Harness::String,CUT::String,MIL_Harness::String)
# Create harness (copy contents)
# Guard for old harnesses
    model_names = [m.name for m in engee.get_all_models()];
    if any(model_names .== SIL_Harness)
        engee.close(SIL_Harness,force=true)
    end
    if isfile("$SIL_Harness.engee")
        rm("$SIL_Harness.engee")
    end

    SIL_mdl = engee.create("$SIL_Harness")
    恩吉。负荷("$(MIL_Harness).engee")
    engee.copy_contents(MIL_Harness,SIL_Harness)
    engee.delete_block("$[\text{医}]\text{沉默}/$CUT")
    try
        engee.copy_block("$(CUT)_verification/C\text{函数}","$SIL_Harness/C Function")
    catch error
        show(error)
    end

    engee.open(SIL_Harness)
    #connect SIL Function And prepare simulation
    engee.add_line("ProcessNoiseVariance/1","C Function/1")
    engee.add_line("MeasurementNoiseVariance/1","C Function/2")
    engee.add_line("Add-1/1","C Function/3")
    engee.add_line("C Function/1","Add/2")
    engee.add_line("C Function/1","SysOut/1")
    engee.set_log("C Function/1") 
    engee.set_param!(SIL_mdl,"FixedStep"=>engee.get_param(MIL_Harness,"FixedStep"))
    engee.arrange_model()
    engee.save("$SIL_Harness.engee")
结束

首先,使用engee。create()我创建一个新的空模型,然后使用engee将原始模型的内容复制到新模型中。复制_contents()。 接下来,我删除链接模型并使用engee替换验证C函数。删除_block()和engee。复制_block()。 然后我恢复信号并指定要记录的信号。 然后我组织模型并保存它。 最终模型将如下所示:

image.png

如何正确比较信号?

我们都习惯使用运算符比较两个数字 ==. 例如:

In [ ]:
1.0 == 1.0
Out[0]:
true

但这里有两个问题。:

  1. 最好不要直接比较浮点数,而是将它们的差的模数与一定的误差范围进行比较。
  2. 如果我们谈论信号,那么再次,我们希望考虑到信号可能不同,我们希望考虑到一个小错误。

此外,有必要检查信号是否同步。

我们来看看比较函数的代码:

``'茱莉亚
函数compare_signals(sig_one,sig_two)
Ds=收集(sig_one);
Rs=收集(sig_two);
Cmp=isapprox。(Ds,Rs)
issynched=all(Cmp.时间)
issimilar=all(Cmp.值)
返回(issynched,issimilar)
结束


我们正在比较两个信号,它们表示为具有两列的DataFrame:time和value。 比较将通过isapprox()函数完成。 我们将返回两个布尔变量。:

*issynched-表示信号在时间上同步
*issimilar-显示信号是相同的以一定的精确度

创建测试套件并运行它

现在我们已经拥有了自动化测试所需的一切,让我们使用Test创建测试套件。jl. 此外,我将发布验证模型的组装以及代码和模型的等价性的测试。 让我们运行测试,看看他们的结果。:

In [ ]:
@testset verbose = true "SIL" begin
    @testset "Code Generation" begin
        @test SILAutomotion.buildCUT(CUT)==true
        @test isfile(CUT*"_verification.engee")
    end
    
    SILAutomotion.buildCUT(CUT)
    SILAutomotion.buildSILHarness(SIL_Harness,CUT, MIL_Harness)
    
    @testset "SIL Equality" begin
        (MR,SR) = SILAutomotion.runSims(MIL_Harness,SIL_Harness)
        (sync,equal) = SILAutomotion.compare_signals(MR["filtered"],SR["C Function.1"])
        @test sync==true
        @test equal==true
    end
end 
Test Summary:     | Pass  Total   Time
SIL               |    4      4  40.3s
  Code Generation |    2      2   8.5s
  SIL Equality    |    2      2  22.3s
Out[0]:
Test.DefaultTestSet("SIL", Any[Test.DefaultTestSet("Code Generation", Any[], 2, false, false, true, 1.766386254735e9, 1.766386263245493e9, false, "In[4]"), Test.DefaultTestSet("SIL Equality", Any[], 2, false, false, true, 1.766386272794816e9, 1.76638629507223e9, false, "In[4]")], 0, false, true, true, 1.7663862547349e9, 1.766386295072248e9, false, "In[4]")

可以看出,所有的测试都通过了,我的自动化正在工作!

结论和后续步骤

使用软件管理,我设法使例程自动化。 我们可以通过将其扩展到PIL测试来进一步开发这种自动化。 使用测试编写。jl测试也可以扩展为更深入的测试。 最后,您可以使用[Variant Source]块在单个绑定中组织测试(https://engee.com/helpcenter/stable/ru/base-lib-signal-routing/variant-source.html )/变种汇。 后者需要单独的出版物。