Snapshooter é uma ferramenta de teste de instantâneo para o núcleo .NET e a estrutura .NET
O Snapshooter é uma ferramenta de teste de instantâneo flexível para simplificar a validação de resultado em seus testes de unidade no .NET. É baseado na idéia de testes de instantâneos no jest.
Para obter informações mais detalhadas sobre o Snapshooter, vá para o Snapshooter Docs
Para começar, instale o pacote Xunit ou Nunit NUGET:
dotnet add package Snapshooter.Xunitdotnet add package Snapshooter.NUnitdotnet add package Snapshooter.MSTestComece
Para afirmar os resultados do seu teste com instantâneos em seus testes de unidade, siga as seguintes etapas:
Insira um instantâneo afirmam Instantação Snapshot.Match(yourResultObject); no seu teste de unidade.
Exemplo:
/// <summary>
/// Tests if the new created person is valid.
/// </summary>
[ Fact ]
public void CreatePersonSnapshotTest ( )
{
// arrange
var serviceClient = new ServiceClient ( ) ;
// act
TestPerson person = serviceClient . CreatePerson (
Guid . Parse ( "2292F21C-8501-4771-A070-C79C7C7EF451" ) , "David" , "Mustermann" ) ;
// assert
Snapshot . Match ( person ) ;
} A instrução Snapshot.Match(person) cria um novo instantâneo do seu objeto de resultado e o armazena na pasta __snapshots__ . A pasta __snapshots__ fica sempre ao lado do seu arquivo de teste de unidade executada.
Nome do instantâneo: <UnitTestClassName>.<TestMethodName>.snap
Revise seu novo arquivo de instantâneo __snapshots__/<UnitTestClassName>.<TestMethodName>.snap
Agora, a instrução Snapshot.Match(person) criará novamente um instantâneo do resultado do seu teste e o comparará com o seu instantâneo revisado na pasta __snapshots__ . A pasta __snapshots__ fica sempre ao lado do seu arquivo de teste de unidade executada.
Se o seu objeto de resultado foi alterado e o instantâneo existente não estiver mais correspondente, o teste de unidade falhará. A mensagem de erro de teste de unidade apontará para a posição exata de incompatibilidade dentro do instantâneo.
Além disso, na pasta instantânea __snapshots__ Uma subpasta com nome __mismatch__ será criada. Nesta pasta, você pode encontrar o instantâneo real que está incompatível com o instantâneo existente na pasta __snapshots__ . Portanto, é possível comparar os dois instantâneos com uma ferramenta de comparação de texto.
Se o instantâneo na pasta incompatível __mismatch__ estiver correto, basta movê -lo para a pasta pai __snapshots__ (substituir a existente).
Leia mais
A sintaxe de correspondência padrão para instantâneos é:
Snapshot . Match ( person ) ;No entanto, também poderíamos usar a sintaxe fluente:
person . MatchSnapshot ( ) ;Ou podemos usar a sintaxe de deve () de fluentAsserção:
person . Should ( ) . MatchSnapshot ( ) ;Para Nunit, apoiaremos a Sintaxe assert. Essa (em breve):
Assert . That ( person , Match . Snapshot ( ) ) ; Se alguns campos no seu instantâneo forem ignorados durante a afirmação de instantâneos, as seguintes opções de ignorar podem ser usadas:
[ Fact ]
public void CreatePersonSnapshot_IgnoreId ( )
{
// arrange
var serviceClient = new ServiceClient ( ) ;
// act
TestPerson person = serviceClient . CreatePerson ( "Hans" , "Muster" ) ;
// assert
Snapshot . Match < Person > ( testPerson , matchOptions => matchOptions . IgnoreField ( "Size" ) ) ;
}Os campos a serem ignorados serão localizados via JSONPATH; portanto, você é muito flexível e também pode ignorar os campos de objetos ou matrizes infantis.
Ignore exemplos:
// Ignores the field 'StreetNumber' of the child node 'Address' of the person
Snapshot . Match < Person > ( person , matchOptions => matchOptions . IgnoreField ( "Address.StreetNumber" ) ) ;
// Ignores the field 'Name' of the child node 'Country' of the child node 'Address' of the person
Snapshot . Match < Person > ( person , matchOptions => matchOptions . IgnoreField ( "Address.Country.Name" ) ) ;
// Ignores the field 'Id' of the first person in the 'Relatives' array of the person
Snapshot . Match < Person > ( person , matchOptions => matchOptions . IgnoreField ( "Relatives[0].Id" ) ) ;
// Ignores the field 'Name' of all 'Children' nodes of the person
Snapshot . Match < Person > ( person , matchOptions => matchOptions . IgnoreField ( "Children[*].Name" ) ) ;
// Ignores all fields with name 'Id'
Snapshot . Match < Person > ( person , matchOptions => matchOptions . IgnoreField ( "**.Id" ) ) ; Se queremos ignorar todos os campos por um nome específico, temos duas opções:
Opção 1: use a opção Ignore Match 'IgnoreAllfields ()' e adicione o nome.
// Ignores all fields with name 'Id'
Snapshot . Match < Person > ( person , matchOptions => matchOptions . IgnoreAllFields ( "Id" ) ) ;Opção 2: use a opção Ignorar padrão, 'ignorefields (**.)' Com a seguinte sintaxe jsonpath **.
// Ignores all fields with name 'Id'
Snapshot . Match < Person > ( person , matchOptions => matchOptions . IgnoreFields ( "**.Id" ) ) ;Se alguns campos do nosso instantâneo forem muito grandes, por exemplo, um campo binário com muitos dados, podemos usar a opção Hashfield. A opção Hashfield cria um hash do valor do campo e, portanto, cada vez que apenas o hash é comparado. Se houver uma alteração no valor do campo, a correspondência instantânea falhará.
[ Fact ]
public void ImageSnapshot_HashImageBinary ( )
{
// arrange
var serviceClient = new ServiceClient ( ) ;
// act
TestImage image = serviceClient . CreateMonaLisaImage ( ) ;
// assert
Snapshot . Match ( image , matchOptions => matchOptions . HashField ( "Data" ) ) ;
}Exemplo instantâneo com hash
{
"Id" : 3450987 ,
"OwnerId" : " 0680faef-6e89-4d52-bad8-291053c66696 " ,
"Name" : " Mona Lisa " ,
"CreationDate" : " 2020-11-10T21:23:09.036+01:00 " ,
"Price" : 951868484.345 ,
"Data" : " m+sQR9KG9WpgYoQiRASPkt9FLJOLsjK86UuiXKVRzas= "
}O (s) campo (s) para hash pode ser localizado via JSONPATH ou via nome de campo.
Exemplos de campo de hash:
// Hash the field 'Data' of the child node 'Thumbnail' of the person
Snapshot . Match < Person > ( person , matchOptions => matchOptions . HashField ( "Thumbnail.Data" ) ) ;
// Hash the field 'Data' of the first thumbnail in the 'Thumbnails' array of the image
Snapshot . Match < Person > ( person , matchOptions => matchOptions . HashField ( "Thumbnails[0].Data" ) ) ;
// Ignores the field 'Data' of all 'Thumbnails' nodes of the image
Snapshot . Match < Person > ( person , matchOptions => matchOptions . HashField ( "Thumbnails[*].Data" ) ) ;
// Ignores all fields with name 'Data'
Snapshot . Match < Person > ( person , matchOptions => matchOptions . HashField ( "**.Data" ) ) ;Às vezes, existem campos em um instantâneo, que você deseja afirmar separadamente contra outro valor.
Por exemplo, o campo de identificação de uma 'pessoa' é sempre gerado recentemente em um serviço; portanto, você recebe no teste sempre uma pessoa com um novo ID (Guid). Agora, se você deseja verificar se o ID não é um GUID vazio, a opção Assert pode ser usada.
/// <summary>
/// Tests if the new created person is valid and the person id is not empty.
/// </summary>
[ Fact ]
public void CreatePersonSnapshot_AssertId ( )
{
// arrange
var serviceClient = new ServiceClient ( ) ;
// act
TestPerson person = serviceClient . CreatePerson ( "Hans" , "Muster" ) ; // --> id is created within the service
// assert
Snapshot . Match < Person > ( testPerson , matchOption => matchOption . Assert (
fieldOption => Assert . NotEqual ( Guid . Empty , fieldOption . Field < Guid > ( "Id" ) ) ) ) ;
}Os campos a serem afirmados estarão localizados via JSONPATH; portanto, você é muito flexível e também pode ignorar os campos de objetos ou matrizes infantis.
ASSERT EXEMPLOS:
// Assert the field 'Street' of the 'Address' of the person
Snapshot . Match < Person > ( person , matchOption => matchOption . Assert (
fieldOption => Assert . Equal ( 15 , fieldOption . Field < int > ( "Address.StreetNumber" ) ) ) ) ;
// Asserts the field 'Code' of the field 'Country' of the 'Address' of the person
Snapshot . Match < Person > ( person , matchOption => matchOption . Assert (
fieldOption => Assert . Equal ( "De" , fieldOption . Field < CountryCode > ( "Address.Country.Code" ) ) ) ) ;
// Asserts the fist 'Id' field of the 'Relatives' array of the person
Snapshot . Match < Person > ( person , > matchOption . Assert (
fieldOption => Assert . NotNull ( fieldOption . Field < string > ( "Relatives[0].Id" ) ) ) ) ;
// Asserts every 'Id' field of all the 'Relatives' of the person
Snapshot . Match < Person > ( person , > matchOption . Assert (
fieldOption => Assert . NotNull ( fieldOption . Fields < string > ( "Relatives[*].Id" ) ) ) ) ;
// Asserts 'Relatives' array is not empty
Snapshot . Match < Person > ( person , > matchOption . Assert (
fieldOption => Assert . NotNull ( fieldOption . Fields < TestPerson > ( "Relatives[*]" ) ) ) ) ;A funcionalidade afirmadora do Snapshooter não se limita a afirmar Xunit ou Nunit, também pode ser usada asserções fluentes ou outra ferramenta assert.
Todas as verificações de campo ignoradas, isttype ou afirmam podem ser concatenadas.
[ Fact ]
public void Match_ConcatenateFieldChecksTest_SuccessfulMatch ( )
{
// arrange
var serviceClient = new ServiceClient ( ) ;
// act
TestPerson person = serviceClient . CreatePerson ( "Hans" , "Muster" ) ;
// act & assert
Snapshot . Match < TestPerson > ( testPerson , matchOption => matchOption
. Assert ( option => Assert . NotEqual ( Guid . Empty , option . Field < Guid > ( "Id" ) ) )
. IgnoreField < DateTime > ( "CreationDate" )
. Assert ( option => Assert . Equal ( - 58 , option . Field < int > ( "Address.StreetNumber" ) ) )
. Assert ( option => testChild . Should ( ) . BeEquivalentTo ( option . Field < TestChild > ( "Children[3]" ) ) )
. IgnoreField < TestCountry > ( "Address.Country" )
. Assert ( option => Assert . Null ( option . Field < TestCountry > ( "Relatives[0].Address.Plz" ) ) ) ) ;
} Ao executar testes de instantâneos em uma construção de CI, você pode garantir que os instantâneos sejam verificados corretamente, pois os testes sem um instantâneo criarão apenas o instantâneo inicial e se tornarão verdes.
Para falhar em testes que não tenham um instantâneo no seu CI-Build, você pode definir o comportamento do Snapshooter como modo rigoroso, definindo a variável de ambiente SNAPSHOOTER_STRICT_MODE on ou true .
Este projeto adotou o Código de Conduta definido pelo Pacto Colaborador para esclarecer o comportamento esperado em nossa comunidade. Para obter mais informações, consulte o Código de Conduta da Swiss Life OSS.