Testing

En esta sección puedes ver cómo escribir y ejecutar pruebas unitarias para contratos inteligentes cosmwasm.

Algunas de las mejores prácticas y consejos incluyen:

  • Probar funciones de consulta: Aprende a probar las funciones de consulta de forma efectiva llamándolas con varios parámetros y verificando los resultados. Esto garantiza que los usuarios puedan interactuar con el contrato inteligente según lo previsto.

  • Pruebe la gestión de errores y los casos extremos: Descubra cómo cubrir escenarios de manejo de errores y casos de borde en sus pruebas. Esto garantiza que el contrato inteligente se comporte correctamente en situaciones inesperadas o cuando reciba entradas inesperadas.

  • Pruebas con dependencias simuladas personalizadas: Comprenda cómo crear dependencias simuladas personalizadas para sus pruebas, simulando diferentes escenarios y condiciones que pueden surgir durante la ejecución del contrato inteligente. Esto ayuda a verificar el comportamiento del contrato en diferentes circunstancias.

Para aprender a realizar pruebas unitarias en contratos inteligentes cosmwasm, puedes echar un vistazo al siguiente tutorial:

En este punto, tu código debería estar compilado, aunque no hayas probado si funciona o no. Puedes desplegar el código en la blockchain cada vez que hagas un cambio, pero esto no es un uso eficiente de tu tiempo. También es importante mantener el contrato intacto y bien probado para futuras modificaciones.

#[cfg(test)]
mod tests {
  use super::*;
  use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MOCK_CONTRACT_ADDR};
  use cosmwasm_std::{attr, coins, CosmosMsg};

Tiene la opción de mantener sus pruebas y código en el mismo archivo o en archivos separados. Vea un ejemplo aquí.

Para cada prueba, es importante simular variables específicas, como el tiempo de bloqueo y el estado. Escribir una función para facilitar la configuración puede hacer que este proceso sea más manejable.

#[test]
fn proper_initialization() {
  let mut deps = mock_dependencies(&[]);
  let msg = InitMsg {
    counter_offer: coins(40, "ETH"),
    expires: 100_000,
  };
  let info = mock_info("creator", &coins(1, "BTC"));
  // we can just call .unwrap() to assert this was a success
  let res = init(deps.as_mut(), mock_env(), info, msg).unwrap();
  assert_eq!(0, res.messages.len());
  // it worked, let's query the state
  let res = query_config(deps.as_ref()).unwrap();
  assert_eq!(100_000, res.expires);
  assert_eq!("creator", res.owner.as_str());
  assert_eq!("creator", res.creator.as_str());
  assert_eq!(coins(1, "BTC"), res.collateral);
  assert_eq!(coins(40, "ETH"), res.counter_offer);
}

Genial, ya tienes un inicializador de entorno de pruebas. Este es bastante básico; puedes pasar variables a la función y hacer varias modificaciones. Echa un vistazo a cosmwasm-plus para más opciones.

Información de dependencias, entorno y mensajes simulados

Hay tres herramientas de mocking que deberíamos mejorar:

/// All external requirements that can be injected for unit tests.
/// It sets the given balance for the contract itself, nothing else
pub fn mock_dependencies(
  contract_balance: &[Coin],
) -> OwnedDeps<MockStorage, MockApi, MockQuerier> {
  let contract_addr = HumanAddr::from(MOCK_CONTRACT_ADDR);
  OwnedDeps {
    storage: MockStorage::default(),
    api: MockApi::default(),
    querier: MockQuerier::new(&[(&contract_addr, contract_balance)]),
  }
}

mock_dependencies se usa para mock storage, api, y querier.

/// Returns a default enviroment with height, time, chain_id, and contract address.
/// You can submit as is to most contracts, or modify height/time if you want to
/// test for expiration.
///
/// This is intended for use in test code only.
pub fn mock_env() -> Env {
  Env {
    block: BlockInfo {
      height: 12_345,
      time: 1_571_797_419,
      time_nanos: 879305533,
      chain_id: "archway-testnet-14002".to_string(),
    },
    contract: ContractInfo {
      address: HumanAddr::from(MOCK_CONTRACT_ADDR),
    },
  }
}

mock_env se utiliza para bloques de imitación y entornos de contrato.

