【问题标题】:Deleting test database in Vapor 3在 Vapor 3 中删除测试数据库
【发布时间】:2019-04-22 05:52:58
【问题描述】:

我想为 Vapor 3 服务器编写一些集成测试,并且每次运行测试时都需要有干净的 Postgre 数据库。我怎样才能做到这一点?如果数据库尚不存在,迁移似乎不是正确的方法,因为它们已经运行过一次。

【问题讨论】:

  • 您是否尝试过使用原始 SQL 查询?此外,drop table 是 SQL 包的一部分
  • @nathan 以及在 vapor3 项目中运行此查询的正确位置是什么?

标签: swift vapor


【解决方案1】:

看看https://github.com/raywenderlich/vapor-til/tree/master/Tests

这需要在运行测试之前运行数据库,但它会在每次测试运行开始时还原所有迁移,从而每次都为您提供干净的数据库。 (具体here

在根目录中还有一个docker-compose.yml,用于在 Linux 上构建一个完全隔离的测试环境

【讨论】:

  • 我已经检查了来源并且有一些担忧 - 在 each 测试方法之前运行不同的应用程序实例(连续 2 次)不是非常浪费吗?他们为什么不使用“覆盖类 func setUp()”呢?...
  • 运行命令(还原和准备)需要启动应用程序。当您向应用程序提供命令时,它将在完成后退出,因此如果您按照上述方式执行此操作,则集成测试没有太多选择
  • 我也在使用 RW repo 中的方法,并且发现由于所有还原重新迁移正在进行,测试非常慢。进行 8 次测试,运行时间为 8 秒。我在 Python/Django 中编写的具有相同测试的相同后端只需要 0.3 秒..
【解决方案2】:

我找到了一个资源密集度较低的解决方案,然后每次都还原所有迁移。

RSpec 有一个配置 (use_transactional_fixtures),它允许将每个测试包装在 SQL 事务中。当测试结束时,它将回滚事务并因此恢复测试期间发生的所有更改。相关documentation is here

我们可以在 Vapor 中实现类似的解决方案。我的示例测试如下所示。

final class VaporTests: XCTestCase {

  var app: Application!

  override func setUp() {
    super.setUp()

    app = try! Application.buildForTesting()
    let conn = try! app.requestPooledConnection(to: .psql).wait()
    try! conn.simpleQuery("BEGIN TRANSACTION").wait()
    try! app.releasePooledConnection(conn, to: .psql)
  }

  override func tearDown() {
    let conn = try! app.requestPooledConnection(to: .psql).wait()
    try! conn.simpleQuery("ROLLBACK").wait()
    try! app.releasePooledConnection(conn, to: .psql)

    super.tearDown()
  }

  func testExample() throws {
    let request = HTTPRequest(method: .GET, url: "my/endpoint/example")
    let wrapper = Request(http: request, using: app)

    let response = try ExampleController().example(wrapper).wait()

    XCTAssertEqual(response, .ok)
  }
}

为了确保不会遇到并发问题,我将测试应用程序中的数据库池限制为 1 个连接。

func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
  // ... other configurations

  let poolConfig = DatabaseConnectionPoolConfig(maxConnections: 1)
  services.register(poolConfig)
}

非常感谢Jakub Jatczak 帮助我了解这在 Rails 中是如何发生的。

【讨论】:

  • 奇怪,当我尝试这个时,测试永远不会运行,它们只是在迁移数据库时永远挂起。
  • 当我看到测试挂起时,这通常意味着我阻止了唯一可访问的连接而没有释放它。确保您的测试代码不会导致死锁。
【解决方案3】:

聚会迟到了,但revertmigrate 命令也可以正常工作。此代码执行与@0xTim 给出的答案类似的命令。但是我已经使用了Console.framework

我们大多使用configure.swift 文件,如下所示:

import FluentPostgreSQL
import Vapor

public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
    // Register providers first
    try services.register(FluentPostgreSQLProvider())

    ...


    /// Configure commands
    var commandConfig = CommandConfig.default()
    commandConfig.useFluentCommands()
    services.register(commandConfig)

    ...

    /// Configure migrations
    services.register { container -> MigrationConfig in
        var migrationConfig = MigrationConfig()
        try migrate(migrations: &migrationConfig)
        return migrationConfig
    }
}

聚会迟到了,但以下代码确实执行了还原和迁移 命令:(我正在使用QuickNimble 所以beforeSuite。注释代码在那里,因为除非你使用上面的configure.swift,否则你可以取消注释代码并直接使用CommandConfig。)

import Quick
import Vapor
import Console
import FluentPostgreSQL
...
        configuration.beforeSuite {
            let console: Console = Terminal()
//            var commandConfig = CommandConfig()
//            commandConfig.use(RevertCommand(), as: "revert")
//            commandConfig.use(MigrateCommand(), as: "migrate")
            var config = Config.default()
            var env = Environment.testing
            var services = Services.default()

            do {
//                try App.configure(&config, &env, &services)
                let container = try Application(config: config, environment: env, services: services)
                let commandConfig = try container.make(CommandConfig.self)
                let commands = try commandConfig.resolve(for: container).group()
                var input = CommandInput(arguments: ["vapor","revert","--all", "-y"])
                try console.run(commands, input: &input, on: container).wait()
                input = CommandInput(arguments: ["vapor","migrate","-y"])
                try console.run(commands, input: &input, on: container).wait()
            } catch let error {
                console.error(error.localizedDescription)
                exit(1)
            }
        }

