Comprensión durante la lectura
¿Qué diferencias fundamentales existen entre XCTest y Swift Testing?
- No se usa el API de
XCTest, o sea clases comoXCTestCaseoXCTAssert. - Swift Testing no soporta el uso de
XCTestExpectation. - Se prefiere el uso de
structsobreclass. - Se usan macros como
@Test,@Suite,#expect.
¿Por qué las expectations no funcionan en Swift Testing y cómo se reemplazan?
Swift Testing no soporta el uso de XCTestExpectation porque es una biblioteca diferente.
En su lugar se puede combinar el uso de:
-
withCheckedContinuation(isolation:function:_:)para poder envolver el llamado de un código asíncrono que no tieneasyncen la firma -
withObservationTracking(_:onChange:)para detectar cambios en alguna propiedad y, ante esto, invocarresume()sobre elCheckedContinuation.
¿Qué rol juegan withCheckedContinuation y confirmation() al probar código asíncrono?
withCheckedContinuation sirve para poner un await sobre un código asíncrono que no tenía async en su firma. Este código asíncrono debe llamar resume() sobre el CheckedContinuation.
confirmation(_:expectedCount:isolation:sourceLocation:_:) confirma que cierto evento ocurrió durante el llamado de una función. Este método recibe un body con async en su firma, cuenta cuántas veces se invocó el Confirmation mientras estuvo esperando el body (i.e. await body(confirmation)).
En la práctica este último método sirve para ver si efectivamente hubo la modificación que se esperaba. En el artículo se usó en conjunto con withObservationTracking(_:onChange:) para detectar si hubo un cambio en articleSearcher.searchResults.
¿Cómo se reemplazan setUp y tearDown en Swift Testing?
setUp y tearDown se reemplazan con init y deinit (para este último se requiere usar class en lugar de struct).
¿Por qué no se puede llamar código async dentro de deinit y cómo se soluciona?
Se supone que deinit se llama porque ya se va a liberar el objeto de memoria - No se puede retrasar más esta operación, así que no se puede retener self ni ningún atributo de la prueba.
Para solucionarlo se requiere llamar el código async que se quiere que vaya en el deinit, directamente dentro del método de prueba.
Una solución alternativa consiste en definir un TestScoping, que es un protocolo que le dice al test runner que ejecute un código personalizado antes o después de que ejecuta la suite o función de pruebas. Lo anterior quiere decir que se usa en conjunto con SuiteTrait y/o TestTrait.
Particularmente, si lo que se requiere es manipular el "sut" entonces se lo puede inyectar en una variable estática marcada con el macro TaskLocal para poder usarla desde el contexto de un Task.
Por ejemplo:
struct Environment {
@TaskLocal static var articleSearcher = ArticleSearcher()
}
struct ArticleSearcherDatabaseTrait: SuiteTrait, TestTrait, TestScoping {
@MainActor
func provideScope(
for test: Test,
testCase: Test.Case?,
performing function: () async throws -> Void
) async throws {
print("Running for test \(test.name)")
let articleSearcher = ArticleSearcher()
try await ArticleSearcherSwiftTesting.Environment.$articleSearcher
.withValue(articleSearcher) {
await articleSearcher.prepareDatabase()
try await function()
await articleSearcher.closeDatabase()
}
}
}
@Test(ArticleSearcherDatabaseTrait())
func testEmptyQuery() async {
await Environment.articleSearcher.search("")
#expect(
Environment.articleSearcher.searchResults
== ArticleSearcher.articleTitlesDatabase
)
}
¿Qué hace exactamente el macro @Test y en qué se diferencia de XCTestCase?
Marca una función como prueba para ser ejecutada por el runner de pruebas.
¿Cuándo conviene usar withCheckedContinuation versus confirmation()?
El primero es para convertir código asíncrono que recibe closures, en async/await. El segundo es para validar si un evento ocurre durante el llamado de una función async.
¿Qué protocolos implementa ArticleSearcherDatabaseTrait y para qué sirve cada uno?
Implementa SuiteTrait y TestTrait. El primero sirve para agregar código antes y después de la ejecución de todos los métodos de la suite de pruebas, y el segundo solo cubre a una sola función de pruebas.
¿Qué hace @TaskLocal y por qué es importante en el contexto de los "Test Scoping Traits"?
@TaskLocal permite acceder a una propiedad dentro de una jerarquía de tareas. O sea que, sin importar el nivel de profundidad dentro de esta jerarquía, siempre que no se haya usado un Task.detached, se puede acceder a ese TaskLocal.
Top comments (0)