设计和单元测试跨平台应用程序

时间:2018-02-20 23:04:34

标签: go

我正在开发一个为windows(当前)构建的项目,并将在未来为darwin构建。

以下是快速概述:

file1.go:

package management

type Manager interface {
    Action1()
}

file2_windows.go:

package management

type WinManager struct {
    some configs
}

func (WinManager) Action1() {
   ...
}

func InitWinManager() WinManager {
    create and return inited WinManager with configs
}

handler.go:

package handle

func handle() {
    ...

    var m Manager
    if runtime.GOOS = "windows" {
        m = InitWinManager()
    }

    ... 
}

Q1:如何正确避免针对特定操作系统的这种条件初始化?

关于CI的几句话 - 因为我们的构建机器在linux上运行,我需要一个.exe文件,我就像这样构建它:

  1. 使用特定于版本的属性和可执行文件的名称创建versioninfo.json
  2. 使用GOOS=windows go generate
  3. 在main.go上运行//go:generate goversioninfo
  4. 运行GOOS=windows GOARCH=amd64 go build -o application.exe
  5. 它构建得很好,我没有任何麻烦。

    go test ./handle提出了另一个问题。在运行实际测试之前,它尝试编译测试包,但由于我的构建机器的操作系统与windows不同,因此无法找到InitWinManager()方法。

    所以 Q2:如何在go test中指定要运行的操作系统版本?

2 个答案:

答案 0 :(得分:3)

将Windows特定代码放在file2_windows.go中。此文件仅为Windows目标生成。

package management

type Manager struct {
    ... Windows specific type
}

func (Manager) Action1() {
   ... Windows specific code
}

func InitManager() Manager {
    create and return inited Manager with configs
}

将Darwin特定代码放在file2_darwin.go中。此文件仅为Darwin目标构建。

package management

type Manager struct {
    ... Darwin specific type
}

func (Manager) Action1() {
   ... Darwin specific code
}

func InitManager() Manager {
    create and return inited Manager with configs
}

从处理程序函数调用InitManager:

m := InitManager()

这会在Darwin目标上调用Darwin版本,在Windows目标上调用Windows版本。

以上使用文件名约定来设置build constraints。也可以使用注释指定约束。使用

// +build windows

指定仅为Windows目标和

构建包含文件
// +build darwin

指定仅为Darwin目标构建包含文件。

我假设您将Manager定义为Windows和Darwin特定管理器的通用接口。因为一次只在代码中构建一个管理器,所以不需要为此目的定义接口。如果还有其他原因需要接口,请将上面使用的类型类型更改为ManagerImpl或类似的类型。

Q2:对于与当前系统不同的目标系统,无法运行go test

答案 1 :(得分:2)

我认为构建标志将成为你的朋友。

有没有理由将Darwin版本的Manager编译成Windows可执行文件?可能不是,为什么甚至参考呢?反之亦然。

如果$ GOOS是Windows,你可以通过将以下行放在文件顶部来告诉go build / test只构建一个文件...

// +build windows

这意味着在Darwin上构建时将完全忽略该文件。

这对你有什么用?那么,您可以实现相同的两种不同方式,而不是实现两种管理器类型!像这样......

windowsmanager.go

// +build windows

package management

type MyManager struct {
    some configs
}

func (MyManager) PlatformGnosticAction1() {
   ...
}

func InitMyManager() MyManager {
    create and return inited windows version of MyManager with configs
}

同样,对于达尔文来说

darwinmanager.go

// +build darwin

package management

type MyManager struct {
    some configs
}

func (MyManager) PlatformGnosticAction1() {
   ...
}

func InitMyManager() MyManager {
    create and return inited darwin version of MyManager with configs
}

现在,只有一个版本的MyManager会被编译,所以调用它的代码不需要知道它们之间的区别......

handler.go

package handle

func handle() {
    ...

    m := InitMyManager()

    ... 
}

如果编译了文件,标签会产生影响,所以如果darwinmanager.go和windowsmanager.go之间有很多公共代码,你可能想要重构那么多的公共代码。您的标记文件,但在同一个包中。 E.G。

mymanager.go

package Manager

func (m *MyManager) APlatformAgnosticFunc() {
    ...
}


func (m *MyManager) AnotherPlatformAgnosticFunc() {
    ...
}

在标记文件中保留平台不可知内容。

你可以用文件后缀显然做类似的事情,但我从来没有尝试过。 Dave Cheney写了一篇关于这个主题的更全面的博客,你可以读到here

希望有所帮助!

中号