0


2312d,D语言单元测试等

原文
我最近决定在

"系统编程"

领域试些

小众语言

.我已用了

Java,Dart

Kotlin

高级语言编程

多年了(并试了许多其他

相同级别

或更高级的语言),需要扩大视野,因为对

某些类型

应用,

这些语言

并不是最好的工具.

这篇博文中,我想重点介绍

D语言

这里,经过

一些

初步实验,它比

其他语言

更能引起注意.
我还尝试了

Zig

Nim

12,但觉得它们

不适合

我,至少

现在

是这样.
当然,我已试过

Rust

,但是

Rust

虽然在很多方面都是个

天才语言

,但并没有真正让我对

编写代码

感到兴奋.相反,一想到

周末

要花时间与

借用检查器

作斗争,我就充满了

恐惧

.

我绝对会在工作环境中使用

Rust

(且已这样了),因为它的

安全保证

(不仅是

内存安全

,还有

资源

和线程安全)和

出色

的性能(在

低内存消耗

原始速度

方面),但对

业余

爱好项目,谢谢.

在我看来,

Nim

(另一

非常有趣

语言),在

另一端

走得太远了,

安全

不如

速度和快乐

重要.因此,如果你喜欢它(

速度

非常快,创建

微小

二进制文件且使用

很少

的内存),它可能只是适合你的语言.

Zig

有很多承诺,但目前还没有

准备

好.尽管它专注于

简单性

,但也

非常冗长且难以正确

使用.

D

似乎是个

很好的平衡

.它很容易

熟悉

,同时

有些

非常有趣的功能.它已

存在

了足够长的时间,已足够

稳定

.

本文,我想分享我所学到的东西,

重点

元编程

单元测试

.

D简介

2023

年,D并不是一门新语言.它自

2001

年以来一直存在,但从那时起已有了很大的发展,特别是自

2010

年左右的

D2

版本稳定以来.

它有

3个

不同且维护良好的

编译器

,下载页:

1,

DMD

是用

D

自身编写的

参考

编译器.
2,

GDC

是D的

GCC

前端.
3,基于

LLVM

LDC

.

DMD

一般用来

更快编译

(事实上,它可能是

生产级语言

中最快

编译器

之一),但

其他两个

一般更擅长

优化

运行时速度.

在介绍

D的功能

方面D语言旅游做得

非常出色

,而D的

Gems

部分

特别有趣

,因为它展示了

D有

的,而大多数

其他语言

所没有的东西,如

(UFCS)

统一函数调用语法,域保护,

(CTFE)

编译时函数求值,(如

@safe,@nogc,@mustuse

)属性等等.

另见包括消息传递和线本存储的

多线程节

,用它们来共同支持使用类似

Actor

模型来编写

并发代码

.
讨论更高级功能前,先展示一些

D示例

.

下面显示了

D切片

的实际效果:

