草庐IT

sql - TDD 构造器 Golang

coder 2023-06-30 原文

尽管有几篇关于此的文章,但我还没有找到一篇有实质内容的文章。所以希望一些人能就此分享意见。

阻碍我拥有真正的 TDD 工作流程的一件事是,我无法找到一种干净的方法来测试必须连接到网络服务(如数据库)的东西。

例如:

type DB struct {
    conn *sql.DB
}

func NewDB(URL string) (*DB, err) {
    conn, err := sql.Open("postgres", URL)
    if err != nil {
        return nil, err
    }
}

我知道我可以将 sql 连接传递给 NewDB,或者直接传递给结构并将其分配给一个接口(interface),该接口(interface)具有我需要的所有方法,并且可以轻松测试。但是在某个地方,我将不得不连接。我能找到的测试这个的唯一方法是......

var sqlOpen = sql.Open
func CreateDB() *DB {
    conn, err := sqlOpen("postgres", "url...")
    if err != nil {
         log.Fatal(err)
    }

    dataBase = DB{
        conn: conn
    }
}

然后在测试中,您将 sqlOpen 函数替换为返回具有相同签名的函数的函数,该函数会为一个测试用例提供错误,而不会为另一个测试用例提供错误。但这感觉像是一种 hack,尤其是当您对同一个文件中的多个函数执行此操作时。有没有更好的办法?我正在使用的代码库在包和网络连接方面有很多功能。因为我正在努力以干净的方式测试事物,所以它让我远离 TDD。

最佳答案

典型的业务应用程序在查询中有很多逻辑。如果未测试,我们会显着降低测试覆盖率并为回归错误留出空间。因此,模拟数据库存储库不是最佳选择。相反,我们可以模拟数据库本身并测试我们如何在 SQL 级别上使用它。

以下是使用 DATA-DOG/go-sqlmock 的示例代码, 但可能还有其他模拟 sql 数据库的库。

首先,我们需要在我们的代码中注入(inject)sql连接。 GO sql connection 是一个误导性的名称,它实际上是连接池,而不仅仅是单个 DB 连接。这就是为什么即使您不编写测试,在您的组合根中创建单个 *sql.DB 并在您的代码中重用也是有意义的。

下面的示例展示了如何模拟网络服务。

一开始,我们需要创建一个带有注入(inject)连接的新处理程序:

// New creates new handler
func New(db *sql.DB) http.Handler {
    return &handler{
        db:     db,
    }
}

处理程序代码:

type handler struct {
    db     *sql.DB
}

func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  // some code that loads person name from database using id
}

单元测试模拟数据库的代码。它使用 stretchr/testify对于断言:

func TestHandler(t *testing.T) {
    db, sqlMock, _ := sqlmock.New()
    rows := sqlmock.NewRows([]string{"name"}).AddRow("John")
    // regex is used to match query
    // assert that we execute SQL statement with parameter and return data
    sqlMock.ExpectQuery(`select name from person where id \= \?`).WithArgs(42).WillReturnRows(rows)
    defer db.Close()

    sut := mypackage.New(db)

    r, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
    require.NoError(t, err, fmt.Sprintf("Failed to create request: %v", err))
    w := httptest.NewRecorder()

    sut.ServeHTTP(w, r)
    // make sure that all DB expectations were met 
    err = sqlMock.ExpectationsWereMet()
    assert.NoError(t, err)
    // other assertions that check DB data should be here 
    assert.Equal(t, http.StatusOK, w.Code)
}

我们的测试断言针对数据库的简单 SQL 语句。但是使用 go-sqlmock 可以测试所有 CRUD 操作和数据库事务。

上面的测试还有一个弱点。我们测试了我们的 SQL 语句是从代码执行的,但我们没有测试它是否适用于我们的真实数据库。这个问题不能用单元测试来解决。唯一的解决方案是针对真实数据库进行集成测试。

不过,我们现在处于更好的位置。业务逻辑已经在单元测试中进行了测试。我们不需要创建大量集成测试来涵盖不同的场景和参数,相反,我们只需要对每个查询进行一次测试来验证 SQL 语法并匹配我们的数据库模式。

测试愉快!

关于sql - TDD 构造器 Golang,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53329767/

