#ObjectScript

0 关注者 · 95 帖子

InterSystems ObjectScript 是一种脚本语言,可使用 InterSystems 数据平台的任何数据模型(对象、关系、键值、文档、全局变量/Global)处理数据,并在 InterSystems 数据平台上为服务器端应用程序开发业务逻辑。

文档

文章 Hao Ma · 一月 15, 2021 2m read

什么是npm-iris? 

NPM是“No Project Mess(项目不乱)”的缩写。 

NPM是使用Intersystems IRIS和Bootstrp 4建成的项目和任务管理应用程序。 

NPM的创建初衷是通过一个简单直观的项目和任务管理软件,帮助开发者和小型商业公司降低日常问题的复杂度。 

它能提供不同的任务视图,包括电子表格、看板、日历,甚至甘特图! 

为什么? 

在不同的团队中工作,您会发现不同的人喜欢不同的工具。 

所以,有时您会在一个项目中使用甘特图,在另一个项目中使用看板,在其他项目中使用纸上的列表…… 

NPM专注于任务。无论您和您的团队喜欢以哪种方式查看。只需单击并更改您的视图。 

功能 

  • 初始安装 
  • 项目 
  • 用户 
  • 任务 - 创建和管理任务 
  • 调度程序 - 任务的日历视图 
  • 看板 - 用看板风格管理您的任务 
  • 甘特图 - 使用甘特图查看截止日期、里程碑和进度 

新特性/改进的路线图 

  • OAuth2身份验证 
  • 项目/团队/用户的安全性 
  • 时间跟踪 
  • 自定义日历(假期) 
  • 支持附件 
  • 利用AppS.REST框架 
  • Vue.js版本 
  • Home面板,可以查看活动的概况 

 

试一下这款应用程序! 
http://npm-iris.eastus.cloudapp.azure.com:52773/npm/home.csp  

0
0 148
文章 Qiao Peng · 一月 14, 2021 12m read

你好,开发者!

你们中的许多人在 Open Exchange 和 Github 上发布了 InterSystems ObjectScript 库。

但对于开发者来说,如何简化项目的使用和协作呢?

在本文中,我想介绍一种简单方法,只需将一组标准文件复制到你的仓库中,就可以启动任何 ObjectScript 项目和对其做出贡献。

我们开始吧!

TLDR - 将以下文件从该仓库复制到你的仓库:

Dockerfile

docker-compose.yml

Installer.cls

iris.script