importstd.stdio : writeln;voidmain(){int[] test =[3,9,11,7,2,76,90,6];
    test.writeln;writeln("First element: ", test[0]);writeln("Last element: ", test[$ -1]);writeln("Exclude the first two elements: ",
        test[2.. $]);writeln("Slices are views on the memory:");auto test2 = test;auto subView = test[3.. $];
    test[]+=1;//将每个元素递增1
    test.writeln;
    test2.writeln;
    subView.writeln;//创建空切片assert(test[2..2].length ==0);}

编译并运行它:

  dmd -of=slices slices.d
  ./slices 
[3,9,11,7,2,76,90,6]
First element:3
Last element:6
Exclude the first two elements:[11,7,2,76,90,6]
Slices are views on the memory:[4,10,12,8,3,77,91,7][4,10,12,8,3,77,91,7][8,3,77,91,7]

还可直接

dmd -runfile.d

rdmd

(

DMD

自带),从源码运行

D程序

.甚至可按脚本运行:

#!/usr/bin/env rdmd

它显示了许多有趣的

特征

.
1,

test.writeln

writeln(test)

相同.这就是

UFCS

.
2,

test[$-1]

,显示了如何在

[]

中按

数组/切片

长度使用

$符号

.
3,

test[2..$]

,类似同样使用

$

Go

典型切片

.
4,

test[]+=1

,显示了可由

编译器

优化的

向量运算

.
5,

assert(test[2 .. 2].length == 0);

,

D

断定,稍后用来

测试单元

.
相当不错.

D元编程

D有许多

元编程

功能.

元编程

是针对程序自身编程的编程.

Lisp

可能是使用

宏元编程

的先驱,但

并不是

元编程

的唯一方法.
如,如下例所示,

D

有允许在

编译时

检查类型的

模板

,以

特化

函数:

@safe:autoconcat(T)(T lhs, T rhs){staticif(is(T:double)){//T可转换为双精return lhs + rhs;}else{//'~'一般是D中的`连接`符号return lhs ~ rhs;}}
unittest {assert(2.concat(3)==5);assert(4.2.concat(0.8)==5.0);assert("Hello".concat(" D")=="Hello D");}

运行

单元测试

:

  dmd -w -main -unittest -run tests.d 
 //1个模块通过单元测试

该示例有点傻,因为D支持

重载符号

这里,所以只能这样.

如果熟悉

Java

,

concat

类似

通用静态方法

,但与

Java

不同,

D

允许

编译时

检查类型,因此可专门针对

某些类型

特化函数.

static if

是编译时执行的

if

块,

运行时

不存在.
注意,模板有

两个

参数列表:一个包含

编译时参数

,另一个包含

运行时参数

.如果

D编译器

可推导

编译时参数

,则可省略它.
可用

!

符号显式提供编译时参数.

如,

std.conv

标准模块中的

to

模板,把类型当参数,但因为一般

无法推导

,因此几乎总是

显式传递

:

unittest {importstd.conv: to;assert(42.to!string =="42");}

而这只是最基本的

D模板

.
还可用

template

关键字来

执行

更高级操作,如

生成多个函数

:

templateBiDirectionalConverter(T1, T2){importstd.conv: to;
  T2 convert(T1 t){return t.to!T2();}
  T1 convert(T2 t){return t.to!T1();}}
unittest {
  alias StringIntConv = BiDirectionalConverter!(string,int);assert(StringIntConv.convert("20")==20);assert(StringIntConv.convert(20)=="20");}
std.conv

中的八进制(octal)模板,用来在D中声明编译时的

八进制

:

voidmain(){importstd.stdio: writeln;importstd.conv;writeln(octal!"750");}

运行:

  dmd -run tests.d
488

强烈建议浏览

D模板

教程,以

了解

更多信息.

D中的

另一个

模板是

插件

模板.它是一个允许好像在

周围域内

编写它一样,

直接

调用点

粘贴代码的

复制和粘贴模板

.

mixin templateAbcd(T){
  T a, b, c, d;}
unittest {
  mixin Abcd!int;
  a =10;assert(a ==10);assert(b ==0);assert(c ==0);assert(d ==0);}

最后,还可用

串插件

这里生成

代码串

:

///用T类型的`a,b`和c字段构建`一个结构`.
string abcStruct(T)(string name){return"struct "~ name
    ~" { "~ T.stringof ~" a; "~ T.stringof ~" b; "~ T.stringof ~" c; "~" }\n";}
unittest {mixin(abcStruct!string("StringStruct"));mixin(abcStruct!int("IntStruct"));auto abcstr =StringStruct("hey","ho","let's go");assert(abcstr.a =="hey");assert(abcstr.b =="ho");assert(abcstr.c =="let's go");auto abcint =IntStruct(42);assert(abcint.a ==42);assert(abcint.b ==0);assert(abcint.c ==0);}

D可用

-mixin

标志创建包含

编译过程

中生成的所有

插件

文件

:

  dmd -w -main -unittest -mixin=mixins.d -run tests.d
 1个模块通过单元测试

现在,查看

mixins.d

文件,找到D编译器生成的

结构

:

//测试.d中扩展.d(67)structStringStruct{ string a; string b; string c;}//测试.d中扩展.d(68)structIntStruct{int a;int b;int c;}

或,用pragma

编译指示

,以便编译时

D

仅打印

生成代码

:

pragma(msg, abcStruct!double("DoubleStruct"));//dmd -w -main -of=tests tests.d//结果:structDoubleStruct{double a;double b;double c;}

更多的

mixin

技巧
官方D文档中的

(Parser)

代码生成示例,显示了编译时很容易解析

来生成

常量配置数据

.

单元测试

前例中,使用

unittest

块来演示D的一些功能.我想很明显,

编译单元

中一般不

包含

这些块中代码,因此编译器

运行

测试时,必须传递

-unittest

选项给

编译器

(要实际

运行测试

,或执行生成的

二进制文件

,加上

-run

选项).

回顾下,如下

单元测试

:

unittest {assert(2+2==4);}

把上面的4更改为5并

运行代码

:

  dmd -w -main -of=tests -run tests.d

使用

-main

选项,以便编译器在没有

函数

时生成空

main

函数.

-w

标志,按错误对待警告,

-of

来命名

输出文件

.用

--help

查看

所有选项

.

如果

不打印内容

,则所有

测试

都正常.即没有

运行

测试.
现在用

-unittest

重试:

  dmd -w -main -unittest -run tests.d
 `tests.d(18):[unittest]unittest`失败
 `1/1`模块失败的单元测试

输出非常简单.它只是告诉你有

多少模块

的测试失败了,及

断定失败

的文件和行.

快速测试

来说不错,但

最好

告诉失败的

真正原因

论坛.

如,这是我

想出

的一个显示

失败断定

期望结果和实际结果的

小模板

,来使

断定

更强大:

autoassertThat(string desc, string op, T)(T lhs, T rhs){importstd.conv: to;const str ="assert(lhs "~ op ~" rhs, \""~
    desc ~": \" ~ lhs.to!string() ~ \" "~ op ~" \" ~ rhs.to!string())";returnmixin(str);}

现在,

断定

如下:

unittest {
  assertThat!("adding two and two","==")(2+2,5);}
运行

它:

  dmd -w -main -unittest -run tests.d
 `tests.d-mixin-20(20):[unittest]`加二加二`:4==5`
 `1/1`模块失败的单元测试

真酷!
顺便,

D

单元测试一般来

验证函数

属性是否符合

期望

(D编译器一般会

推导

它们,给

每个函数

手动注解

大量属性

非常麻烦).
如,在

D中

实现

时,我试

测试

:

@safe @nogc nothrow pure unittest {auto tree =Tree([0,0,0,2],[10,11,12,13]);
    assertThat!("children(2) basic case","==")(tree.children(2),[3,-1]);}

仅当按

@safe @nogc nothrow pure

注解,推导

unittest

中使用的函数时,才有效(编译器会

传递性

检查这些函数).
结果

如下

:

   myd dmd -unittest -run source/app.d 
...一堆错误略...

很有意思!

另一个

常见

用例是只运行

单个测试

,编译器不支持,但你可自己做,正如

@jfondren

D论坛

上所示:

moduletester1;
unittest {assert(true);}
unittest {assert(!!true);}
unittest {assert(1!=1);}
unittest {assert(1>0);}version(unittest){booltester(){importstd.meta : AliasSeq;importstd.stdio : writef, writeln;
        alias tests = AliasSeq!(__traits(getUnitTests, tester1));staticforeach(i;0.. tests.length){
            writef!"Test %d/%d ..."(i +1, tests.length);try{
                tests[i]();writeln("ok");}catch(Throwable t){writeln("failed");}}returnfalse;}
    shared staticthis(){importcore.runtime : Runtime;
        Runtime.moduleUnitTester =&tester;}}voidmain(){assert(false);//这不会运行}

运行它:

  dmd -w -main -unittest -run tests.d
 测试1/4...好
 测试2/4...好
 测试3/4...失败
 测试4/4...好
非常整洁

,但可能不是你想要的.

这使用了如

getUnitTests

特征等

相当高级

的东西(

D

特征是

元编程

,如果你来自

Rust

Scala

,概念可能不一样)和

UDA

(编译时注解)这里.
dub包管理

最后

IDE

支持似乎还不错,但与

Java,Kotlin,Typescript

甚至

Rust

等主流语言相去甚远.
我首先试使用

emacs

(你需要获得d模式,然后安装

serve-d

这里,

LSP

服务器,也支持

VSCode

的D支持).
然后注意到

D

IntelliJ

插件非常强大,并且作为

Jebrains

产品的大用户,很好惊喜(一般,小众语言在

IntelliJ

中没有很好的支持)!

IntelliJ

插件的

开发者

致敬!它提供了非常好的

开箱即用

体验,来生成

片段

漂亮模板

,

代码浏览

(包括进入

Dstdlib

,非常适合学习),

内置文档

风格精美,有扫描器,因此在代码中显示

警告

,通过

dfmt

自动

格式化

,内置支持

dub

.
如果用

d-unit

作为

依赖

,甚至可运行

测试

.

D

,还支持

CPU

和内存分析,及非常好的文档工具

ddoc

这里,与在

Rust

中一样,可在

编译时

执行

D

文档,确保

文档示例

总是有效!

我厌倦了像

Java

这样基于

VM

的语言,并且不太喜欢编写

Rust

,

D

可能会成为我

下个

最喜欢的

语言

.

标签: d

本文转载自: https://blog.csdn.net/fqbqrr/article/details/135063994
版权归原作者 fqbqrr 所有, 如有侵权,请联系我们删除。

“2312d,D语言单元测试等”的评论:

还没有评论