有关sql - TDD 构造器 Golang的更多相关文章

  1. ruby - 调用其他方法的 TDD 方法的正确方法 - 2

    我需要一些关于TDD概念的帮助。假设我有以下代码defexecute(command)casecommandwhen"c"create_new_characterwhen"i"display_inventoryendenddefcreate_new_character#dostufftocreatenewcharacterenddefdisplay_inventory#dostufftodisplayinventoryend现在我不确定要为什么编写单元测试。如果我为execute方法编写单元测试,那不是几乎涵盖了我对create_new_character和display_invent

  2. Hive SQL 五大经典面试题 - 2

    目录第1题连续问题分析:解法:第2题分组问题分析:解法:第3题间隔连续问题分析:解法:第4题打折日期交叉问题分析:解法:第5题同时在线问题分析:解法:第1题连续问题如下数据为蚂蚁森林中用户领取的减少碳排放量iddtlowcarbon10012021-12-1212310022021-12-124510012021-12-134310012021-12-134510012021-12-132310022021-12-144510012021-12-1423010022021-12-154510012021-12-1523.......找出连续3天及以上减少碳排放量在100以上的用户分析:遇到这类

  3. sql - 查询忽略时间戳日期的时间范围 - 2

    我正在尝试查询我的Rails数据库(Postgres)中的购买表,我想查询时间范围。例如,我想知道在所有日期的下午2点到3点之间进行了多少次购买。此表中有一个created_at列,但我不知道如何在不搜索特定日期的情况下完成此操作。我试过:Purchases.where("created_atBETWEEN?and?",Time.now-1.hour,Time.now)但这最终只会搜索今天与那些时间的日期。 最佳答案 您需要使用PostgreSQL'sdate_part/extractfunction从created_at中提取小时

  4. sql - 在 Rails Console for PostgreSQL 的表中显示数据 - 2

    我找到了这样的东西:Rails:Howtolistdatabasetables/objectsusingtheRailsconsole?这一行没问题:ActiveRecord::Base.connection.tables并返回所有表但是ActiveRecord::Base.connection.table_structure("users")产生错误:ActiveRecord::Base.connection.table_structure("projects")我认为table_structure不是Postgres方法。如何列出Postgres数据库的Rails控制台中表中的所有

  5. ruby - 防止SQL注入(inject)/好的Ruby方法 - 2

    Ruby中防止SQL注入(inject)的好方法是什么? 最佳答案 直接使用ruby?使用准备好的语句:require'mysql'db=Mysql.new('localhost','user','password','database')statement=db.prepare"SELECT*FROMtableWHEREfield=?"statement.execute'value'statement.fetchstatement.close 关于ruby-防止SQL注入(inject

  6. ruby-on-rails - 如何在 Rails 中的不同数据库上执行直接 SQL 代码 - 2

    我正在编写一个Rails应用程序,它将监视某些特定数据库的数据质量。为了做到这一点,我需要能够对这些数据库执行直接SQL查询——这当然与用于驱动Rails应用程序模型的数据库不同。简而言之,这意味着我无法使用通过ActiveRecord基础连接的技巧。我需要连接的数据库在设计时是未知的(即:我不能将它们的详细信息放在database.yaml中)。相反,我有一个模型“database_details”,用户将使用它来输入应用程序将在运行时执行查询的数据库的详细信息。因此与这些数据库的连接实际上是动态的,细节仅在运行时解析。 最佳答案

  7. sql - Rails:使用 Postgres 创建对象时重复 ActiveRecord::RecordNotUnique? - 2

    我正在使用Rails4应用程序,它需要创建大量对象以响应来自另一个系统的事件。当我调用create!时,主键列上出现非常频繁的ActiveRecord::RecordNotUnique错误(由PG::UniqueViolation引起)我的模型之一。我在SO上找到了其他答案,建议挽救异常并调用retry:beginTableName.create!(data:'here')rescueActiveRecord::RecordNotUnique=>eife.message.include?'_pkey'#Onlyretryprimarykeyviolationslog.warn"Retr

  8. ruby-on-rails - Ruby 和 SQL 中的重复业务逻辑 - 2

    我有一个PORO(普通旧Ruby对象)来处理一些业务逻辑。它接收一个ActiveRecord对象并对其进行分类。为了简单起见,以下面为例:classClassificatorSTATES={1=>"Positive",2=>"Neutral",3=>"Negative"}definitializer(item)@item=itemenddefnameSTATES.fetch(state_id)endprivatedefstate_idreturn1if@item.value>0return2if@item.value==0return3if@item.value但是,我还想根据这些st

  9. sql - ActiveRecord 的意外行为包括 - 2

    我正在使用ARincludes在对象User和Building之间执行LEFTOUTERJOIN的方法,其中User可能有也可能没有Building关联:users=User.includes(:building).references(:buildings)因为我正在使用references,任何关联的Building对象都将被预先加载。我的期望是我随后能够遍历用户列表,并检查用户是否有与其关联的建筑物而不会触发额外的查询,但实际上每当我尝试访问建筑物属性时我都会看到对于没有建筑物的用户,AR会进行另一个SQL调用以尝试检索该建筑物(尽管在后续尝试中它只会返回nil)。这些查询显然是

  10. sql - 如何查询具有 3 个标签的事件? - 2

    我有以下模型:activity.rbtag.rbtagging.rb标签是事件和标签的连接模型。我想搜索具有2个或更多标签的事件。我如何在Rails中执行此操作?例如:我有tag1=Christmas,tag2=Florida,tag3=John如果存在,我想找到tag1、tag2和tag3存在的Activity。[编辑]我最终做了什么:tags=[tag1,tag2,tag3]activities=[]tags.eachdo|tag|activities如果任何组值的大小等于tags.size,则该事件包含所有标签。 最佳答案 如

随机推荐