Featured image of post Golang 项目集成 SonarQube 做代码质量分析

Golang 项目集成 SonarQube 做代码质量分析

测试覆盖率, 代码质量分析分析

安装配置

安装

配置Gitlab登录

  • 文档说明: https://docs.sonarsource.com/sonarqube/10.1/instance-administration/authentication/gitlab/

    • 创建应用https://<Your Gitlab URL>/-/profile/applications(read_user权限)
    • 注意生成callback<Your SonarQube URL>/oauth2/callback/gitlab
  • 再次访问登录页出现Gitlab登录按钮代表配置成功

    • 这里注意一定要配置Server base URL否则无法跳转成功
    • 配置Gitlab导入项目
    • 创建Token https://<Your Gitlab URL>/-/profile/personal_access_tokens
    • Gitlab网址为 https://<Your Gitlab URL>/api/v4
    • 配置成功之后,在创建项目的地方就会出现Gitlab选项
  • 配置多分支(默认只能一个分支)

项目配置

  • SonarQube创建好项目需要准备三个配置(变量名自定义)
    • SONAR_HOST_URL=(sonar的访问网址)
    • SONAR_PROJECT_KEY=(项目标识)
    • SONAR_TOKEN=(令牌, 创建完项目最后一步会给出创建页面)
    • 把以上三个配置到Gitlab

.gitlab-ci.yml

sonarqube-check:
  stage: check
  ## 需要 runner 是 Docker 类型才可以, 当下不好用, 以下配置多余的忽略
  image:
    name: sonarsource/sonar-scanner-cli:latest
    entrypoint: [""]
  variables:
    SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
    GIT_DEPTH: "0"
    ## 注意引入三个变量
    SONAR_HOST_URL: "${SONAR_HOST_URL}"
    SONAR_TOKEN: "${SONAR_TOKEN}"
    SONAR_PROJECT_KEY: "${SONAR_PROJECT_KEY}"
  allow_failure: false
  only:
    - main
    - dev
  cache:
    key: "${CI_JOB_NAME}"
    paths:
      - .sonar/cache
  coverage: '/total:\s+\(statements\)\s+\d+.\d+%/'
  script:
    - bash ./deployment/sonarqube-check.sh
    - sonar-scanner -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.token=$SONAR_TOKEN -Dsonar.projectKey=$SONAR_PROJECT_KEY -Dsonar.branch.name=$CI_COMMIT_REF_NAME

.golangci.yml

run:
  # 并发运行线程数
  concurrency: 4
  # 分析超时时间
  timeout: 5m
  # 退出状态码
  issues-exit-code: 1
  # 包含测试文件
  tests: true
  # 忽略以下默认目录
  #   vendor$, third_party$, testdata$, examples$, Godeps$, builtin$


output:
  # 输出的格式
  # colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions
  # 打印有问题的代码行
  print-issued-lines: true
  # 打印没通过哪个检测器
  print-linter-name: true
  # 问题唯一输出
  uniq-by-line: true
  # 按文件路径排序
  sort-results: false

linters:
  disable-all: true  # 关闭其他linter
  enable:            # 下面是开启的linter列表,之后的英文注释介绍了相应linter的功能
    # 降低代码复杂度
    - cyclop
    - gocognit
    - gocyclo
    - maintidx
    # 高可拓展性的go源码linter
    - gocritic
    # 禁止保留未使用的代码块
    #---------------------------------------
    - ineffassign
    - durationcheck
    # 所有err都要处理
    - errcheck
    # 在Go 1.13之后使用errors.Wrap可能导致的问题
    - errorlint
    # 检查switch的全面性,以免遗漏场景
    - exhaustive
    # 禁止将for-range value的指针暴露给外部
    - exportloopref
    # 禁止出现长函数
    - funlen
    # 如果有相同的string变量请使用consts替换
    - goconst
    # 禁止出现长语句
    - lll
    # 返回两个参数,一个数据,一个是err,禁止两个都是nil
    - nilnil
    # 如果知道slice大小,定义时需分配空间
    - prealloc
    # 检查prometheus meteics的指标名是否规范
    - promlinter
    # 强制要求const/import/var在一个组
    - grouper
    # 检查变量名长度
    - varnamelen
    # 强制一致性的impotr别名
    - importas
    # 类型断言时需检查是否成功
    - forcetypeassert
    # 保证类型、常量、变量和函数的声明顺序和数量
    - decorder
    # 检查err的定义规范--types类型的定义是以Error结尾的,常量的定义是Err打头的
    - errname
    # 禁止errors使用'=='和'!='等表达式--与nil和io.EOF比较除外
    - err113
    # 官方代码格式化
    - gofmt
    - goimports
    # 检查依赖的黑白名单
    - gomodguard
    # 检查类似printf的函数是否以f结尾
    - goprintffuncname
    # 官方错误检查
    - govet
    # 检查拼写错误
    - misspell
    # 如果函数过长,禁用裸返回
    - nakedret
    # 禁止深度嵌套的if语句
    - nestif
    # 禁止使用Go关键字命名
    - predeclared
    # 去掉没有必要的type转换
    - unconvert
    # 检查帮助函数里面有没有调用t.Helper()函数
    - thelper

