Skip to content

源码导读

Ai-feier edited this page May 29, 2024 · 6 revisions

选型分析

  1. 面向接口编程与抽象设计

    2.1 选型考虑

    • 接口定义:面向接口编程,使用依赖注入。
    • 依赖注入容器:digwire

    2.2 实施建议

    • 遵循SOLID原则,特别是接口隔离原则。l
    • 使用依赖注入容器简化管理。
  2. Go反射与unsafe编程

    3.1 选型考虑

    • 反射支持:选择支持Go反射特性的ORM框架。
    • 安全性:慎用unsafe包,确保安全性。

    3.2 实施建议

    • 封装反射逻辑,提高可读性。
    • 对使用unsafe的代码进行仔细审查。
  3. Go并发编程

    4.1 选型考虑

    • Go协程与通道:选择适合Go并发编程的ORM框架。
    • 内存分配:理解Go的内存分配模型。

    4.2 实施建议

    • 确保ORM框架是并发安全的。
    • 了解Go调度器工作原理。
  4. SQL编程

    5.1 选型考虑

    • SQL生成与执行:选择支持良好的SQL生成与执行功能的ORM框架。
    • 事务支持:确保ORM框架提供良好的事务支持。

    5.2 实施建议

    • 理解并优化生成的SQL语句。
    • 选择稳定性能良好的数据库驱动。
  5. 结论

    综合考虑面向接口设计、反射与unsafe使用、并发编程和SQL操作等。确保所选框架在满足业务需求的同时,提供高效、安全、可测试的解决方案。了解框架文档、社区活跃度,以获得及时的支持和帮助。

系统设计

ORM 框架工作流程

image-20240117131239311

源码对象架构

image-20240117131155535

顶级抽象

// Querier sql 查询语句抽象
type Querier[T any] interface {
	Get(ctx context.Context) (*T, error)
	GetMulti(ctx context.Context) ([]*T, error)
}

// Executor insert update 语句执行抽象
type Executor interface {
	Exec(ctx context.Context) Result
}

// Query sql 中间结构体
// SQL: sql 语句
// Args: sql 语句中的占位符参数
type Query struct {
	SQL  string
	Args []any
}

// QueryBuilder 构造 sql 语句的抽象
type QueryBuilder interface {
	Build() (*Query, error)
}

// Session 抽象的数据库操作接口
// 包含 事务 与 非事务
type Session interface {
	queryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
	execContext(ctx context.Context, query string, args ...any) (sql.Result, error)
}

元数据

Registry 注册中心抽象

// Registry 元数据注册中心的抽象
type Registry interface {
	// Get 查找元数据
	Get(val any) (*Model, error)
	// Register 注册一个模型
	Register(val any, opts ...Option) (*Model, error)
}

Registry 注册中心底层数据实现

存储 元数据(用户结构体 与 操作数据对象)

type registry struct {
	models sync.Map
}

Registry 的具体实现的底层数据结构采用 go 提供的 sync.Map 保证了并发安全

核心方法

func (r *registry) Get(val any) (*Model, error)

func (r *registry) Register(val any, opts ...Option)

// 接受一级结构体指针
// parseModel 支持从标签中提取自定义设置
// 标签形式 orm:"key1=value1,key2=value2"
func (r *registry) parseModel(val any) (*Model, error)

func (r *registry) parseTag(tag reflect.StructTag) (map[string]string, error)

Model

用户结构体名 -> Model -> database.sql

type Model struct {
	// TableName 结构体对应的表名
	TableName string
	Fields    []*Field
	FieldMap  map[string]*Field
	ColumnMap map[string]*Field
}

// Field 字段
type Field struct {
	ColName string
	GoName  string
	Type    reflect.Type
	Index   int
	// Offset 相对于对象起始地址的字段偏移量
	Offset uintptr
}

Model Option

扩展 Model 属性

type Option func(m *Model) error

WithColumnName

func WithColumnName(field string, columnName string) Option {
	return func(model *Model) error {
		fd, ok := model.FieldMap[field]
		if !ok {
			return errs.NewErrUnknownField(field)
		}
		// 注意,这里我们根本没有检测 ColName 会不会是空字符串
		// 因为正常情况下,用户都不会写错
		// 即便写错了,也很容易在测试中发现
		fd.ColName = columnName
		return nil
	}
}

扩展

tag parser

支持的 tag

const (
	tagKeyColumn = "column"
)

TableName

// TableName 用户实现这个接口来返回自定义的表名
type TableName interface {
	TableName() string
}

流程

TODO: picture

调用方创建模型注册中心 -> 调用方向数据中心注册元数据

调用方向数据中获取元数据 -> 数据中心查找响应的元数据 (如果没找到就注册该条元数据, 懒加载实现)

注册模型

func (r *registry) Register(val any, opts ...Option) (*Model, error)
	->	func (r *registry) parseModel(val any) (*Model, error)
		-> func (r *registry) parseTag(tag reflect.StructTag) (map[string]string, error)

获取元数据

func (r *registry) Get(val any) (*Model, error) => 是否已有元数据
	-> func (r *registry) Register(val any, opts ...Option) (*Model, error)
		->	func (r *registry) parseModel(val any) (*Model, error)
			-> func (r *registry) parseTag(tag reflect.StructTag) (map[string]string, error)

核心代码

获取调用方结构体反射类型 (check接受一级结构体指针)

​ -> 逐一解析结构体字段

​ -> 解析其 tag 填充 Field 结构体

// 接受一级结构体指针
// parseModel 支持从标签中提取自定义设置
// 标签形式 orm:"key1=value1,key2=value2"
func (r *registry) parseModel(val any) (*Model, error) {
	typ := reflect.TypeOf(val)
	if typ.Kind() != reflect.Ptr ||
		typ.Elem().Kind() != reflect.Struct {
		return nil, errs.ErrPointerOnly
	}
	typ = typ.Elem()

	// 获得字段的数量
	numField := typ.NumField()
	fds := make(map[string]*Field, numField)
	fields := make([]*Field, 0, numField)
	colMap := make(map[string]*Field, numField)
	for i := 0; i < numField; i++ {
		fdType := typ.Field(i)
		tags, err := r.parseTag(fdType.Tag)
		if err != nil {
			return nil, err
		}
		colName := tags[tagKeyColumn]
		if colName == "" {
			colName = underscoreName(fdType.Name)
		}
		f := &Field{
			ColName: colName,
			Type:    fdType.Type,
			GoName:  fdType.Name,
			Offset:  fdType.Offset,
			Index:   i,
		}
		fds[fdType.Name] = f
		fields = append(fields, f)
		colMap[colName] = f
	}
	var tableName string
	if tn, ok := val.(TableName); ok {
		tableName = tn.TableName()
	}

	if tableName == "" {
		tableName = underscoreName(typ.Name())
	}

	return &Model{
		TableName: tableName,
		Fields:    fields,
		FieldMap:  fds,
		ColumnMap: colMap,
	}, nil
}

DB

定义

type DB struct {
	dialect    Dialect
	r          model.Registry
	db         *sql.DB
	valCreator valuer.Creator
}

拥有元数据注册中心, 维护与数据库的连接, 并提供数据库查询结果集向对象的映射实现

创建数据连接

// Open 创建一个 DB 实例。
// 默认情况下,该 DB 将使用 MySQL 作为方言
// 如果你使用了其它数据库,可以使用 DBWithDialect 指定
func Open(driver string, dsn string, opts ...DBOption) (*DB, error) {
	db, err := sql.Open(driver, dsn)
	if err != nil {
		return nil, err
	}
	return OpenDB(db, opts...)
}

// 语法糖
func OpenDB(db *sql.DB, opts ...DBOption) (*DB, error)
func MustNewDB(driver string, dsn string, opts ...DBOption) *DB

操作数据库 (非事务)

func (db *DB) queryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) {
	return db.db.QueryContext(ctx, query, args...)
}

func (db *DB) execContext(ctx context.Context, query string, args ...any) (sql.Result, error) {
	return db.db.ExecContext(ctx, query, args...)
}

操作数据库 (事务版)

Tx

// Tx 继承 Session 操作数据库
type Tx struct {
	tx *sql.Tx
	db *DB
}

事务类图:

