Generación automática de simulaciones mediante mockall en AWS SDK para Rust
AWS SDK para Rust proporciona varios enfoques para probar el código que interactúa con Servicios de AWS. Puede generar automáticamente la mayoría de las implementaciones simuladas que necesitan las pruebas utilizando el popular automock de la caja mockall.
En este ejemplo se prueba un método personalizado denominado determine_prefix_file_size(). Este método llama a un método de contenedor list_objects() personalizado que llama a Amazon S3. Al simular list_objects(), el método determine_prefix_file_size() se puede probar sin contactar con Amazon S3.
-
En la línea de comandos del directorio del proyecto, agregue la caja
mockallcomo dependencia:$cargo add --dev mockallAl usar la opción
--dev, se agrega la caja a la sección[dev-dependencies]del archivoCargo.toml. Como dependencia de desarrollo, no se compila ni se incluye en el binario final que se utiliza para el código de producción. Este código de ejemplo también utiliza Amazon Simple Storage Service como el Servicio de AWS de ejemplo.
$cargo add aws-sdk-s3De esta forma, se agrega la caja a la sección
[dependencies]del archivoCargo.toml. -
Incluya el módulo
automockde la cajamockall.Incluya también cualquier otra biblioteca relacionada con el Servicio de AWS que esté probando, en este caso, Amazon S3.
use aws_sdk_s3 as s3; #[allow(unused_imports)] use mockall::automock; use s3::operation::list_objects_v2::{ListObjectsV2Error, ListObjectsV2Output}; -
A continuación, agregue el código que determina cuál de las dos implementaciones de la estructura de encapsulador de Amazon S3 de la aplicación debe utilizarse.
-
La real, escrita para acceder a Amazon S3 a través de la red.
-
La implementación simulada generada por
mockall.
En este ejemplo, al que está seleccionada se le asigna el nombre
S3. La selección es condicional y se basa en el atributotest:#[cfg(test)] pub use MockS3Impl as S3; #[cfg(not(test))] pub use S3Impl as S3; -
-
La estructura
S3Imples la implementación de la estructura de encapsulador de Amazon S3 Que realmente envía solicitudes a AWS.-
Cuando las pruebas están habilitadas, este código no se usa porque la solicitud se envía a la simulación y no a AWS. El atributo
dead_codele indica al linter que no informe de ningún problema si no se utiliza el tipoS3Impl. -
El
#[cfg_attr(test, automock)]condicional indica que cuando las pruebas están habilitadas, se debe establecer el atributoautomock. Esto indica amockallque hay que generar una simulación deS3Implal que se le asignará el nombreMock.S3Impl -
En este ejemplo, el método
list_objects()es la llamada que quiere que se simule.automockcreará automáticamente un métodoexpect_para usted.list_objects()
#[allow(dead_code)] pub struct S3Impl { inner: s3::Client, } #[cfg_attr(test, automock)] impl S3Impl { #[allow(dead_code)] pub fn new(inner: s3::Client) -> Self { Self { inner } } #[allow(dead_code)] pub async fn list_objects( &self, bucket: &str, prefix: &str, continuation_token: Option<String>, ) -> Result<ListObjectsV2Output, s3::error::SdkError<ListObjectsV2Error>> { self.inner .list_objects_v2() .bucket(bucket) .prefix(prefix) .set_continuation_token(continuation_token) .send() .await } } -
-
Cree las funciones de prueba en un módulo denominado
test.-
El
#[cfg(test)]condicional indica quemockalldebe crear el módulo de prueba si el atributotestestrue.
#[cfg(test)] mod test { use super::*; use mockall::predicate::eq; #[tokio::test] async fn test_single_page() { let mut mock = MockS3Impl::default(); mock.expect_list_objects() .with(eq("test-bucket"), eq("test-prefix"), eq(None)) .return_once(|_, _, _| { Ok(ListObjectsV2Output::builder() .set_contents(Some(vec![ // Mock content for ListObjectsV2 response s3::types::Object::builder().size(5).build(), s3::types::Object::builder().size(2).build(), ])) .build()) }); // Run the code we want to test with it let size = determine_prefix_file_size(mock, "test-bucket", "test-prefix") .await .unwrap(); // Verify we got the correct total size back assert_eq!(7, size); } #[tokio::test] async fn test_multiple_pages() { // Create the Mock instance with two pages of objects now let mut mock = MockS3Impl::default(); mock.expect_list_objects() .with(eq("test-bucket"), eq("test-prefix"), eq(None)) .return_once(|_, _, _| { Ok(ListObjectsV2Output::builder() .set_contents(Some(vec![ // Mock content for ListObjectsV2 response s3::types::Object::builder().size(5).build(), s3::types::Object::builder().size(2).build(), ])) .set_next_continuation_token(Some("next".to_string())) .build()) }); mock.expect_list_objects() .with( eq("test-bucket"), eq("test-prefix"), eq(Some("next".to_string())), ) .return_once(|_, _, _| { Ok(ListObjectsV2Output::builder() .set_contents(Some(vec![ // Mock content for ListObjectsV2 response s3::types::Object::builder().size(3).build(), s3::types::Object::builder().size(9).build(), ])) .build()) }); // Run the code we want to test with it let size = determine_prefix_file_size(mock, "test-bucket", "test-prefix") .await .unwrap(); assert_eq!(19, size); } }-
Cada prueba utiliza
let mut mock = MockS3Impl::default();para crear una instanciamockdeMockS3Impl. -
Usa el método
expect_list_objects()de la simulación (queautomockcreó automáticamente) para establecer el resultado esperado cuando el métodolist_objects()se usa en otra parte del código. -
Una vez establecidas las expectativas, las usa para probar la función llamando a
determine_prefix_file_size(). El valor devuelto se comprueba para confirmar que es correcto, utilizando una aserción.
-
-
La función
determine_prefix_file_size()usa el encapsulador de Amazon S3 para obtener el tamaño del archivo de prefijos:#[allow(dead_code)] pub async fn determine_prefix_file_size( // Now we take a reference to our trait object instead of the S3 client // s3_list: ListObjectsService, s3_list: S3, bucket: &str, prefix: &str, ) -> Result<usize, s3::Error> { let mut next_token: Option<String> = None; let mut total_size_bytes = 0; loop { let result = s3_list .list_objects(bucket, prefix, next_token.take()) .await?; // Add up the file sizes we got back for object in result.contents() { total_size_bytes += object.size().unwrap_or(0) as usize; } // Handle pagination, and break the loop if there are no more pages next_token = result.next_continuation_token.clone(); if next_token.is_none() { break; } } Ok(total_size_bytes) }
El tipo S3 se usa para llamar a las funciones encapsuladas de SDK para Rust sean compatibles con S3Impl y MockS3Impl cuando se realizan solicitudes HTTP. La simulación generada automáticamente por mockall informa de cualquier error en las pruebas cuando estas están habilitadas.
Puede ver el código completo para estos ejemplos