【讨论】:

    【解决方案4】:

    对于那些正在寻求另一种不涉及注册新迁移的方法(并且,对我来说,增加更多代码复杂性)的人,您可以使用 Pre-Action 脚本来测试目标(⌘ +

    通过使用 bash 脚本,您可以创建一个全新的 postgresql 数据库,该数据库将用于构建项目仅用于测试:

    # Variables 
    export IS_TEST=true
    export DB_USERNAME="`whoami`"
    export DB_DBNAME="BARTENDER_TEST_DB"
    
    
    #Creating dedicated Postgres DB 
    echo "deleting & recreating $DB_DBNAME for user $DB_USERNAME"
    psql postgres<< EOF
      DROP DATABASE "$DB_DBNAME";
      CREATE DATABASE "$DB_DBNAME";
      \list
    EOF
    

    然后在configure.swift 文件中创建一个与新创建的数据库匹配的PostgreSQLDatabaseConfig

        if let _ = Environment.get("IS_TEST") { // IS_TEST is defined only in Pre-Action script 
    
            guard let username = Environment.get("DB_USERNAME") else {
                fatalError("Failed to create PostgresConfig - DB_USERNAME in Environment variables")
            }
            guard let databasename = Environment.get("DB_DBNAME") else {
                fatalError("Failed to create PostgresConfig - DB_DBNAME in Environment variables")
            }
    
            postgresqlConfig = PostgreSQLDatabaseConfig(
                hostname: "127.0.0.1",
                port: 5432,
                username: username,
                database: databasename,
                password: nil
            )
        } 
        else { /* your other config here */ }
    
        let database = PostgreSQLDatabase(config: postgresqlConfig)
        ... 
    
    

    我在其中发现的最大优势是,我什至可以从另一个项目中触发 vapor buildvapor run,这将为我的持续集成测试创建一个全新的 Web 服务环境,只需插入正确的环境变量

    【讨论】:

      【解决方案5】:

      我发现的最简单方法是将PostgresKit 添加到测试目标并使用它来设置连接以调用我的“清理”查询。

      @testable import App
      import Vapor
      import XCTest
      import PostgresKit
      
      final class UserTests: XCTestCase {
          
          var pools: EventLoopGroupConnectionPool<PostgresConnectionSource>!
          var postgresDb: PostgresDatabase!
          var eventLoopGroup: EventLoopGroup!
          
          override func setUp() {
              let configuration = PostgresConfiguration(
                  hostname: "localhost",
                  username: "postgres",
                  password: "password",
                  database: "db_name"
              )
              
              eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2)
              
              pools = EventLoopGroupConnectionPool(
                  source: PostgresConnectionSource(configuration: configuration),
                  on: eventLoopGroup
              )
              
              postgresDb = pools.database(logger: Logger.init(label: "TestLogger"))
              
          }
          
          override func tearDown() {
              let _ = try! postgresDb.query("DELETE FROM \(User.schema)").wait()
              try! pools.syncShutdownGracefully()
              try! eventLoopGroup.syncShutdownGracefully()
          }
          
          func testUploadUser() throws {
              let app = Application(.testing)
              defer { app.shutdown() }
              try configure(app)
              
              try app.testable(method: .running).test(.POST, "api/users", beforeRequest: { req in
                  try req.content.encode(["firstName" : "Dwide", "lastName" : "Shrewd"])
              }, afterResponse: { res in
                  XCTAssertEqual(res.status, .ok)
                  let user = try res.content.decode(User.self)
                  XCTAssertEqual(user, User(id: user.id, firstName: "Dwide", lastName: "Shrewd"))
              })
          }
          
      }
      

      这是我的Package.swift

      // swift-tools-version:5.2
      import PackageDescription
      
      let package = Package(
          name: "MyVaporProject",
          platforms: [
             .macOS(.v10_15)
          ],
          dependencies: [
              // ? A server-side Swift web framework.
              .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
              .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
              .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"),
              .package(url: "https://github.com/vapor/postgres-kit.git", from: "2.0.0")
          ],
          targets: [
              .target(
                  name: "App",
                  dependencies: [
                      .product(name: "Fluent", package: "fluent"),
                      .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
                      .product(name: "Vapor", package: "vapor")
                  ],
                  swiftSettings: [
                      // Enable better optimizations when building in Release configuration. Despite the use of
                      // the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release
                      // builds. See <https://github.com/swift-server/guides#building-for-production> for details.
                      .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
                  ]
              ),
              .target(name: "Run", dependencies: [.target(name: "App")]),
              .testTarget(
                  name: "AppTests",
                  dependencies: [
                      .target(name: "App"),
                      .product(name: "XCTVapor", package: "vapor"),
                      .product(name: "PostgresKit", package: "postgres-kit")
                  ]
              )
          ]
      )
      

      与之前的答案一样,这需要一个已迁移的站立式 Postgres 数据库,准备好在测试开始之前建立连接。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-07-24
        • 1970-01-01
        • 1970-01-01
        • 2021-11-04
        • 2021-11-05
        • 2011-04-15
        • 1970-01-01
        相关资源
        最近更新 更多