classDiagram
    class Dialect
    class Registry
    class DB
    class Creator
    class Middleware

    class DBOption {
        +DBOption(*DB)
    }

    class DB {
        - dialect: Dialect
        - r: model.Registry
        - db: *sql.DB
        - valCreator: valuer.Creator
        - mdls: []Middleware
        + Open(driver: string, dsn: string, opts: ...DBOption): (*DB, error)
        + OpenDB(db: *sql.DB, opts: ...DBOption): (*DB, error)
        + DBWithDialect(dialect: Dialect): DBOption
        + DBWithRegistry(r: model.Registry): DBOption
        + DBUseReflectValuer(): DBOption
        + DBUseMiddlewares(mdls: ...Middleware): DBOption
        + MustNewDB(driver: string, dsn: string, opts: ...DBOption): *DB
        + Close(): error
        + Wait(): error
        + queryContext(ctx: context.Context, query: string, args: any): (*sql.Rows, error)
        + execContext(ctx: context.Context, query: string, args: any): (sql.Result, error)
        + BeginTx(ctx: context.Context, opts: *sql.TxOptions): (*Tx, error)
        + DoTx(ctx: context.Context, fn: func(ctx: context.Context, tx: *Tx) error, opts: *sql.TxOptions): error
    }

    class Tx {
        - tx: *sql.Tx
        - db: *DB
        + Commit(): error
        + Rollback(): error
        + RollbackIfNotCommit(): error
    }


		DB "1" *-- "1" Dialect 
		DB "1" *-- "0..*" DBOption 
    DB "1" *-- "1" sql_DB
    DB "1" *-- "1" Creator
    DB "1" *-- "1" Registry
    DB "1" *-- "0..*" Middleware

    Tx "1" *-- "1" DB
Loading

DB Option

type DBOption func(*DB)

DBWithRegistry 更换数据中心实现

func DBWithRegistry(r model.Registry) DBOption {
	return func(db *DB) {
		db.r = r
	}
}

DBUseReflectValuer 更改数据返回结果实现

func DBUseReflectValuer() DBOption {
	return func(db *DB) {
		db.valCreator = valuer.NewReflectValue
	}
}

优雅启动, 确保数据正确连接

func (db *DB) Wait() error {
	err := db.db.Ping()
	for errors.Is(err, driver.ErrBadConn) {
		log.Println("数据库启动中")
		err = db.db.Ping()
	}
	return nil
}

Valuer

作用

在返回数据库查询查询结果时, 填充数据

Interface

// Value 是对结构体实例的内部抽象
type Value interface {
	// Field 返回字段对应的值
	Field(name string) (any, error)
	// SetColumns 设置新值
	SetColumns(rows *sql.Rows) error
}

函数式工厂构造

type Creator func(val any, meta *model.Model) Value

实现类

基于 reflect 实现

类型

// reflectValue 基于反射的 Value
type reflectValue struct {
	val  reflect.Value
	meta *model.Model
}

构造方法

// NewReflectValue 返回一个封装好的,基于反射实现的 Value
// 输入 val 必须是一个指向结构体实例的指针,而不能是任何其它类型
func NewReflectValue(val interface{}, meta *model.Model) Value {
	return reflectValue{
		val:  reflect.ValueOf(val).Elem(),
		meta: meta,
	}
}

填充数据库查询结果

func (r reflectValue) SetColumns(rows *sql.Rows) error

反射获取查询返回结果
用反射填充 valuer 对象值

基于 unsafe 实现

类型

type unsafeValue struct {
	// 基准地址
	addr unsafe.Pointer
	meta *model.Model
}

构造方法

func NewUnsafeValue(val interface{}, meta *model.Model) Value {
	return unsafeValue{
		addr: unsafe.Pointer(reflect.ValueOf(val).Pointer()),
		meta: meta,
	}
}

填充数据库查询结果

func (u unsafeValue) SetColumns(rows *sql.Rows) error

根据 unsafe 基准地址 + offset 在对应位置, 利用反射在当前位置开辟一个新对象
把其地址指向查询返回结果

获取 field 的值

func (u unsafeValue) Field(name string) (any, error)

benchmark 测试

// 执行命令: go test -bench=BenchmarkQuerier_Get -benchmem -benchtime=10000x
// goos: windows
// goarch: amd64
// pkg: LORM/v8
// cpu: Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz
// BenchmarkQuerier_Get/unsafe-8              10000            418712 ns/op            3324 B/op        112 allocs/op
// BenchmarkQuerier_Get/reflect-8             10000           1462437 ns/op            3503 B/op        120 allocs/op
// PASS
// ok      LORM/v8 19.787s
// 可以看出 unsafe 的性能远远快于直接使用 reflect

builer

Interface

Expression 代表语句,或者语句的部分

// Expression 代表语句,或者语句的部分
type Expression interface {
    expr()
}

Assignable 标记接口

// Assignable 标记接口,
// 实现该接口意味着可以用于赋值语句,
// 用于在 UPDATE 和 UPSERT 中
type Assignable interface {
    assign()
}

Selectable select 语句中 colume, rawexpr, aggregate 的抽象

// Selectable select 语句中 colume, rawexpr, aggregate 的抽象
type Selectable interface {
    selectable()
}

实现类

Predicate 代表一个查询条件

  • Expression
// Predicate 代表一个查询条件
// Predicate 可以通过和 Predicate 组合构成复杂的查询条件
type Predicate struct {
    left  Expression
    op    op
    right Expression
}

Aggregate 代表聚合函数,例如 AVG, MAX, MIN 等

  • Expression
  • Selectable
// Aggregate 代表聚合函数,例如 AVG, MAX, MIN 等
type Aggregate struct {
    fn    string
    arg   string
    alias string
}

Column 列名

  • Expression
  • Selectable
  • Assignable
type Column struct {
    name  string
    alias string
}

RawExpr 原生 sql 语句

  • Expression
  • Selectable
  • Assignable
// RawExpr 原生 sql 语句
type RawExpr struct {
    raw  string
    args []any
}

binaryExpr, MathExpr 带有关系的表达式

  • Expression
type binaryExpr struct {
    left  Expression
    op    op
    right Expression
}

type MathExpr binaryExpr
func (m MathExpr) Add(val interface{}) MathExpr
func (m MathExpr) Multi(val interface{}) MathExp

value 代表单独的值, 可单独作为表达式

type value struct {
    val any
}

builder 实现类

type builder struct {
	// 构造 SQL
	sb strings.Builder
	// 存放 SQL 参数
	args []any
	// 存放当前对象的元数据信息
	model *model.Model
	// 方言抽象
	dialect Dialect
	quoter  byte
}

Dialect 方言

兼容不同数据库的方言

type Dialect interface {
    // quoter 返回一个引号,引用列名,表名的引号
    quoter() byte
    // buildUpsert 构造插入冲突部分
    buildUpsert(b *builder, odk *Upsert) error
}

mysql, sqlite3 方言实现

var (
    MySQL   Dialect = &mysqlDialect{}
    SQLite3 Dialect = &sqlite3Dialect{}
)

核心方法

// buildColumn 构造列
func (b *builder) buildColumn(fd string) error

// 构造方言的quote
func (b *builder) quote(name string)

// 构造原生表达式
func (b *builder) raw(r RawExpr)

// 构造 sql 语句参数
func (b *builder) addArgs(args ...any)

// 构造条件表达式
func (b *builder) buildPredicates(ps []Predicate) error

// 构造表达式
func (b *builder) buildExpression(e Expression) error

// 构造二分表达式
func (b *builder) buildBinaryExpr(e binaryExpr) error
流程:
err := b.buildSubExpr(e.left)  => 构造做部分
左右部分有可能又是一个 Predicate, 所以递归构造, 到最后为 raw, value


// 构造二分表达式的右半部分
func (b *builder) buildSubExpr(subExpr Expression) error

// 构造 Aggregate 表达式
func (b *builder) buildAggregate(a Aggregate, useAlias bool) error

// 构造 As 别名
func (b *builder) buildAs(alias string)

Selector

定义

构造 select 语句发送给数据库处理

用户需传入要查询的结构体(泛型的使用)

采用构造复杂结构的 Build 模式, 链式调用

// Selector 用于构造 SELECT 语句
type Selector[T any] struct {
	builder
	table   string
	db      *DB
	where   []Predicate
	having  []Predicate
	columns []Selectable
	groupBy []Column
	limit   int
	offset  int
}

构造方法

// NewSelector 构造 Selector
func NewSelector[T any](db *DB) *Selector[T] {
	return &Selector[T]{
		builder: builder{
			dialect: db.dialect,
			quoter:  db.dialect.quoter(),
		},
		db: db,
	}
}

Build

依次构造: SELECT 列名... FROM 表名 WHERE 条件 GROUP BY 列名 HAVING 条件 LIMIT limit OFFSET offest

列名, 表名有不同数据库的方言

// Build 构造 sql 查询语句, 底层调用 database.sql 查询数据库
func (s *Selector[T]) Build() (*Query, error)

其余方法及 Build() 流程

// Select 选择要查询的列
func (s *Selector[T]) Select(cols ...Selectable) *Selector[T]

// From 指定表名,如果是空字符串,那么将会使用默认表名
func (s *Selector[T]) From(tbl string) *Selector[T] 

func (s *Selector[T]) buildColumns() error

func (s *Selector[T]) buildColumn(c Column, useAlias bool) error

// Where 用于构造 WHERE 查询条件。如果 ps 长度为 0,那么不会构造 WHERE 部分
func (s *Selector[T]) Where(ps ...Predicate) *Selector[T]

// GroupBy 设置 group by 子句
func (s *Selector[T]) GroupBy(cols ...Column) *Selector[T]

func (s *Selector[T]) Having(ps ...Predicate) *Selector[T]

func (s *Selector[T]) Offset(offset int) *Selector[T]