settings.json{#9f423fcac90bf80939d78b509e9c2dd2-d165a4a3719c56158cd42a4899e791c99338ce73}

.dockerignore{#f7c5b4068637e2def526f9bbc7200c4e-c292b730421792d809e51f096c25eb859f53b637}
.gitattributes{#fc723d30b02a4cca7a534518111c1a66-051218936162e5338d54836895e0b651e57973e1}
.gitignore{#a084b794bc0759e7a6b77810e01874f2-e6aff5167df2097c253736b40468e7b21e577eeb}

你已经知道启动你的项目和协作的标准方式。 以下将详细说明这样做的步骤和原因。

**注意:**在本文中,我们将考虑可在 InterSystems IRIS 2019.1 及更新版本上运行的项目。

选择 InterSystems IRIS 项目的启动环境

通常,我们希望开发者尝试项目/库,并确保这是快速安全的练习。

在我看来,快速安全地启动任何新项目的理想方式是 Docker 容器,它可以保证开发者启动、导入、编译和计算任何内容对于主机来说都是安全的,并且不会破坏任何系统或代码。如果出了问题,只需停止并删除容器即可。 如果应用程序占用大量磁盘空间,使用容器将其擦除,空间就回来了。 如果某个应用程序破坏了数据库配置,只需删除配置被破坏的容器。 简单又安全。

Docker 容器提供安全和标准化。

运行通用 InterSystems IRIS Docker 容器的最简单方法是运行 IRIS 社区版映像

  1. 安装 Docker desktop 

  2. 在操作系统终端中运行以下命令:

docker run --rm -p 52773:52773 --init --name my-iris store/intersystems/iris-community:2020.1.0.199.0
  1. 然后在主机浏览器上打开管理门户:

http://localhost:52773/csp/sys/UtilHome.csp

  1. 或者打开终端启动 IRIS:

    docker exec -it my-iris iris session IRIS

  2. 不需要 IRIS 容器时,将其停止:

    docker stop my-iris

好! 我们在一个 docker 容器中运行 IRIS。 但是你希望开发者将你的代码安装到 IRIS 中,可能还要进行一些设置。 这就是我们下面要讨论的。

导入 ObjectScript 文件

最简单的 InterSystems ObjectScript 项目可以包含一组 ObjectScript 文件,例如类、例程、宏和global。 请查看有关命名和建议的文件夹结构的文章。

问题是如何将所有这些代码导入 IRIS 容器?

此时我们可以借助 Dockerfile 来获取通用 IRIS 容器,并将某个仓库中的所有代码导入 IRIS,然后根据需要对 IRIS 进行一些设置。 我们需要在仓库中添加一个 Dockerfile。

我们来看一下 ObjectScript 模板仓库中的 Dockerfile

ARG IMAGE=store/intersystems/irishealth:2019.3.0.308.0-community
ARG IMAGE=store/intersystems/iris-community:2019.3.0.309.0
ARG IMAGE=store/intersystems/iris-community:2019.4.0.379.0
ARG IMAGE=store/intersystems/iris-community:2020.1.0.199.0
FROM $IMAGE

USER root

WORKDIR /opt/irisapp
RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp

USER irisowner

COPY  Installer.cls .
COPY  src src
COPY iris.script /tmp/iris.script # run iris and initial 

RUN iris start IRIS \
    && iris session IRIS < /tmp/iris.script

 

前几个 ARG 行设置 $IMAGE 变量,随后在 FROM 中使用该变量。 这适合在不同的 IRIS 版本中测试/运行代码,只需在 FROM 前面的最后一行中更改 $IMAGE 变量即可切换版本。

这里我们采用:

ARG IMAGE=store/intersystems/iris-community:2020.1.0.199.0

FROM $IMAGE

这意味着我们将使用 IRIS 2020 社区版 build 199。

我们想要导入仓库中的代码,这意味着我们需要将仓库中的文件复制到 docker 容器。 下面几行完成此操作:

USER root

WORKDIR /opt/irisapp
RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp

USER irisowner

COPY  Installer.cls .
COPY  src src

USER root - 这里我们将用户切换为 root,以在 docker 中创建文件夹和复制文件。

WORKDIR  /opt/irisapp - 我们在此行中设置将文件复制到的工作目录。

RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp   -  这里我们为运行 IRIS 的 irisowner 用户和组授予权限。

USER irisowner - 将用户从 root 切换到 irisowner

COPY Installer.cls .  -Installer.cls 复制到工作目录的根目录。 不要漏了那个点儿。

COPY src src - 将仓库的 src 文件夹中的源文件复制到 docker 上的工作目录的 src 文件夹中。

在下一个块中,我们运行初始脚本,其中将调用安装程序和 ObjectScript 代码:

COPY iris.script /tmp/iris.script # run iris and initial 
RUN iris start IRIS \
    && iris session IRIS < /tmp/iris.script

COPY iris.script / - 我们将 iris.script 复制到根目录。 它包含我们要调用以设置容器的 ObjectScript。

RUN iris start IRIS</span>  - 启动 IRIS

&& iris session IRIS < /tmp/iris.script - 启动 IRIS 终端并在其中输入初始 ObjectScript。

很好! 我们有 Dockerfile,它将文件导入 docker。 但还有两个文件:installer.cls 和 iris.script。我们来检查一下。

Installer.cls

Class App.Installer
{

XData setup
{
<Manifest>
  <Default Name="SourceDir" Value="#{$system.Process.CurrentDirectory()}src"/>
  <Default Name="Namespace" Value="IRISAPP"/>
  <Default Name="app" Value="irisapp" />

  <Namespace Name="${Namespace}" Code="${Namespace}" Data="${Namespace}" Create="yes" Ensemble="no">

    <Configuration>
      <Database Name="${Namespace}" Dir="/opt/${app}/data" Create="yes" Resource="%DB_${Namespace}"/>

      <Import File="${SourceDir}" Flags="ck" Recurse="1"/>
    </Configuration>
    <CSPApplication Url="/csp/${app}" Directory="${cspdir}${app}"  ServeFiles="1" Recurse="1" MatchRoles=":%DB_${Namespace}" AuthenticationMethods="32"
       
    />
  </Namespace>

</Manifest>
}

ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]
{
  #; Let XGL document generate code for this method. 
  Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "setup")
}

}

坦白说,我们不需要 Installer.cls 就可以导入文件。 这只需要一行就能完成。 但除了导入代码之外,我们经常还需要设置 CSP 应用,引入安全设置,创建数据库和命名空间。

在此 Installer.cls 中,我们创建了名为 IRISAPP 的新数据库和命名空间,并为该命名空间创建了默认的 /csp/irisapp 应用程序。

所有这些都在元素中 <Namespace> 执行:

<Namespace Name="${Namespace}" Code="${Namespace}" Data="${Namespace}" Create="yes" Ensemble="no">

    <Configuration>
      <Database Name="${Namespace}" Dir="/opt/${app}/data" Create="yes" Resource="%DB_${Namespace}"/>

      <Import File="${SourceDir}" Flags="ck" Recurse="1"/>
    </Configuration>
    <CSPApplication Url="/csp/${app}" Directory="${cspdir}${app}"  ServeFiles="1" Recurse="1" MatchRoles=":%DB_${Namespace}" AuthenticationMethods="32"
       
    />
  </Namespace>

我们用 Import 标签导入 SourceDir 中的所有文件:

<Import File="${SourceDir}" Flags="ck" Recurse="1"/>

这里的 SourceDir 是一个变量,设置为当前目录/src 文件夹:

<Default Name="SourceDir" Value="#{$system.Process.CurrentDirectory()}src"/>

有了带这些设置的 Installer.cls,我们可以自信地创建一个干净的新数据库 IRISAPP,我们将从 src 文件夹导入任意 ObjectScript 代码到该数据库中。

iris.script

欢迎在这里提供任何所需的初始 ObjectScript 设置代码来启动 IRIS 容器。

例如, 我们加载并运行 installer.cls,然后让 UserPasswords 永远有效,以避免第一次启动时出现密码更改请求,因为开发不需要这个提示。

; run installer to create namespace
do $SYSTEM.OBJ.Load("/opt/irisapp/Installer.cls", "ck")
set sc = ##class(App.Installer).setup()  zn "%SYS"
Do ##class(Security.Users).UnExpireUserPasswords("*") ; call your initial methods here
halt

docker-compose.yml

为什么需要 docker-compose.yml,不能只用 Dockerfile 构建和运行映像吗? 可以的。 但 docker-compose.yml 能让生活更轻松。

通常,docker-compose.yml 用于启动连接到一个网络的多个 docker 映像。

当启动一个 docker 映像需要处理多个参数时,也可以使用 docker-compose.yml 来简化过程。 你可以使用它向 docker 传递参数,例如端口映射、卷、VSCode 连接参数。

version: '3.6' 
services:
  iris:
    build: 
      context: .
      dockerfile: Dockerfile
    restart: always
    ports: 
      - 51773
      - 52773
      - 53773
    volumes:
      - ~/iris.key:/usr/irissys/mgr/iris.key
      - ./:/irisdev/app

这里我们声明服务 iris,它使用 docker 文件 Dockerfile,并开放 IRIS 的以下端口:51773、52773、53773。 此服务还映射两个卷:将主机主目录中的 iris.key 映射到期望的 IRIS 文件夹,以及将源代码的根文件夹映射到 /irisdev/app 文件夹。

Docker-compose 提供了更短的统一命令来构建和运行映像,无论你在 docker compose 中设置了什么参数。

总之,构建和启动镜像的命令是:

$ docker-compose up -d

要打开 IRIS 终端:

$ docker-compose exec iris iris session iris

Node: 05a09e256d6b, Instance: IRIS

USER>

此外,docker-compose.yml 有助于设置 VSCode ObjectScript 插件的连接。

.vscode/settings.json

与 ObjectScript 加载项连接设置有关的部分如下:

{
    "objectscript.conn" :{
      "ns": "IRISAPP",
      "active": true,
      "docker-compose": {
        "service": "iris",
        "internalPort": 52773
      }
    }     

}

在这里,我们看到的设置与 VSCode ObjectScript 插件的默认设置不同。

我们想要连接到 IRISAPP 命名空间(我们用 Installer.cls 创建的):

"ns": "IRISAPP",

一个 docker-compose 设置指示,docker-compose 文件在服务“iris”内,VSCode 将连接到 52773 映射到的端口:

"docker-compose": {
        "service": "iris",
        "internalPort": 52773
      }

如果我们检查一下 52773 的相关设置,我们会看到没有为 52773 定义映射端口:

ports: 
      - 51773
      - 52773
      - 53773

这意味着将使用主机上的随机可用端口,并且 VSCode 将通过随机端口自动连接到 docker 上的 IRIS。

这是一个非常方便的功能,因为它允许在随机端口上运行任意数量的带 IRIS 的 docker 映像,并让 VSCode 自动连接到它们。

其他文件呢?

我们还有:

.dockerignore  - 该文件可用于过滤你不想复制到所构建的 docker 映像中的主机文件。 通常 .git 和 .DS_Store 是必须有的行。

.gitattributes - git 的属性,用于统一来源中的 ObjectScript 文件的行尾。 如果仓库由 Windows 和 Mac/Ubuntu 所有者协作,此文件非常有用。

.gitignore - 你不希望 git 跟踪其更改历史记录的文件。 通常是一些隐藏的操作系统级文件,例如 .DS_Store。

好了!

如何使你的仓库可被 docker 运行并且对协作友好?

  1. 克隆此仓库

  2. 复制以下所有文件:

Dockerfile

docker-compose.yml

Installer.cls

iris.script

settings.json{#9f423fcac90bf80939d78b509e9c2dd2-d165a4a3719c56158cd42a4899e791c99338ce73}

.dockerignore{#f7c5b4068637e2def526f9bbc7200c4e-c292b730421792d809e51f096c25eb859f53b637}
.gitattributes{#fc723d30b02a4cca7a534518111c1a66-051218936162e5338d54836895e0b651e57973e1}
.gitignore{#a084b794bc0759e7a6b77810e01874f2-e6aff5167df2097c253736b40468e7b21e577eeb}

到你的仓库。

更改 Dockerfile 中的这一行,使目录与仓库中要导入 IRIS 的 ObjectScript 匹配(如果在 /src 文件夹中则不要更改)。

就这样。 每个人(也包括你)都会将你的代码导入到新的 IRISAPP 命名空间的 IRIS 中。

人们如何启动你的项目

在 IRIS 中执行任何 ObjectScript 项目的法则为:

  1. Git clone 项目到本地

  2. 运行项目:

$ docker-compose up -d
$ docker-compose exec iris iris session iris

Node: 05a09e256d6b, Instance: IRIS

USER>zn "IRISAPP"

开发者如何为你的项目做出贡献

  1. 对仓库执行分叉,并将分叉后的仓库 git clone 到本地

  2. 在 VSCode 中打开该文件夹(还需要在 VSCode 中安装 DockerObjectScript 扩展)

  3. 右击 docker-compose.yml->重启 - VSCode ObjectScript 将自动连接并准备好编辑/编译/调试

  4. 向你的仓库提交、推送和拉取请求更改

以下是操作过程的短 gif:

好了! 编码愉快!

0
0 250
文章 Nicky Zhu · 一月 11, 2021 9m read

简介

许多应用程序都需要记录数据库中的数据变化,包括:哪些数据被更改、更改人和更改时间(审计日志记录) (维基百科audit logging)。 关于这个问题已经有了很多文章,而关于如何在Caché中实现也有很多不同的方法。

本文将介绍一个机制,帮助您实现用一个框架来跟踪和记录数据更改。一旦您的持久类继承自“审计抽象类”(Sample.AuditBase),此机制将通过“objectgenarator”方法创建一个触发器。由于这个持久类继承了Sample.AuditBase,所以当您编译持久类时,将自动生成用于审计更改的触发器。


Audit Class  

这是将记录更改的类。

Class Sample.Audit Extends %Persistent
{
          Property Date As %Date;
          Property UserName As %String(MAXLEN = "");
          Property ClassName As %String(MAXLEN = "");
          Property Id As %Integer;
          Property Field As %String(MAXLEN = "");
          Property OldValue As %String(MAXLEN = "");
          Property NewValue As %String(MAXLEN = "");
}

Audit Abstract Class  

这是您的持久类将继承的抽象类。这个类包含触发器方法(objectgenerator),除了在审核表(Sample.Audit)中写入更改之外,该触发器方法还知道如何识别哪些字段被更改、更改人、新旧值等。

Class Sample.AuditBase [ Abstract ]
{
Trigger SaveAuditAfter [ CodeMode = objectgenerator, Event = INSERT/UPDATE, Foreach = row/object, Order = 99999, Time = AFTER ]
{
          #dim %compiledclass As %Dictionary.CompiledClass
          #dim tProperty As %Dictionary.CompiledProperty
          #dim tAudit As Sample.Audit
          Do %code.WriteLine($Char(9)_"; get username and ip adress")
          Do %code.WriteLine($Char(9)_"Set tSC = $$$OK")
          Do %code.WriteLine($Char(9)_"Set tUsername = $USERNAME")
          Set tKey = ""
          Set tProperty = %compiledclass.Properties.GetNext(.tKey)
          Set tClassName = %compiledclass.Name
          Do %code.WriteLine($Char(9)_"Try {")
          Do %code.WriteLine($Char(9,9)_"; Check if the operation is an update - %oper = UPDATE")
          Do %code.WriteLine($Char(9,9)_"if %oper = ""UPDATE"" { ")
          While tKey '= "" {
                    set tColumnNbr = $Get($$$EXTPROPsqlcolumnnumber($$$pEXT,%classname,tProperty.Name))
                    Set tColumnName = $Get($$$EXTPROPsqlcolumnname($$$pEXT,%classname,tProperty.Name))
                    If tColumnNbr '= "" {
                              Do %code.WriteLine($Char(9,9,9)_";")
                              Do %code.WriteLine($Char(9,9,9)_";")
                              Do %code.WriteLine($Char(9,9,9)_"; Audit Field: "_tProperty.SqlFieldName)
                              Do %code.WriteLine($Char(9,9,9)_"if {" _ tProperty.SqlFieldName _ "*C} {")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit = ##class(Sample.Audit).%New()")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.ClassName = """_tClassName_"""")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.Id = {id}")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.UserName = tUsername")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.Field = """_tColumnName_"""")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.Date = +$Horolog")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.OldValue = {"_tProperty.SqlFieldName_"*O}")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tAudit.NewValue = {"_tProperty.SqlFieldName_"*N}")
                              Do %code.WriteLine($Char(9,9,9,9)_"Set tSC = tAudit.%Save()")
                              do %code.WriteLine($Char(9,9,9,9)_"If $$$ISERR(tSC) $$$ThrowStatus(tSC)")
                              Do %code.WriteLine($Char(9,9,9)_"}")
                    }
                    Set tProperty = %compiledclass.Properties.GetNext(.tKey)
          }
          Do %code.WriteLine($Char(9,9)_"}")
          Do %code.WriteLine($Char(9)_"} Catch (tException) {")
          Do %code.WriteLine($Char(9,9)_"Set %msg = tException.AsStatus()")
          Do %code.WriteLine($Char(9,9)_"Set %ok = 0")
          Do %code.WriteLine($Char(9)_"}")
          Set %ok = 1
}
}

Data Class (Persistent Class)

这是用户数据类,用户(应用程序)可以在其中进行更改、创建记录、删除记录,以及任何您允许他做的事情。总之,这通常是持久化类。

要开始跟踪和记录更改,您需要从抽象类(Sample.AuditBase)继承这个持久类。

Class Sample.Person Extends (%Persistent, %Populate, Sample.AuditBase)
{
          Property Name As %String [ Required ];
          Property Age As %String [ Required ];
          Index NameIDX On Name [ Data = Name ];
}

测试

由于您从审计抽象类(Sample.AuditBase)继承了数据类(Sample.Person),因此您能够插入数据、进行更改并查看审计类(Sample. Audit)上记录的更改。

如果要测试一下,您需要在Sample.Person类或您选择的任何其他类创建一个Test()类方法。

ClassMethod Test(pKillExtent = 0)
{
          If pKillExtent '= 0 {
                    Do ##class(Sample.Person).%KillExtent()
                    Do ##class(Sample.Audit).%KillExtent()
          }
          &SQL(INSERT INTO Sample.Person (Name, Age) VALUES ('TESTE', '01'))
          Write "INSERT INTO Sample.Person (Name, Age) VALUES ('TESTE', '01')",!
          Write "SQLCODE: ",SQLCODE,!!!
          Set tRS = $SYSTEM.SQL.Execute("SELECT * FROM Sample.Person")
          Do tRS.%Display()
          &SQL(UPDATE Sample.Person SET Name = 'TESTE 2' WHERE Name = 'TESTE')
          Write !!!
          Write "UPDATE Sample.Person SET Name = 'TESTE 2' WHERE Name = 'TESTE'",!
          Write "SQLCODE:",SQLCODE,!!!
          Set tRS = $SYSTEM.SQL.Execute("SELECT * FROM Sample.Person")
          Do tRS.%Display()
          Quit
}

运行Test()方法:

d ##class(Sample.Person).Test(1)

参数1将消除Sample.Person和Sample.Audit类的内容。

 

测试类方法负责:

  • 插入一个名为“TEST”的新person;
  • 显示插入结果;
  • 将person “TEST”更新为“TEST ABC”
  • 显示更新结果;

现在您可以检查审核日志表。为此,依次打开系统管理门户 -> 系统探索 -> SQL。(记得切换到您的命名空间)

运行以下 SQL 命令并检查结果:

SELECT * FROM Sample.Audit 

注意,OldValue是“TEST”,NewValue是“TEST ABC”。现在您可以通过将“TEST ABC”的名称更改为“您自己的名称”或更改年龄值(举个例子)来自行测试。见:

UPDATE Sample.Person SET Name = 'Fabio Goncalves' WHERE Name = 'TEST ABC'


生成的代码

考虑到您已经实现了下面的审计机制,可以在您的计算机上启动Studio(或Atelier),打开持久类(Sample.Person)并检查编译Sample.Person类后生成的中间代码。为此,您只要按下Ctrl + Shift + V(查看其他源代码)- 检查 .INT。然后向下滚动到zSaveAuditAfterExecute标签,查看生成的代码:

 

优点

通过滚出旧数据实现审计日志记录,很简单。您不需要附加表。维护也很简单。如果您决定删除旧数据,那么这就是一个SQL的问题。

如果您需要在更多表中实现审计日志记录,您只需从抽象类(Sample.AuditBase)继承即可。

根据您的需要进行更改,例如:在流上记录更改。

只记录已修改的字段。不保存已更改的整个记录。

缺点

有这样的问题:当数据更改时,那么整个记录会被复制,也就是没更改的数据也会被复制。

如果表person有一个带有包含图片的二进制数据(流)字段-“photo”,那么每次用户更改图片时,流都会被记录(消耗磁盘空间)。  

另一个缺点是,增加了每个支持审计日志记录的表的复杂性。请时刻牢记,检索记录不是一件简单的事情。切记必须有条件地使用SELECT子句:“...WHERE Status = active”或考虑添加“日期间隔”。

考虑事务和回滚。


对于某些应用程序来说,为了提高效率,审计是一个重要的需求。通常,要确定数据更改,应用程序开发人员必须在其应用程序中通过组合使用触发器、时间戳列和附加表来实现自定义跟踪方法。创建这些机制,通常需要做大量的工作来实现,因此导致方案改动,且常常大幅增加性能开销。这是一个简单的例子,可以帮助您开始创建自己的框架。  

请阅读 下一篇文章! 

0
0 389
文章 Nicky Zhu · 一月 11, 2021 5m read

本文将描述通过ObjectScript包管理器(见https://openexchange.intersystems.com/package/ObjectScript-Package-Manager-2)运行单元测试的过程,包括测试覆盖率测量(见https://openexchange.intersystems.com/package/Test-Coverage-Tool)。

ObjectScript中的单元测试

关于在ObjectScript中编写单元测试,已经有很好的文档,因此我就不再赘述了。您可以在这里找到单元测试教程:https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=TUNT_preface

最好的做法是将单元测试代码单独放在源代码树中,无论它只是“/tests”还是其他名字。在InterSystems中,我们最终使用/internal/testing/unit_tests/作为我们事实上的标准,这是有意义的,因为测试是内部/非发布的,而且除了单元测试还有其他类型的测试,但这对于简单的开源项目来说可能有点复杂。您可以在我们的一些GitHub仓库中看到这种结构。

从工作流的角度来看,这在VSCode中非常简单,您只需创建目录并将类放在里面。对于较老的以服务器为中心的源代码控制方法(Studio中使用的方法),您需要正确地地映射这个包,使用的方法会因源代码控制程序而异。

从单元测试类命名的角度来看,我个人的偏好(以及我的团队的最佳实践)是:

UnitTest.<待测试的包/类>[.<待测试的方法/特性>] 例如,如果在类MyApplication.SomeClass 中对方法Foo进行单元测试,单元测试类将被命名为UnitTest.MyApplication.SomeClass.Foo;如果测试是针对整个类的,那么名字就是UnitTest.MyApplication.SomeClass。

ObjectScript 包管理器中的单元测试

让ObjectScript包管理器知道您的单元测试,很简单!只需按如下所示向module.xml中添加一行代码(来自https://github.com/timleavitt/ObjectScript-Math/blob/master/module.xml - 这是Open Exchange上的@Get Oxycodone Online Trusted Affordable & Fast Steiwer的出色数学扩展包,我以它作为简单的正面例子):

<Module> 
  ... 
  <UnitTest Name="tests" Package="UnitTest.Math" Phase="test"/> 
</Module> 

这些代码的意思是:

  • 单元测试位于模块根目录下的“tests”目录中。
  • 单元测试在“UnitTest.Math”包中。这样很直观,因为被测试的类就在“Math”包中。
  • 单元测试在包生命周期的“测试”阶段运行。(当然还有一个可以运行它们的“验证”阶段,这里不赘述。)

运行单元测试

对于上述定义的单元测试,包管理器提供了一些实用工具来运行它们。您仍然可以像平常使用%UnitTest.Manager那样设置^UnitTestRoot等,但下面的方法可能更简单,尤其在同一个环境下做几个项目的时候。

您可以克隆上述objectscript-math仓库,然后用 zpm "load /path/to/cloned/repo/" 加载,或者在您自己的包上使用包名(和测试名)替换“objectscript-math”来尝试所有这些方法。

重新加载模块,然后运行所有单元测试: zpm "objectscript-math test"

只运行单元测试(不重新加载): zpm "objectscript-math test -only"

只运行单元测试(不重新加载)并提供详细输出: zpm "objectscript-math test -only -verbose"

运行一个特定的测试套件(指一个测试目录 - 在本例中,是UnitTest/Math/Utils中的所有测试)而不重新加载,并提供详细输出: zpm "objectscript-math test -only -verbose -DUnitTest.Suite=UnitTest.Math.Utils"

运行一个特定的测试用例(在本例中,是UnitTest.Math.Utils.TestValidateRange)而不重新加载,并提供详细输出: zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange"

如果您只是想解决单个测试方法中的小问题: zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange -DUnitTest.Method=TestpValueNull"

通过ObjectScript包管理器进行测试覆盖率测量

怎样评估单元测试的质量?测量测试覆盖率虽然并不全面,但至少有参考意义。早在2018年的全球峰会上,我就展示过。 见 - https://youtu.be/nUSeGHwN5pc

首先需要安装“测试覆盖率”包: zpm "install testcoverage"

注意,并不需要ObjectScript包管理器才能安装/运行;可以在Open Exchange上了解更多信息:https://openexchange.intersystems.com/package/Test-Coverage-Tool

不过如果您已经在用ObjectScript包管理器,那么您可以更好地利用这个“测试覆盖率”工具。

运行测试前,需要指定测试所覆盖的类/routine宏。这一点很重要,因为在非常大的代码库中(例如,HealthShare),测试和收集项目中所有文件的测试覆盖率所需要的内存可能超出您的系统内存。(提一句,如果您感兴趣,可以使用逐行监视器的gmheap。)

文件列表在您的单元测试根目录下的coverage.list文件中;单元测试的不同子目录(套件)可以拥有它们自己的副本,以覆盖在测试套件运行时将跟踪的类/例程。

有关objectscript-math的简单示例,见:https://github.com/timleavitt/ObjectScript-Math/blob/master/tests/UnitTest/coverage.list;测试覆盖率工具用户指南有更详细的介绍。

要在启用测试覆盖率测量的情况下运行单元测试,只需再向命令添加一个参数,指定应使用TestCoverage.Manager而非%UnitTest.Manager 来运行测试: zpm "objectscript-math test -only -DUnitTest.ManagerClass=TestCoverage.Manager"

输出(即使是非详细模式)将包括一个URL,供您查看您的类/routine(宏)的哪些行被单元测试覆盖了,以及一些汇总统计信息。

接下来的步骤

这些能不能在CI中自动化?能不能报告单元测试的结果和覆盖率分数/差异?答案是:能!您可以使用Docker,Travis CI和codecov.io来试一下这个简单示例,见https://github.com/timleavitt/ObjectScript-Math;我打算以后写篇文章来详细讲讲,介绍几种不同的方法。

0
0 263
文章 Jeff Liu · 一月 8, 2021 5m read

你好!

本文简单介绍一款工具,帮您理解InterSystems产品(从IRISCachéEnsemble以及HealthShare)中的类及其结构。

简言之,它将类或整个包可视化,显示类之间的关系,并向开发人员和团队领导提供各种信息,而无需到 Studio 中检查代码。

如果您正在学习InterSystems产品,经常查看项目,或只对InterSystems技术解决方案中的新内容感兴趣,欢迎阅读ObjectScript类浏览器概述!

InterSystems 产品简介

IRIS(之前称为Caché) 是一个多模型DBMS。您可以使用SQL查询来访问它,也可以通过各种编程语言可用的接口与存储的对象和过程进行交互。但最多的还是使用DBMS原生内置语言--ObjectScript (COS) 开发应用程序。

Caché支持DBMS级别的类。有两种主要的类类型:Persistent(可以存储在数据库中)和 Registered(不存储在数据库中,扮演程序和处理程序的角色)。还有几种特殊的类类型:Serial(可集成到持久类中用于创建复杂数据类型(如:地址)的类),DataType(用于创建用户定义的数据类型)、IndexView Stream

进入类浏览器

0
1 473