linters-settings:
  cyclop:
    # 函数最大复杂度=(函数本身是1 + 1 * ('if', 'for', 'case', 'select', '&&', '||'))
    # 1 - 10 程序简单,风险小
    # 11 - 20 更复杂,中等风险
    # 21 - 50 复杂、高风险
    # 50 不可测试的代码,非常高的风险
    max-complexity: 30
    # 跳过测试
    skip-tests: false
  gocognit:
    # 代码复杂度
    min-complexity: 30
  gocyclo:
    # 代码复杂度
    min-complexity: 30
  dogsled:
    # val, _, _ := xx() 这种的 _ 不能超过 n 个
    max-blank-identifiers: 2
  errcheck:
    # `a := b.(MyStruct)`; 类型断言不加 err
    check-type-assertions: true
    # `num, _ := strconv.Atoi(numStr)`; 忽略错误
    check-blank: true
    # 要忽略检测的函数列表
    # see https://github.com/kisielk/errcheck#excluding-functions for details
    exclude-functions:
      - io.Copy(*bytes.Buffer)
      - io.Copy(os.Stdout)
  errorlint:
    # 打印错误只能 fmt.Errorf("oh noes: %w", err), 而不是 fmt.Errorf("oh noes: %v", err)
    errorf: true
    # 使用 ok := errors.As(err, &me), 而不是 myErr, ok := err.(*MyError)
    asserts: true
    # 使用 errors.Is(err, ErrFoo), 而不是 err == ErrFoo
    comparison: true

  exhaustive:
    # 枚举检查, 枚举在 switch 中不能写 default
    default-signifies-exhaustive: false

  funlen:
    # 函数不能超过 100 行
    lines: 100
    # 40 个语句 (statements + 空白行 < lines)
    statements: 40

  goconst:
    # 字符串常量最小长度
    min-len: 3
    # 当这个字符串超过 n 次, 就应该替换为常量
    min-occurrences: 3
    # 忽略测试
    ignore-tests: false
    # 查找有现有常量定义的字符串
    match-constant: true
    # 数字常量也要定义
    numbers: true
    # 数字的最小值
    min: 3
    # 数字的最大值
    max: 99999
    # 当行数不用坐参数时,是否忽略(不忽略, 当 if 的时候也要判断)
    ignore-calls: false

  gofmt:
    # 代码格式化
    simplify: true

  goimports:
    # 优化 import 引入
  lll:
    # 每一行最大长度
    line-length: 120
    # tab 宽度=1
    tab-width: 1

  makezero:
    # 只允许初始化长度为0的切片。非零长度的切片 append
    always: false
  nestif:
    # 深度嵌套 if, 最大只能 n
    min-complexity: 4

  nilnil:
    # 不能同时返回两个 nil
    checked-types:
      - ptr
      - func
      - iface
      - map
      - chan

  nlreturn:
    # return 之前有一个空行
    block-size: 1
  varnamelen:
    max-distance: 9
    min-name-length: 1

  #  prealloc:
  #    # 优化与分配, 不要项目刚开始的时候使用
  #    simple: true
  #    range-loops: true
  #    for-loops: false

  predeclared:
    # 声明的东西不能是预订的名字, 比如 new,int
    ignore: ""
    # include method names and field names (i.e., qualified names) in checks
    q: false

  staticcheck:
    # 静态检测
    checks: [ "all" ]

  stylecheck:
    # 代码风格检测
    # https://staticcheck.io/docs/options#checks
    checks: [ "all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022" ]
    # https://staticcheck.io/docs/options#dot_import_whitelist
    dot-import-whitelist:
      - fmt
    # https://staticcheck.io/docs/options#initialisms
    initialisms: [ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS" ]
    # https://staticcheck.io/docs/options#http_status_code_whitelist
    http-status-code-whitelist: [ "200", "400", "404", "500" ]

  unparam:
    # 没使用的参数
    check-exported: false

  unused:
    exported-is-used: false
    exported-fields-are-used: false

./deployment/sonarqube-check.sh

#!/bin/bash

go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct

go mod tidy

## 代码质量检查
golangci-lint run ./... --out-format checkstyle > report.xml
ret01=$?
if [ $ret01 -ne 0 ]; then
    echo -e "\033[31m  >>>> 代码质量检查失败,退出 <<<<  \033[0m"
    exit 1
fi


## 代码覆盖率
go test ./... -coverprofile="coverage.cov" -covermode count
ret02=$?
if [ $ret02 -ne 0 ]; then
    echo -e "\033[31m  >>>> 代码覆盖率检查失败,退出 <<<<  \033[0m"
    exit 2
fi

## 给 gitlab 正则匹配找到覆盖率
go tool cover -func="coverage.cov"


## 代码测试
go test -json ./... > report.json
ret03=$?
if [ $ret03 -ne 0 ]; then
    echo -e "\033[31m  >>>> 代码测试失败,退出 <<<<  \033[0m"
    exit 3
fi

exit 0

最后结果

  • 一切准备就绪之后, 就可以在你的代码仓库显示统计数据

本作品采用知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,可适当缩放并在引用处附上图片所在的文章链接。