/// Just set sender and sent funds for the message.
/// This is intended for use in test code only.
pub fn mock_info<U: Into<HumanAddr>>(sender: U, sent: &[Coin]) -> MessageInfo {
  MessageInfo {
    sender: sender.into(),
    sent_funds: sent.to_vec(),
  }
}

mock_info se utiliza para simular entornos de transacción.

Gestor de pruebas

Gestor de transferencias de prueba

#[test]
fn transfer() {
  let mut deps = mock_dependencies(&[]);
  let msg = InitMsg {
    counter_offer: coins(40, "ETH"),
    expires: 100_000,
  };
  let info = mock_info("creator", &coins(1, "BTC"));
  // we can just call .unwrap() to assert this was a success
  let res = init(deps.as_mut(), mock_env(), info, msg).unwrap();
  assert_eq!(0, res.messages.len());
  // random cannot transfer
  let info = mock_info("anyone", &[]);
  let err = handle_transfer(deps.as_mut(), mock_env(), info, HumanAddr::from("anyone"))
    .unwrap_err();
  match err {
    ContractError::Unauthorized {} => {}
    e => panic!("unexpected error: {}", e),
  }
  // owner can transfer
  let info = mock_info("creator", &[]);
  let res =
    handle_transfer(deps.as_mut(), mock_env(), info, HumanAddr::from("someone")).unwrap();
  assert_eq!(res.attributes.len(), 2);
  assert_eq!(res.attributes[0], attr("action", "transfer"));
  // check updated properly
  let res = query_config(deps.as_ref()).unwrap();
  assert_eq!("someone", res.owner.as_str());
  assert_eq!("creator", res.creator.as_str());
}

Ejecucion de test

#[test]
fn execute() {
  let mut deps = mock_dependencies(&[]);
  let amount = coins(40, "ETH");
  let collateral = coins(1, "BTC");
  let expires = 100_000;
  let msg = InitMsg {
    counter_offer: amount.clone(),
    expires: expires,
  };
  let info = mock_info("creator", &collateral);
  // we can just call .unwrap() to assert this was a success
  let _ = init(deps.as_mut(), mock_env(), info, msg).unwrap();
  // set new owner
  let info = mock_info("creator", &[]);
  let _ = handle_transfer(deps.as_mut(), mock_env(), info, HumanAddr::from("owner")).unwrap();
  // random cannot execute
  let info = mock_info("creator", &amount);
  let err = handle_execute(deps.as_mut(), mock_env(), info).unwrap_err();
  match err {
    ContractError::Unauthorized {} => {}
    e => panic!("unexpected error: {}", e),
  }
  // expired cannot execute
  let info = mock_info("owner", &amount);
  let mut env = mock_env();
  env.block.height = 200_000;
  let err = handle_execute(deps.as_mut(), env, info).unwrap_err();
  match err {
    ContractError::OptionExpired { expired } => assert_eq!(expired, expires),
    e => panic!("unexpected error: {}", e),
  }
  // bad counter_offer cannot execute
  let msg_offer = coins(39, "ETH");
  let info = mock_info("owner", &msg_offer);
  let err = handle_execute(deps.as_mut(), mock_env(), info).unwrap_err();
  match err {
    ContractError::CounterOfferMismatch {
      offer,
      counter_offer,
    } => {
      assert_eq!(msg_offer, offer);
      assert_eq!(amount, counter_offer);
    }
    e => panic!("unexpected error: {}", e),
  }
  // proper execution
  let info = mock_info("owner", &amount);
  let res = handle_execute(deps.as_mut(), mock_env(), info).unwrap();
  assert_eq!(res.messages.len(), 2);
  assert_eq!(
    res.messages[0],
    CosmosMsg::Bank(BankMsg::Send {
      from_address: MOCK_CONTRACT_ADDR.into(),
      to_address: "creator".into(),
      amount,
    })
  );
  assert_eq!(
    res.messages[1],
    CosmosMsg::Bank(BankMsg::Send {
      from_address: MOCK_CONTRACT_ADDR.into(),
      to_address: "owner".into(),
      amount: collateral,
    })
  );
  // check deleted
  let _ = query_config(deps.as_ref()).unwrap_err();
}

Ahora ejecuta las pruebas:

cargo test

Si todo está en verde, el código funcionará en la cadena.

Last updated