func (s *Selector[T]) Limit(limit int) *Selector[T]

Build() 流程:
s.model, err = s.db.r.Get(&t) => 从注册中心获取模型
s.sb.WriteString("SELECT ")  => 构造固定语法
if err = s.buildColumns();   => 构造列
	-> for i, c := range s.columns  => 遍历所有列名
		-> switch val := c.(type)  => 判断列类型
			-> case Column:  => 普通列名构造
				if err := s.buildColumn(val, true);
			-> case Aggregate:  => 构造 Aggregate 类型列
					if err := s.buildAggregate(val, true);
			-> case RawExpr:  => 构造原生 sql 
					s.raw(val)
s.sb.WriteString(" FROM ")  => 构造固定语法
s.quote(s.model.TableName) || s.sb.WriteString(s.table)  => 构造表名
if len(s.where) > 0; if err = s.buildPredicates(s.where); => 构造 where 条件语句
	-> for i := 1; i < len(ps); i++ {  => 不同的 Predicate And 连接
		p = p.And(ps[i])
	-> b.buildExpression(p)  => 构造表达式
if len(s.groupBy) > 0  => 构造 group by
if len(s.having) > 0   => 构造 having
if s.limit > 0         => 构造 limit
if s.offset > 0        => 构造 offset

Build() 流程图

graph TD
    start(["调用selector的Build()"]) --> selector[/将selector字段作为输入/] -->
    A(从注册中心获取模型) --> B(构造固定语法 SELECT)
    B --> C(构造select选中列)
    C ==> switch{判断列类型}
    switch -->|Column| E(普通列名构造)
    switch -->|Aggregate| F(构造Aggregate类型列)
    switch -->|RawExpr| G(构造原生sql列)
    E --> from(构造固定语法 FROM)
    F --> from
    G --> from

    from --> table(构造表名)
    from --> join(构造Join语句)
    from --> subquery(构造子查询语句)
    table --> where(构造WHERE条件语句)
    join --> where
    subquery --> where
    where --> K(不同的Predicate AND连接)
    K --> L(构造表达式)
    L --> group(构造GROUP BY)
    group --> having(构造HAVING)
    having --> limit(构造LIMIT)
    limit --> offset(构造OFFSET) -->
    
    out[/构建完成的sql/] --> 
    finish(["selector的Build()结束"])

Loading

Get() 与 GetMulti()

func (s *Selector[T]) Get(ctx context.Context) (*T, error)

func (s *Selector[T]) GetMulti(ctx context.Context) (res []*T, err error)

q, err := s.Build()  => 构造 sql
rows, err := db.QueryContext(ctx, q.SQL, q.Args...)  => 执行 sql
val := s.db.valCreator(tp, meta)  => 构造 valuer
err = val.SetColumns(rows)  => 填充数据

Get() 与 GetMulti() 流程图:

graph TD
    start(["调用selector的Get() 或 GetMulti()()"]) -->
    selector(/将selector字段作为输入/) -->
    build(构造SQL语句) -->
    exec(执行SQL) -->
    sql[(查询数据库)] -->
    |数据| valuer(构造Valuer) -->
    SetColumns(填充数据) -->
    data(/返回结果集/) -->
    finish([结束])
Loading

Deleter

  • Executor
  • QueryBuilder
type Deleter[T any] struct {
	builder
	tableName string
	where     []Predicate
	db        *DB
}

Build() (*Query, error)

d.model, err = d.db.r.Get(&t)  => 获取模型
d.sb.WriteString("DELETE FROM ")  => 构造固定部分
if d.tableName == ""  => 获取删除表名
if len(d.where) > 0  => 是否存在 where 条件
	if err = d.buildPredicates(d.where); err != nil  => 构造 where 条件

Exec(ctx context.Context) Result

q, err := d.Build()  => 构造 sql
res, err := d.db.db.ExecContext(ctx, q.SQL, q.Args...)  => 执行 sql

Inserter & Upsert

  • Executor
  • QueryBuilder
type Inserter[T any] struct {
	builder
	values  []*T
	db      *DB
	columns []string
	upsert  *Upsert
}

Build() (*Query, error)

m, err := i.db.r.Get(i.values[0])  => 获取模型
i.sb.WriteString("INSERT INTO ")  => 构造固定部分
i.quote(m.TableName)
i.sb.WriteString("(")
fields := m.Fields   => 获取列名
if len(i.columns) != 0
for idx, fd := range fields  => 构造 insert 列名
i.sb.WriteString(") VALUES")
for vIdx, val := range i.values  => 遍历传入参数, 构造 insert 
	-> refVal := i.db.valCreator(val, i.model)  => 构造 valuer
       for fIdx, field := range fields  => 遍历列名
			-> fdVal, err := refVal.Field(field.GoName)  => 获取列名值, 加入 inserter args, 替换 ?
if i.upsert != nil  => 支持 upsert 语句, 不同数据库格式不同

Exec(ctx context.Context) Result

q, err := i.Build()  => 构造 sql
res, err := i.db.db.ExecContext(ctx, q.SQL, q.Args...)  => 执行 sql

Updater

  • Executor
  • QueryBuilder

Build() (*Query, error)

u.model, err = u.db.r.Get(&t)  => 获取模型
u.sb.WriteString("UPDATE ")  => 构造固定部分
u.quote(u.model.TableName)
u.sb.WriteString(" SET ")
val := u.db.valCreator(u.val, u.model)  => 构造 valuer
for i, a := range u.assigns  => 遍历所有要更改的字段
	-> switch assign := a.(type)  => 判断不同的类型
if len(u.where) > 0  => 构造 where

Exec(ctx context.Context) Result

q, err := u.Build()  => 构造 sql
res, err := u.db.db.ExecContext(ctx, q.SQL, q.Args...)  => 执行 sql

Middleware

定义

// Middleware 函数式中间件
type Middleware func(next HandleFunc) HandleFunc

type HandleFunc func(ctx context.Context, qc *QueryContext) *QueryResult

// QueryContext Middleware 向下传递的 Context
type QueryContext struct {
	// Type 声明查询类型。即 SELECT, UPDATE, DELETE 和 INSERT
	Type    string
	Builder QueryBuilder
	// Model 向 Middleware 提供当前 sql 操作的元数据信息
	Model   *model.Model
}

// QueryResult 每个 Handler 向上返回的结果
type QueryResult struct {
	Result any
	Err    error
}

系统测试

概述

这个项目作为Go ORM框架的中间件,在系统测试中确实需要特别关注各个组成部分的单元测试,以确保整体的稳定性、安全性和降低错误率。以下是对这些方面的更详细扩展:

单元测试的重要性:

  1. 系统稳定性: 单元测试有助于发现和修复代码中的潜在问题,确保每个组件都按照预期工作。通过检查每个模块的输入和输出,可以识别潜在的错误和边界情况,从而提高整个系统的稳定性。
  2. 安全性: 在ORM框架中,数据的处理和存储是至关重要的。通过单元测试,可以验证数据的正确性、完整性和安全性。检测潜在的安全漏洞,例如SQL注入、数据泄漏等,有助于提高系统的安全性。
  3. 错误率降低: 单元测试是在开发早期就发现和解决问题的有效手段。通过在每个组件上运行测试,可以迅速发现并修复潜在的错误,降低后期维护的成本,并提高整个项目的质量。

单元测试用例的设计:

  1. 模块独立性: 每个单元测试用例应该专注于测试一个特定的组件或模块,确保它在隔离的环境中运行。这有助于迅速定位和解决问题。
  2. 边界条件: 单元测试应覆盖各种输入情况,包括正常情况和边界条件。这可以确保系统在各种情况下都能正确运行,提高系统的健壮性。
  3. 错误处理: 测试应该包括对错误处理代码的覆盖,以确保系统在出现异常情况时能够正确响应并提供有用的错误信息。
  4. 性能测试: 虽然性能测试通常在系统测试阶段进行,但在单元测试中也可以包含一些基本的性能测试,以确保每个组件在合理的时间内完成任务。
  5. 数据一致性: 对于ORM框架,确保数据的一致性和正确性是至关重要的。单元测试应该验证数据的正确插入、更新和删除,并确保查询结果是准确的。
  6. Mock和Stub的使用: 使用Mock和Stub技术模拟外部依赖,以确保每个组件在测试中的独立性。这有助于在不同组件之间解耦,使测试更加可靠和可维护。

测试覆盖率的监控:

使用工具来监控测试覆盖率,确保所有关键代码路径都得到了测试。高测试覆盖率通常与更高的代码质量和可维护性相关。

持续集成和持续部署:

将单元测试集成到持续集成和持续部署流程中,以确保每次代码更改都通过了所有单元测试。这有助于快速检测和修复潜在问题,保持整个系统的健康状态。

通过认真设计和执行单元测试,您可以确保ORM框架的各个组成部分在各种情况下都能够正确、高效地运行,从而提高系统的可靠性和稳定性。

测试用例设计

Registry 单元测试

TestModelWithTableName 测试用例设计:

  1. 设置空字符串表名:

    • 描述:测试是否允许将表名设置为空字符串。
    • 输入:WithTableName("")
    • 期望结果:返回的 ModelTableName 应为空字符串。
  2. 设置非空字符串表名:

    • 描述:测试是否能够成功设置非空字符串的表名。
    • 输入:WithTableName("test_model_t")
    • 期望结果:返回的 ModelTableName 应为 "test_model_t"。

TestWithColumnName 测试用例设计:

  1. 设置新的列名:

    • 描述:测试是否能够成功设置新的列名。
    • 输入:WithColumnName("FirstName", "first_name_new")
    • 期望结果:返回的 Model 的对应字段的列名应为 "first_name_new"。
  2. 设置空字符串的新列名:

    • 描述:测试是否允许将新列名设置为空字符串。
    • 输入:WithColumnName("FirstName", "")
    • 期望结果:返回的 Model 的对应字段的列名应为空字符串。
  3. 设置不存在的字段的新列名:

    • 描述:测试是否在设置新列名时能够正确处理不存在的字段。
    • 输入:WithColumnName("FirstNameXXX", "first_name")
    • 期望结果:返回错误 ErrUnknownField("FirstNameXXX")

TestRegistry_get 测试用例设计:

  1. 测试 Model 类型:

    • 描述:检查是否能够正确处理 Model 类型,即非指针的类型。
    • 输入:TestModel{}
    • 期望结果:返回错误 errors.New("orm: 只支持一级指针作为输入,例如 *User")
  2. 测试指针类型:

    • 描述:检查是否能够正确处理指针类型的 Model。
    • 输入:&TestModel{}
    • 期望结果:返回包含正确表名、字段等信息的 Model
  3. 测试多级指针:

    • 描述:检查是否能够正确处理多级指针。
    • 输入:func() any { val := &TestModel{}; return &val }()
    • 期望结果:返回错误 errors.New("orm: 只支持一级指针作为输入,例如 *User")
  4. 测试非指针类型:

    • 描述:检查是否能够正确处理非指针的 Map、Slice 和基础类型。
    • 输入:map[string]string{}, []int{}, 0
    • 期望结果:返回错误 errors.New("orm: 只支持一级指针作为输入,例如 *User")
  5. 测试带有 column tag 的 Model:

    • 描述:检查是否能够正确处理带有 column tag 的 Model。
    • 输入:带有 column tag 的结构体。
    • 期望结果:返回包含正确表名、字段等信息的 Model
  6. 测试空 column tag:

    • 描述:检查是否能够处理用户设置了 column 但没有指定值的情况。
    • 输入:type EmptyColumn struct { FirstName string "orm:\"column=\"" }
    • 期望结果:返回包含正确表名、字段等信息的 Model,其中列名为默认生成的。
  7. 测试无效 tag:

    • 描述:检查是否能够处理无效的 tag。
    • 输入:type InvalidTag struct { FirstName string "orm:\"column\"" }
    • 期望结果:返回错误 errs.NewErrInvalidTagContent("column")
  8. 测试忽略的 tag:

    • 描述:检查是否能够忽略无效的 tag 部分。
    • 输入:type IgnoreTag struct { FirstName string "orm:\"abc=abc\"" }
    • 期望结果:返回包含正确表名、字段等信息的 Model
  9. 测试接口自定义模型信息:

    • 描述:检查是否能够正确处理通过接口定义的自定义模型信息。
    • 输入:&CustomTableName{}, &CustomTableNamePtr{}, &EmptyTableName{}
    • 期望结果:返回包含正确表名、字段等信息的 Model

Test_underscoreName 测试用例设计:

  1. 测试下划线转换:
    • 描述:检查是否能够正确将大写字母和数字转换为下划线形式。
    • 输入:多个字符串。
    • 期望结果:返回正确的下划线形式的字符串。

Valuer 单元测试

reflectValue 单元测试

TestReflectValue_Field 测试用例设计:

  1. 测试 NewReflectValue 函数:

    • 描述:测试 NewReflectValue 函数是否能够正确创建 ReflectValue 实例,并且正确处理字段的获取。
    • 输入:NewReflectValue(&test.SimpleStruct{}, meta)
    • 期望结果:返回正确的 ReflectValue 实例,并能够正确获取字段值。
  2. 测试零值情况:

    • 描述:测试在结构体为零值的情况下,ReflectValue 是否能够正确获取字段值。
    • 输入:entity := &test.SimpleStruct{}
    • 期望结果:返回正确的字段值。
  3. 测试正常情况:

    • 描述:测试在结构体为正常非零值的情况下,ReflectValue 是否能够正确获取字段值。
    • 输入:entity := test.NewSimpleStruct(1)
    • 期望结果:返回正确的字段值。
  4. 测试字段为无效值:

    • 描述:测试当请求获取的字段不存在时,ReflectValue 是否能够正确处理并返回错误。
    • 输入:"UpdateTime" 字段。
    • 期望结果:返回错误 errs.NewErrUnknownField("UpdateTime")

Test_reflectValue_SetColumn 测试用例设计:

  1. 测试正常情况:

    • 描述:测试在正常情况下,ReflectValue 是否能够正确设置字段值。
    • 输入:模拟数据库查询结果和结构体实例。
    • 期望结果:返回正确设置后的结构体实例。
  2. 测试字段不存在:

    • 描述:测试当数据库查询结果中包含结构体中不存在的字段时,ReflectValue 是否能够正确处理并返回错误。
    • 输入:map[string][]byte{"invalid_column": nil}
    • 期望结果:返回错误 errs.NewErrUnknownColumn("invalid_column")

unsafeValue 单元测试

Test_unsafeValue_SetColumn 测试用例设计:

  1. 测试正常情况:

    • 描述:测试在正常情况下,UnsafeValue 是否能够正确设置字段值。
    • 输入:模拟数据库查询结果和结构体实例。
    • 期望结果:返回正确设置后的结构体实例。
  2. 测试字段不存在:

    • 描述:测试当数据库查询结果中包含结构体中不存在的字段时,UnsafeValue 是否能够正确处理并返回错误。
    • 输入:map[string][]byte{"invalid_column": nil}
    • 期望结果:返回错误 errs.NewErrUnknownColumn("invalid_column")

Tx 单元测试

TestTx_Commit 测试用例设计:

  1. 正常提交事务:
    • 描述:测试事务正常提交是否能够成功执行。
    • 输入:db.BeginTx(context.Background(), &sql.TxOptions{}),然后调用 tx.Commit()
    • 期望结果:事务能够成功提交,不产生错误。

TestTx_Rollback 测试用例设计:

  1. 事务回滚:
    • 描述:测试事务回滚是否能够成功执行。
    • 输入:db.BeginTx(context.Background(), &sql.TxOptions{}),然后调用 tx.Rollback()
    • 期望结果:事务能够成功回滚,不产生错误。

Selector 单元测试

TestSelector_Build() 测试用例设计

  1. 无 From 调用:
    • 描述:测试在未调用 From 方法时,是否能够正确生成默认的 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model;"。
  2. 调用 FROM 方法:
    • 描述:测试在调用 From 方法时,是否能够正确生成指定表名的 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model_t;"。
  3. 调用 FROM 方法,传入空字符串:
    • 描述:测试在调用 From 方法时传入空字符串,是否能够正确处理并生成默认的 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model;"。
  4. 调用 FROM 方法,传入带数据库的表名:
    • 描述:测试在调用 From 方法时传入带数据库的表名,是否能够正确生成带有数据库的 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_db.test_model;"。
  5. 单一简单条件:
    • 描述:测试在添加单一的简单条件时,是否能够正确生成带条件的 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model_t WHERE id = ?;",同时包含正确的参数。
  6. 多个条件:
    • 描述:测试在添加多个条件时,是否能够正确生成带有多个条件的 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model WHERE (age > ?) AND (age < ?);",同时包含正确的参数。
  7. 使用 AND 连接条件:
    • 描述:测试在使用 And 连接条件时,是否能够正确生成带有 AND 连接的 SQL 查询语句。
    • 期望结果:生成的 SQL 语句与第 6 个测试用例相同。
  8. 使用 OR 连接条件:
    • 描述:测试在使用 Or 连接条件时,是否能够正确生成带有 OR 连接的 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model WHERE (age > ?) OR (age < ?);",同时包含正确的参数。
  9. 使用 NOT 连接条件:
    • 描述:测试在使用 Not 连接条件时,是否能够正确生成带有 NOT 连接的 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model WHERE NOT (age > ?);",同时包含正确的参数。
  10. 非法列:
    • 描述:测试在使用不存在的列名时,是否能够正确处理并返回预期的错误。
    • 期望结果:返回一个 errs.NewErrUnknownField 的错误。
  11. 使用 RawExpr:
    • 描述:测试在使用 RawExpr 时,是否能够正确生成带有原始表达式的 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model WHERE age < ?;",同时包含正确的参数。

TestSelector_Get() 测试用例设计:

  1. 查询返回错误:
    • 描述:测试当数据库查询返回错误时,Get 方法是否能够正确处理并返回相应的错误。
    • 期望结果:返回与查询错误相匹配的错误信息。
  2. 无行返回:
    • 描述:测试当查询没有返回任何行时,Get 方法是否能够正确处理并返回 ErrNoRows
    • 期望结果:返回 ErrNoRows
  3. 返回的列数过多:
    • 描述:测试当查询返回的列数超过预期时,Get 方法是否能够正确处理并返回 ErrTooManyReturnedColumns
    • 期望结果:返回 ErrTooManyReturnedColumns
  4. 获取数据成功:
    • 描述:测试当查询成功返回数据时,Get 方法是否能够正确处理并返回预期的数据。
    • 期望结果:返回与查询结果相匹配的 TestModel 对象。

在测试中,使用 sqlmock 模拟了数据库的行为,并为每个测试用例创建了相应的模拟数据。通过对每个测试用例的期望结果进行验证,确保了 Get 方法在不同情况下的正确性。在循环中,每个测试用例的查询模拟被期望并设置相应的返回值。

TestSelector_OffsetLimit 测试用例设计:

  1. Offset 方法测试:

    • 描述:测试 Offset 方法是否能够正确生成包含 Offset 的 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model OFFSET ?;",同时包含正确的参数。
  2. Limit 方法测试:

    • 描述:测试 Limit 方法是否能够正确生成包含 Limit 的 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model LIMIT ?;",同时包含正确的参数。
  3. Limit 和 Offset 方法组合测试:

    • 描述:测试当同时使用 LimitOffset 方法时,是否能够正确生成 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model LIMIT ? OFFSET ?;",同时包含正确的参数。

TestSelector_Having 测试用例设计:

  1. GroupBy 后没有条件:

    • 描述:测试在调用 GroupBy 后没有传递条件时,Having 方法是否能够正确生成 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model GROUP BY age;"。
  2. 单一条件测试:

    • 描述:测试在传递单一条件时,Having 方法是否能够正确生成 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model GROUP BY age HAVING first_name = ?;",同时包含正确的参数。
  3. 多个条件测试:

    • 描述:测试在传递多个条件时,Having 方法是否能够正确生成 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model GROUP BY age HAVING (first_name = ?) AND (last_name = ?);",同时包含正确的参数。
  4. 聚合函数测试:

    • 描述:测试在使用聚合函数时,Having 方法是否能够正确生成 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model GROUP BY age HAVING AVG(age) = ?;",同时包含正确的参数。

TestSelector_GroupBy() 测试用例设计:

  1. GroupBy 后没有列名:

    • 描述:测试在调用 GroupBy 后没有传递列名时,GroupBy 方法是否能够正确生成 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model;"。
  2. 单个列名测试:

    • 描述:测试在传递单个列名时,GroupBy 方法是否能够正确生成 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model GROUP BY age;"。
  3. 多个列名测试:

    • 描述:测试在传递多个列名时,GroupBy 方法是否能够正确生成 SQL 查询语句。
    • 期望结果:生成的 SQL 语句为 "SELECT * FROM test_model GROUP BY age,first_name;"。
  4. 不存在的列名测试:

    • 描述:测试在传递不存在的列名时,GroupBy 方法是否能够正确处理并返回相应的错误。
    • 期望结果:返回一个 errs.NewErrUnknownField 的错误。

BenchmarkQuerier_Get() 性能测试:

这段代码包含了一个基准测试函数 BenchmarkQuerier_Get,该函数对 Querier 接口的 Get 方法进行性能比较,使用了 unsafereflect 两种方式。

Benchmark 结果解释:

  • BenchmarkQuerier_Get/unsafe-8:

    • 每次迭代花费时间:418,712 纳秒(ns)
    • 每次迭代分配的内存:3,324 字节(B)
    • 每次迭代的内存分配次数:112 次
  • BenchmarkQuerier_Get/reflect-8:

    • 每次迭代花费时间:1,462,437 纳秒(ns)
    • 每次迭代分配的内存:3,503 字节(B)
    • 每次迭代的内存分配次数:120 次

性能比较:

  • unsafe vs. reflect:
    • 使用 unsafe 的性能远远快于直接使用 reflect
    • unsafe 方式的迭代时间仅为 reflect 方式的 1/3.5,内存分配次数也较少。

测试配置:

  • 测试环境:

    • 操作系统: Windows
    • 架构: AMD64
    • CPU: Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz
  • 测试命令:

    go test -bench=BenchmarkQuerier_Get -benchmem -benchtime=10000x
    

性能评估:

该基准测试明确表明,使用 unsafe 方式相对于 reflect 方式在性能上有显著的优势,因为它显著降低了每次迭代的时间和内存分配次数。因此,在追求性能的场景下,推荐使用 unsafe 方式。然而,使用 unsafe 需要谨慎,确保在使用过程中不会引起不安全的内存操作。

Deleter 单元测试

这段代码包含了一个针对 Deleter 接口的 Build 方法的测试函数 TestDeleter_Build,测试了删除操作的 SQL 查询语句构建。

TestDeleter_Build() 测试用例设计:

  1. 无条件删除:

    • 描述:测试在不添加任何条件的情况下,Deleter 是否能够正确生成删除表中所有数据的 SQL 查询语句。
    • 输入:NewDeleter[TestModel](db).From("test_model")
    • 期望结果:生成的 SQL 语句为 "DELETE FROM test_model;"
  2. 带条件删除:

    • 描述:测试在添加条件的情况下,Deleter 是否能够正确生成带有条件的删除 SQL 查询语句。
    • 输入:NewDeleter[TestModel](db).Where(C("Id").EQ(16))
    • 期望结果:生成的 SQL 语句为 "DELETE FROM test_model WHERE id = ?;",同时包含正确的参数。
  3. From 和 Where 组合:

    • 描述:测试在同时指定表名和条件的情况下,Deleter 是否能够正确生成删除 SQL 查询语句。
    • 输入:NewDeleter[TestModel](db).From("test_model").Where(C("Id").EQ(16))
    • 期望结果:生成的 SQL 语句为 "DELETE FROM test_model WHERE id = ?;",同时包含正确的参数。

Insertor 单元测试

TestInserter_Build() 测试用例设计:

  1. 零行插入:
    • 描述:测试插入操作时未提供任何值时是否能够返回正确的错误。
    • 输入:NewInserterTestModel.Values()
    • 期望结果:返回错误 errs.ErrInsertZeroRow。
  2. 单行插入:
    • 描述:测试插入一行数据时是否能够正确生成 SQL 查询语句。
    • 输入:NewInserterTestModel.Values(...)
    • 期望结果:生成正确的 SQL 语句,包含正确的参数。
  3. 多行插入:
    • 描述:测试插入多行数据时是否能够正确生成 SQL 查询语句。
    • 输入:NewInserterTestModel.Values(..., ...)
    • 期望结果:生成正确的 SQL 语句,包含正确的参数。
  4. 指定列插入:
    • 描述:测试插入时指定列是否能够正确生成 SQL 查询语句。
    • 输入:NewInserterTestModel.Values(...).Columns(...)
    • 期望结果:生成正确的 SQL 语句,包含正确的参数。
  5. 指定列插入 - 无效列:
    • 描述:测试插入时指定不存在的列是否能够返回正确的错误。
    • 输入:NewInserterTestModel.Values(...).Columns("FirstName", "Invalid")
    • 期望结果:返回错误 errs.NewErrUnknownField("Invalid")。
  6. upsert 操作:
    • 描述:测试 upsert 操作时是否能够正确生成 SQL 查询语句。
    • 输入:NewInserterTestModel.Values(...).OnDuplicateKey().Update(...)
    • 期望结果:生成正确的 SQL 语句,包含正确的参数。
  7. upsert 操作 - 无效列:
    • 描述:测试 upsert 操作时指定不存在的列是否能够返回正确的错误。
    • 输入:NewInserterTestModel.Values(...).OnDuplicateKey().Update(Assign("Invalid", "Da"))
    • 期望结果:返回错误 errs.NewErrUnknownField("Invalid")。
  8. upsert 操作 - 使用插入的值:
    • 描述:测试 upsert 操作时是否能够使用原本插入的值。
    • 输入:NewInserterTestModel.Values(...).OnDuplicateKey().Update(C("FirstName"), C("LastName"))
    • 期望结果:生成正确的 SQL 语句,包含正确的参数。

Upsert 单元测试

TestUpsert_SQLite3_Build() 测试用例设计:

  1. upsert 操作 - SQLite3:
    • 描述:测试在 SQLite3 中使用 upsert 操作时是否能够正确生成 SQL 查询语句。
    • 输入:NewInserterTestModel.Values(...).OnDuplicateKey().ConflictColumns("Id").Update(...)
    • 期望结果:生成正确的 SQL 语句,包含正确的参数。
  2. upsert 操作 - 无效列:
    • 描述:测试在 SQLite3 中 upsert 操作时指定不存在的列是否能够返回正确的错误。
    • 输入:NewInserterTestModel.Values(...).OnDuplicateKey().ConflictColumns("Id").Update(Assign("Invalid", "Da"))
    • 期望结果:返回错误 errs.NewErrUnknownField("Invalid")。
  3. upsert 操作 - 冲突列无效:
    • 描述:测试在 SQLite3 中 upsert 操作时指定冲突列不存在是否能够返回正确的错误。
    • 输入:NewInserterTestModel.Values(...).OnDuplicateKey().ConflictColumns("Invalid").Update(Assign("FirstName", "Da"))
    • 期望结果:返回错误 errs.NewErrUnknownField("Invalid")。
  4. upsert 操作 - 使用插入的值:
    • 描述:测试在 SQLite3 中 upsert 操作时是否能够使用原本插入的值。
    • 输入:NewInserterTestModel.Values(...).OnDuplicateKey().ConflictColumns("Id").Update(C("FirstName"), C("LastName"))
    • 期望结果:生成正确的 SQL 语句,包含正确的参数。

Updater 单元测试

TestUpdater_Build() 测试用例设计:

  1. 无更新列:

    • 描述:测试在更新操作时没有指定更新列是否能够返回正确的错误。
    • 输入:NewUpdater[TestModel](db)
    • 期望结果:返回错误 errs.ErrNoUpdatedColumns
  2. 单列更新:

    • 描述:测试更新操作时单列是否能够正确生成 SQL 查询语句。
    • 输入:NewUpdater[TestModel](db).Update(...).Set(C("Age"))
    • 期望结果:生成正确的 SQL 语句,包含正确的参数。
  3. 多列更新:

    • 描述:测试更新操作时多列是否能够正确生成 SQL 查询语句。
    • 输入:NewUpdater[TestModel](db).Update(...).Set(C("Age"), Assign("FirstName", "YangZhuolin"))
    • 期望结果:生成正确的 SQL 语句,包含正确的参数。
  4. 带条件更新:

    • 描述:测试更新操作时带有条件是否能够正确生成 SQL 查询语句。
    • 输入:NewUpdater[TestModel](db).Update(...).Set(...).Where(C("Id").EQ(1))
    • 期望结果:生成正确的 SQL 语句,包含正确的参数。
  5. 增量更新:

    • 描述:测试增量更新操作是否能够正确生成 SQL 查询语句。
    • 输入:NewUpdater[TestModel](db).Update(...).Set(Assign("Age", C("Age").Add(1)))
    • 期望结果:生成正确的 SQL 语句,包含正确的参数。
  6. 增量更新 - 使用原始 SQL:

    • 描述:测试使用原始 SQL 进行增量更新操作是否能够正确生成 SQL 查询语句。
    • 输入:NewUpdater[TestModel](db).Update(...).Set(Assign("Age", Raw("age+?", 1)))
    • 期望结果:生成正确的 SQL 语句,包含正确的参数。

集成测试

SelectSuite 测试用例设计:

  1. 测试 MySQL 数据库查询功能:

    • 描述:在 SelectSuite 中,对 MySQL 数据库进行查询功能的集成测试。
    • 输入:数据库驱动为 "mysql",DSN 为 "root:root@tcp(localhost:13306)/integration_test"。
    • 期望结果:测试查询功能是否能够正确执行,验证获取数据和处理不存在行的情况。
  2. SetupSuite 设置数据库插入数据:

    • 描述:在 SetupSuite 中,使用 orm.NewInserter 在数据库中插入一条数据。
    • 输入:插入一条 test.SimpleStruct 数据,ID 为 100。
    • 期望结果:验证插入操作是否成功执行。
  3. 测试 Get 方法:

    • 描述:在 TestGet 方法中,通过 orm.NewSelectorGet 方法测试从数据库中获取数据。
    • 输入:包括获取存在的数据和获取不存在的数据的测试用例。
    • 期望结果:验证获取数据和处理不存在行的情况是否正确。

InsertSuite 测试用例设计:

  1. 测试 MySQL 数据库插入功能:

    • 描述:在 InsertSuite 中,对 MySQL 数据库进行插入功能的集成测试。
    • 输入:数据库驱动为 "mysql",DSN 为 "root:root@tcp(localhost:13306)/integration_test"。
    • 期望结果:测试插入功能是否能够正确执行,验证单条和多条插入以及插入指定 ID 是否成功。
  2. 测试 TestInsert 方法:

    • 描述:在 TestInsert 方法中,通过 orm.NewInserterExec 方法测试插入数据到数据库。
    • 输入:包括插入一条数据、插入多条数据和插入指定 ID 的数据的测试用例。
    • 期望结果:验证插入操作是否成功执行,且受影响的行数符合预期。

测试结果

测试执行:

$ go test -v ./...

测试结果:

$ go test -v ./...
?       github.com/Ai-feier/lorm/appexample/rbacapp     [no test files]
?       github.com/Ai-feier/lorm/internal/errs  [no test files]
=== RUN   TestDeleter_Build
=== RUN   TestDeleter_Build/no_where
=== RUN   TestDeleter_Build/where
=== RUN   TestDeleter_Build/from
--- PASS: TestDeleter_Build (0.00s)
    --- PASS: TestDeleter_Build/no_where (0.00s)
    --- PASS: TestDeleter_Build/where (0.00s)
    --- PASS: TestDeleter_Build/from (0.00s)
=== RUN   TestInserter_Build
=== RUN   TestInserter_Build/no_value
=== RUN   TestInserter_Build/single_values
=== RUN   TestInserter_Build/multiple_values
=== RUN   TestInserter_Build/specify_columns
=== RUN   TestInserter_Build/invalid_columns
=== RUN   TestInserter_Build/upsert
=== RUN   TestInserter_Build/upsert_invalid_column
=== RUN   TestInserter_Build/upsert_use_insert_value
--- PASS: TestInserter_Build (0.00s)
    --- PASS: TestInserter_Build/no_value (0.00s)
    --- PASS: TestInserter_Build/single_values (0.00s)
    --- PASS: TestInserter_Build/multiple_values (0.00s)
    --- PASS: TestInserter_Build/specify_columns (0.00s)
    --- PASS: TestInserter_Build/invalid_columns (0.00s)
    --- PASS: TestInserter_Build/upsert (0.00s)
    --- PASS: TestInserter_Build/upsert_invalid_column (0.00s)
    --- PASS: TestInserter_Build/upsert_use_insert_value (0.00s)
=== RUN   TestUpsert_SQLite3_Build
=== RUN   TestUpsert_SQLite3_Build/upsert
=== RUN   TestUpsert_SQLite3_Build/upsert_invalid_column
=== RUN   TestUpsert_SQLite3_Build/conflict_invalid_column
=== RUN   TestUpsert_SQLite3_Build/upsert_use_insert_value
--- PASS: TestUpsert_SQLite3_Build (0.00s)
    --- PASS: TestUpsert_SQLite3_Build/upsert (0.00s)
    --- PASS: TestUpsert_SQLite3_Build/upsert_invalid_column (0.00s)
    --- PASS: TestUpsert_SQLite3_Build/conflict_invalid_column (0.00s)
    --- PASS: TestUpsert_SQLite3_Build/upsert_use_insert_value (0.00s)
=== RUN   TestSQLite
=== RUN   TestSQLite/TestCRUD
--- PASS: TestSQLite (0.00s)
    --- PASS: TestSQLite/TestCRUD (0.00s)
=== RUN   TestSelector_OffsetLimit
=== RUN   TestSelector_OffsetLimit/offset_only
=== RUN   TestSelector_OffsetLimit/limit_only
=== RUN   TestSelector_OffsetLimit/limit_offset
--- PASS: TestSelector_OffsetLimit (0.00s)
    --- PASS: TestSelector_OffsetLimit/offset_only (0.00s)
    --- PASS: TestSelector_OffsetLimit/limit_only (0.00s)
    --- PASS: TestSelector_OffsetLimit/limit_offset (0.00s)
=== RUN   TestSelector_Having
=== RUN   TestSelector_Having/none
=== RUN   TestSelector_Having/single
=== RUN   TestSelector_Having/multiple
=== RUN   TestSelector_Having/avg
--- PASS: TestSelector_Having (0.00s)
    --- PASS: TestSelector_Having/none (0.00s)
    --- PASS: TestSelector_Having/single (0.00s)
    --- PASS: TestSelector_Having/multiple (0.00s)
    --- PASS: TestSelector_Having/avg (0.00s)
=== RUN   TestSelector_GroupBy
=== RUN   TestSelector_GroupBy/none
=== RUN   TestSelector_GroupBy/single
=== RUN   TestSelector_GroupBy/multiple
=== RUN   TestSelector_GroupBy/invalid_column
--- PASS: TestSelector_GroupBy (0.00s)
    --- PASS: TestSelector_GroupBy/none (0.00s)
    --- PASS: TestSelector_GroupBy/single (0.00s)
    --- PASS: TestSelector_GroupBy/multiple (0.00s)
    --- PASS: TestSelector_GroupBy/invalid_column (0.00s)
=== RUN   TestSelector_Build
=== RUN   TestSelector_Build/no_from
=== RUN   TestSelector_Build/with_from
=== RUN   TestSelector_Build/empty_from
=== RUN   TestSelector_Build/with_db
=== RUN   TestSelector_Build/single_and_simple_predicate
=== RUN   TestSelector_Build/multiple_predicates
=== RUN   TestSelector_Build/and
=== RUN   TestSelector_Build/or
=== RUN   TestSelector_Build/not
=== RUN   TestSelector_Build/invalid_columns
=== RUN   TestSelector_Build/raw_expression
--- PASS: TestSelector_Build (0.00s)
    --- PASS: TestSelector_Build/no_from (0.00s)
    --- PASS: TestSelector_Build/with_from (0.00s)
    --- PASS: TestSelector_Build/empty_from (0.00s)
    --- PASS: TestSelector_Build/with_db (0.00s)
    --- PASS: TestSelector_Build/single_and_simple_predicate (0.00s)
    --- PASS: TestSelector_Build/multiple_predicates (0.00s)
    --- PASS: TestSelector_Build/and (0.00s)
    --- PASS: TestSelector_Build/or (0.00s)
    --- PASS: TestSelector_Build/not (0.00s)
    --- PASS: TestSelector_Build/invalid_columns (0.00s)
    --- PASS: TestSelector_Build/raw_expression (0.00s)
=== RUN   TestSelector_Get
=== RUN   TestSelector_Get/query_error
=== RUN   TestSelector_Get/no_row
=== RUN   TestSelector_Get/too_many_columns
=== RUN   TestSelector_Get/get_data
--- PASS: TestSelector_Get (0.00s)
    --- PASS: TestSelector_Get/query_error (0.00s)
    --- PASS: TestSelector_Get/no_row (0.00s)
    --- PASS: TestSelector_Get/too_many_columns (0.00s)
    --- PASS: TestSelector_Get/get_data (0.00s)
=== RUN   TestTx_Commit
--- PASS: TestTx_Commit (0.00s)
=== RUN   TestTx_Rollback
--- PASS: TestTx_Rollback (0.00s)
=== RUN   TestUpdater_Build
=== RUN   TestUpdater_Build/no_column
=== RUN   TestUpdater_Build/single_column
=== RUN   TestUpdater_Build/assignment
=== RUN   TestUpdater_Build/where
=== RUN   TestUpdater_Build/incremental
=== RUN   TestUpdater_Build/incremental-raw
--- PASS: TestUpdater_Build (0.00s)
    --- PASS: TestUpdater_Build/no_column (0.00s)
    --- PASS: TestUpdater_Build/single_column (0.00s)
    --- PASS: TestUpdater_Build/assignment (0.00s)
    --- PASS: TestUpdater_Build/where (0.00s)
    --- PASS: TestUpdater_Build/incremental (0.00s)
    --- PASS: TestUpdater_Build/incremental-raw (0.00s)
PASS
ok      github.com/Ai-feier/lorm        (cached)
testing: warning: no tests to run
PASS
ok      github.com/Ai-feier/lorm/internal/integration   (cached) [no tests to run]
=== RUN   TestJsonColumn_Scan
=== RUN   TestJsonColumn_Scan/empty_string
=== RUN   TestJsonColumn_Scan/no_fields
=== RUN   TestJsonColumn_Scan/string
=== RUN   TestJsonColumn_Scan/nil_bytes
=== RUN   TestJsonColumn_Scan/empty_bytes
=== RUN   TestJsonColumn_Scan/bytes
=== RUN   TestJsonColumn_Scan/nil
=== RUN   TestJsonColumn_Scan/empty_bytes_ptr
=== RUN   TestJsonColumn_Scan/bytes_ptr
--- PASS: TestJsonColumn_Scan (0.00s)
    --- PASS: TestJsonColumn_Scan/empty_string (0.00s)
    --- PASS: TestJsonColumn_Scan/no_fields (0.00s)
    --- PASS: TestJsonColumn_Scan/string (0.00s)
    --- PASS: TestJsonColumn_Scan/nil_bytes (0.00s)
    --- PASS: TestJsonColumn_Scan/empty_bytes (0.00s)
    --- PASS: TestJsonColumn_Scan/bytes (0.00s)
    --- PASS: TestJsonColumn_Scan/nil (0.00s)
    --- PASS: TestJsonColumn_Scan/empty_bytes_ptr (0.00s)
    --- PASS: TestJsonColumn_Scan/bytes_ptr (0.00s)
PASS
ok      github.com/Ai-feier/lorm/internal/test  (cached)
?       github.com/Ai-feier/lorm/middleproviderexample/nodelete [no test files]
=== RUN   TestReflectValue_Field
=== RUN   TestReflectValue_Field/zero_value
=== RUN   TestReflectValue_Field/zero_value/bool
=== RUN   TestReflectValue_Field/zero_value/bool_pointer
=== RUN   TestReflectValue_Field/zero_value/int
=== RUN   TestReflectValue_Field/zero_value/int_pointer
=== RUN   TestReflectValue_Field/zero_value/int8
=== RUN   TestReflectValue_Field/zero_value/int8_pointer
=== RUN   TestReflectValue_Field/zero_value/int16
=== RUN   TestReflectValue_Field/zero_value/int16_pointer
=== RUN   TestReflectValue_Field/zero_value/int32
=== RUN   TestReflectValue_Field/zero_value/int32_pointer
=== RUN   TestReflectValue_Field/zero_value/int64
=== RUN   TestReflectValue_Field/zero_value/int64_pointer
=== RUN   TestReflectValue_Field/zero_value/uint
=== RUN   TestReflectValue_Field/zero_value/uint_pointer
=== RUN   TestReflectValue_Field/zero_value/uint8
=== RUN   TestReflectValue_Field/zero_value/uint8_pointer
=== RUN   TestReflectValue_Field/zero_value/uint16
=== RUN   TestReflectValue_Field/zero_value/uint16_pointer
=== RUN   TestReflectValue_Field/zero_value/uint32
=== RUN   TestReflectValue_Field/zero_value/uint32_pointer
=== RUN   TestReflectValue_Field/zero_value/uint64
=== RUN   TestReflectValue_Field/zero_value/uint64_pointer
=== RUN   TestReflectValue_Field/zero_value/float32
=== RUN   TestReflectValue_Field/zero_value/float32_pointer
=== RUN   TestReflectValue_Field/zero_value/float64
=== RUN   TestReflectValue_Field/zero_value/float64_pointer
=== RUN   TestReflectValue_Field/zero_value/byte_array
=== RUN   TestReflectValue_Field/zero_value/string
=== RUN   TestReflectValue_Field/zero_value/NullStringPtr
=== RUN   TestReflectValue_Field/zero_value/NullInt16Ptr
=== RUN   TestReflectValue_Field/zero_value/NullInt32Ptr
=== RUN   TestReflectValue_Field/zero_value/NullInt64Ptr
=== RUN   TestReflectValue_Field/zero_value/NullFloat64Ptr
=== RUN   TestReflectValue_Field/zero_value/JsonColumn
=== RUN   TestReflectValue_Field/normal_value
=== RUN   TestReflectValue_Field/normal_value/bool
=== RUN   TestReflectValue_Field/normal_value/bool_pointer
=== RUN   TestReflectValue_Field/normal_value/int
=== RUN   TestReflectValue_Field/normal_value/int_pointer
=== RUN   TestReflectValue_Field/normal_value/int8
=== RUN   TestReflectValue_Field/normal_value/int8_pointer
=== RUN   TestReflectValue_Field/normal_value/int16
=== RUN   TestReflectValue_Field/normal_value/int16_pointer
=== RUN   TestReflectValue_Field/normal_value/int32
=== RUN   TestReflectValue_Field/normal_value/int32_pointer
=== RUN   TestReflectValue_Field/normal_value/int64
=== RUN   TestReflectValue_Field/normal_value/int64_pointer
=== RUN   TestReflectValue_Field/normal_value/uint
=== RUN   TestReflectValue_Field/normal_value/uint_pointer
=== RUN   TestReflectValue_Field/normal_value/uint8
=== RUN   TestReflectValue_Field/normal_value/uint8_pointer
=== RUN   TestReflectValue_Field/normal_value/uint16
=== RUN   TestReflectValue_Field/normal_value/uint16_pointer
=== RUN   TestReflectValue_Field/normal_value/uint32
=== RUN   TestReflectValue_Field/normal_value/uint32_pointer
=== RUN   TestReflectValue_Field/normal_value/uint64
=== RUN   TestReflectValue_Field/normal_value/uint64_pointer
=== RUN   TestReflectValue_Field/normal_value/float32
=== RUN   TestReflectValue_Field/normal_value/float32_pointer
=== RUN   TestReflectValue_Field/normal_value/float64
=== RUN   TestReflectValue_Field/normal_value/float64_pointer
=== RUN   TestReflectValue_Field/normal_value/byte_array
=== RUN   TestReflectValue_Field/normal_value/string
=== RUN   TestReflectValue_Field/normal_value/NullStringPtr
=== RUN   TestReflectValue_Field/normal_value/NullInt16Ptr
=== RUN   TestReflectValue_Field/normal_value/NullInt32Ptr
=== RUN   TestReflectValue_Field/normal_value/NullInt64Ptr
=== RUN   TestReflectValue_Field/normal_value/NullFloat64Ptr
=== RUN   TestReflectValue_Field/normal_value/JsonColumn
=== RUN   TestReflectValue_Field/invalid_cases
=== RUN   TestReflectValue_Field/invalid_cases/invalid_field
--- PASS: TestReflectValue_Field (0.00s)
    --- PASS: TestReflectValue_Field/zero_value (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/bool (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/bool_pointer (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/int (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/int_pointer (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/int8 (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/int8_pointer (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/int16 (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/int16_pointer (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/int32 (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/int32_pointer (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/int64 (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/int64_pointer (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/uint (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/uint_pointer (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/uint8 (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/uint8_pointer (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/uint16 (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/uint16_pointer (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/uint32 (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/uint32_pointer (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/uint64 (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/uint64_pointer (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/float32 (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/float32_pointer (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/float64 (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/float64_pointer (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/byte_array (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/string (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/NullStringPtr (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/NullInt16Ptr (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/NullInt32Ptr (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/NullInt64Ptr (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/NullFloat64Ptr (0.00s)
        --- PASS: TestReflectValue_Field/zero_value/JsonColumn (0.00s)
    --- PASS: TestReflectValue_Field/normal_value (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/bool (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/bool_pointer (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/int (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/int_pointer (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/int8 (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/int8_pointer (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/int16 (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/int16_pointer (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/int32 (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/int32_pointer (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/int64 (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/int64_pointer (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/uint (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/uint_pointer (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/uint8 (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/uint8_pointer (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/uint16 (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/uint16_pointer (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/uint32 (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/uint32_pointer (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/uint64 (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/uint64_pointer (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/float32 (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/float32_pointer (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/float64 (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/float64_pointer (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/byte_array (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/string (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/NullStringPtr (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/NullInt16Ptr (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/NullInt32Ptr (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/NullInt64Ptr (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/NullFloat64Ptr (0.00s)
        --- PASS: TestReflectValue_Field/normal_value/JsonColumn (0.00s)
    --- PASS: TestReflectValue_Field/invalid_cases (0.00s)
        --- PASS: TestReflectValue_Field/invalid_cases/invalid_field (0.00s)
=== RUN   Test_reflectValue_SetColumn
=== RUN   Test_reflectValue_SetColumn/normal_value
=== RUN   Test_reflectValue_SetColumn/invalid_field
--- PASS: Test_reflectValue_SetColumn (0.00s)
    --- PASS: Test_reflectValue_SetColumn/normal_value (0.00s)
    --- PASS: Test_reflectValue_SetColumn/invalid_field (0.00s)
=== RUN   Test_unsafeValue_SetColumn
=== RUN   Test_unsafeValue_SetColumn/normal_value
=== RUN   Test_unsafeValue_SetColumn/invalid_field
--- PASS: Test_unsafeValue_SetColumn (0.00s)
    --- PASS: Test_unsafeValue_SetColumn/normal_value (0.00s)
    --- PASS: Test_unsafeValue_SetColumn/invalid_field (0.00s)
PASS
ok      github.com/Ai-feier/lorm/internal/valuer        (cached)
?       github.com/Ai-feier/lorm/middleproviderexample/opentelemetry    [no test files]
?       github.com/Ai-feier/lorm/middleproviderexample/prometheus       [no test files]
?       github.com/Ai-feier/lorm/middleproviderexample/safedml  [no test files]
?       github.com/Ai-feier/lorm/middleproviderexample/slowquery        [no test files]
=== RUN   TestNewMiddlewareBuilder
--- PASS: TestNewMiddlewareBuilder (0.00s)
PASS
ok      github.com/Ai-feier/lorm/middleproviderexample/sqllog   (cached)
=== RUN   TestModelWithTableName
=== RUN   TestModelWithTableName/empty_string
=== RUN   TestModelWithTableName/table_name
--- PASS: TestModelWithTableName (0.00s)
    --- PASS: TestModelWithTableName/empty_string (0.00s)
    --- PASS: TestModelWithTableName/table_name (0.00s)
=== RUN   TestWithColumnName
=== RUN   TestWithColumnName/new_name
=== RUN   TestWithColumnName/empty_new_name
=== RUN   TestWithColumnName/invalid_Field_name
--- PASS: TestWithColumnName (0.00s)
    --- PASS: TestWithColumnName/new_name (0.00s)
    --- PASS: TestWithColumnName/empty_new_name (0.00s)
    --- PASS: TestWithColumnName/invalid_Field_name (0.00s)
=== RUN   TestRegistry_get
=== RUN   TestRegistry_get/test_Model
=== RUN   TestRegistry_get/pointer
=== RUN   TestRegistry_get/multiple_pointer
=== RUN   TestRegistry_get/map
=== RUN   TestRegistry_get/slice
=== RUN   TestRegistry_get/basic_type
=== RUN   TestRegistry_get/column_tag
=== RUN   TestRegistry_get/empty_column
=== RUN   TestRegistry_get/invalid_tag
=== RUN   Test_underscoreName/upper_cases
=== RUN   Test_underscoreName/use_number
--- PASS: Test_underscoreName (0.00s)
    --- PASS: Test_underscoreName/upper_cases (0.00s)
    --- PASS: Test_underscoreName/use_number (0.00s)
PASS
ok      github.com/Ai-feier/lorm/model  (cached)

测试结果分析:

测试结果显示,所有测试用例都通过了。以下是一些关键部分的总结:

  1. lorm/internal/integration 包:
    • 执行了多个集成测试,包括构建删除、插入、更新、查询等操作的测试。
    • 执行了 SQLite3 相关的集成测试。
    • 执行了与数据库交互的各个模块的测试,例如 TestSQLiteTestSelectorTestTx_Commit 等。
  2. lorm/internal/test 包:
    • 包含了一系列用于测试 JSON 列扫描的测试用例,所有测试通过。
  3. lorm/internal/valuer 包:
    • 执行了多个关于值处理的测试,包括通过反射设置列值、通过不安全的方式设置列值等。
    • 所有测试用例通过。
  4. lorm/middleproviderexample/sqllog 包:
    • 执行了测试,包括 TestNewMiddlewareBuilder,所有测试通过。
  5. lorm/model 包:
    • 执行了多个与模型、字段、注册表等相关的测试,所有测试通过。
  6. lorm 包:
    • 执行了一系列基础功能的测试,包括构建删除、插入、更新、查询等操作的测试。
    • 执行了与数据库交互的多个模块的测试,例如 TestSQLiteTestSelectorTestTx_Commit 等。
    • 执行了一些中间件提供者的测试,如 TestModelWithTableNameTestWithColumnName 等。
    • 所有测试用例通过。

总体而言,测试覆盖了核心功能和一些边缘情况,通过率为100%,代码质量较高。

项目总结:

项目概览:

本项目是一个使用Go语言开发的ORM框架,旨在提供高效、灵活的对象关系映射解决方案。在项目的开发过程中,我们注重面向接口的设计,并充分利用Go语言的反射和unsafe特性,以支持复杂的数据模型映射。

**技术亮点:

  1. 面向接口编程:项目中广泛应用了面向接口编程的思想,通过定义清晰的接口,降低了各个组件之间的耦合度。依赖注入容器的使用进一步简化了组件的管理和替换。

  2. Go反射与unsafe编程:充分利用Go语言的反射特性,使得对象关系映射更加灵活。同时,对于涉及到unsafe包的操作,我们在代码中注入了足够的安全性检查,确保代码的稳定性。

  3. 并发编程:在支持高并发的Go语言中,项目中对并发编程的需求进行了深入理解。ORM框架的设计考虑到了协程与通道的使用,保证了在并发环境下的安全性和高效性。

  4. SQL编程:ORM框架在SQL生成与执行方面进行了精心设计,以提供高性能的数据库访问。事务支持的完善性确保了在复杂业务场景下的数据一致性。

挑战与反思:

  1. 技术选型:在项目的初期,我们进行了全面的技术选型,涉及到测试框架、依赖注入容器、反射库等多个方面。不同技术的组合需要保证协同工作,因此我们在选择时充分考虑了它们的兼容性。

  2. 学习曲线:由于项目使用了一些高级的Go语言特性,包括反射和unsafe包的使用,对于团队成员的技术水平提出了一定的挑战。我们通过培训和知识分享确保了团队对这些特性的深入理解。

  3. 文档与社区:在项目中,我们注重编写详细的文档,以便团队成员更好地理解框架的使用和设计思想。此外,我们也积极参与Go社区,获取反馈并分享经验,确保项目的健康发展。

未来展望:

在项目的未来,我们计划进一步优化框架的性能和稳定性。同时,考虑到行业的变化和新的技术趋势,我们将不断更新和升级框架,以保持其在ORM领域的竞争力。通过持续的学习和技术创新,我们将确保项目保持活力并满足用户的需求。