Este artigo foi traduzido por máquina.

O programador

Explorando o NSpec

Ted Neward

Ted NewardOs leitores da minha coluna sabe que um dos mecanismos de que eu gosto de usar para explorar ou demonstrar uma nova tecnologia (biblioteca ou outra forma) é que uma colega descreveu uma vez como um "teste de exploração": uma suíte de testes de unidade projetada para não testar a tecnologia, mas explorá-lo. Permite-me — o desenvolvedor/usuário — para validar algumas suposições que estou fazendo sobre a tecnologia. Mais importante, dá-me uma suite de regressão para executar quando uma nova versão do que a tecnologia vem para fora, para que se a nova versão faz algum tipo de alteração significativa crítica, descobre sobre isso agora mesmo, e não depois que eu fiz a atualização (e teve que descobrir a alteração significativa em tempo de execução, cercada no contexto do meu programaem vez de contexto muito leve de uma suíte de testes).

Sobre o assunto de teste, no entanto, tem havido alguma angústia e consternação na Comunidade sobre diferentes "modos" de testes: unidade de teste versus desenvolvimento Test-driven (TDD) versus Behavior-driven desenvolvimento (BDD), geralmente acompanhado com muitos gritos. Devido à natureza religiosa de tais debates, eu tendia a evitar esse assunto, mas recentemente (como parte de fazer algumas pesquisas para esta coluna, na verdade), eu descobri uma ferramenta de teste — NSpec — que vem da multidão BDD. Independentemente do quanto você gosta ou não gosta da abordagem BDD para testes, NSpec vale a pena explorar.

Guia de Introdução

Como com muitos outro Microsoft .NET Framework pacotes nestes dias, começando com NSpec está apenas um comando de NuGet "Instalar pacote". Como um pretexto para isso, gostaria de começar por criar uma solução de biblioteca de classe (translation from VPE for Csharp, porque ele venceu o concurso de pedra-papel-tesoura-lagarto-Spock que tive entre ele, F # e Visual Basic dos direitos deve ser o código para testar) para construir uma classe de calculadora de notação polonesa reversa (RPN) que chamarei de "NiftyCalc" por falta de algo mais original. NiftyCalc vai ser um pouco de um depravado do tradicionais calculadoras RPN, no entanto, na maior parte para ter algo um pouco mais complexo para testar.

Começarei com noções básicas de esqueleto. Depois de criar uma nova solução e projeto de biblioteca de classes no Visual Studio, vou criar uma classe simples que tem um membro de propriedade única e auto-gerada, chamado corrente, para refletir o atual valor mantido na memória da calculadora:

namespace Calculator
{
  public class NiftyCalc
  {
    public NiftyCalc()
    {
      Current = 0;
    }
    public int Current { get; set; }
  }
}

Não é grande coisa, mas dá bastante andaimes para obter NSpec instalado para que eu possa olhar para os inícios de usá-lo.

Depois de fazer o "Install-Package nspec" no Console do Gerenciador de Pacotes , um par de coisas vai ter aparecido. Primeiro, NSpec insere outro arquivo para o projeto de biblioteca de classes chamado DebuggerShim.cs. Não vou entrar em muitos detalhes sobre isso agora, mas ele permite que você executar testes NSpec via TDD e ReSharper, juntamente com prevêem a integração contínua. Mais importante, NSpec não criou um projeto autônomo como um ponto para o projeto de biblioteca de classe, como um padrão Microsoft teste ou NUnit cenário de teste que — a intenção é que o NSpec testes (ou, como a empresa prefere chamá-los, especificações) estão vivendo dentro do mesmo projeto como o código que estão especificando.

Então, o que significa que para criar um teste para testar isso, você só pode criar uma classe que estende a classe base "nspec":

using NSpec;
namespace Calculator
{
  class my_first_spec : nspec
  {
    void given_the_world_has_not_come_to_an_end()
    {
      it["Thunderhorse should be Thunderhorse"] =
        () => "Thunderhorse".should_be("Thunderhorse ");
      }
  }
  // ...
}

Isto claramente não é um teste de unidade tradicional: falta-lhe as marcas de atributo personalizado; a Convenção de nomenclatura tem todos aqueles sublinhados Há algum tipo de dicionário de cadeia de caracteres-para-lambda no trabalho aqui ("-"); e tudo é essencialmente privado/interno. Fazer mais coisas significativamente diferentes, NSpec instalado uma nova ferramenta de executor de testes para o caminho dentro do Console do Gerenciador de Pacotes , e que é onde tem de virar para executar os testes. No Console do Gerenciador de Pacotes (que, lembre-se, é basicamente um console do Windows PowerShell executando dentro Visual Studio), execute o NSpecRunner.exe com o caminho completo para a biblioteca de classes compiladas:

PM> install-package nspec
Successfully installed 'nspec 0.9.65'.
Successfully added 'nspec 0.9.65' to Calculator.
PM> NSpecRunner.exe .
\Calculator\bin\debug\Calculator.dll
my first spec
  given the world has not come to an end
    Thunderhorse should be Thunderhorse
1 Examples, 0 Failed, 0 Pending
PM>

Agora, as convenções de nomenclatura estranhas tornam-se claras: NSpec usa reflexão para descobrir as classes e métodos para executar como parte da suíte de teste, e ele usa os sublinhados nos nomes como espaços ao imprimir a saída. A intenção aqui é chegar ao mais natural -­sintaxe similar a linguagem, para que eu possa escrever em "Minha primeira spec" é uma especificação, "dado o mundo não chegou ao fim, Thunderhorse deve ser Thunderhorse"; enquanto a expressão lambda associada com essa última parte da especificação produz verdadeiro, tudo é bom. Deve o spec/teste ser mudado ligeiramente tal que não as duas seqüências são iguais, NSpecRunner vai dar uma saída diferente (apenas como qualquer corredor de teste teria), como mostrado em Figura 1.

Figura 1 teste com duas cordas desiguais

PM> NSpecRunner.exe .
\Calculator\bin\debug\Calculator.dll
my first spec
  given the world has not come to an end
  Thunderhorse should be Thunderhorse - FAILED - Expected string   length 12 but was 6.
Strings differ at index 1.,   Expected: "Thunderhorse", But was: "Hello ", -----------------^
FAILURES ****
nspec.
my first spec.
given the world has not come to an end.
Thunderhorse should be Thunderhorse.
Expected string length 12 but was 6.
Strings differ at index 1., Expected: "Thunderhorse", But was: "Hello ", -----------------^
   at Calculator.my_first_spec.<Thunderhorse_should_be_Thunderhorse>b__2() in        c:\Projects\Publications\Articles\MSDN\WorkingProg\NSpec\NSpec\   Calculator\   Class1.cs:line 19
2 Examples, 1 Failed, 0 Pending
PM>

Como se pode inferir, a parte real de "teste" da especificação é o should_be método de extensão que NSpec introduz em qualquer coisa que deriva de System. Object. Além disso, há um número de sobrecargas similarmente nomeados na idéia, tal que, se você quer certificar-se de 2 + 2 é maior que 3, você pode usar o método de should_be_greater_than em vez disso. Outra meia-dúzia ou assim sobrecargas de veia similar estão disponíveis; qualquer pessoa familiarizada com qualquer framework de teste de unidade deve ser confortável com estas.

Um inconveniente a NSpec, pelo menos a versão (0,95) eu testei, é que NSpec profundamente não é integrado com o Visual Studio, para que você não pode pressionar F5 e construção/execução dos projectos; em vez disso, você tem que explicitamente construir a biblioteca, em seguida, encontrar o Console do Gerenciador de Pacotes e a seta para cima para a linha de NSpecRunner para começar os testes. O Web site NSpec.org tem uma solução para este, chamado specwatchr, que é um script Ruby que mantém um olho sobre os arquivos no diretório e arranca NSpecRunner sempre que um arquivo de origem é salvo. Embora não me importo, alguns desenvolvedores e algumas lojas girará em instalar a plataforma Ruby só para ter essa funcionalidade, útil, como pode ser o nariz. Pessoalmente, acho que qualquer aversão que você pode ter para instalar o Ruby em sua máquina é vastamente compensado por ter uma ferramenta que executa automaticamente testes como este, tanto mais que tem provado para ser uma técnica altamente útil em outros ambientes de plataforma e linguagem, com o .NET Framework como o solitário contrarian sobre este ponto. Eu recomendo tomar o tempo para dar um giro — em uma máquina virtual (VM) que não se importa, se você deve.

Teste NiftyCalc

Entretanto, com alguns dos princípios NSpec fora do caminho, posso recorrer colocando alguns testes em torno de NiftyCalc e estendê-lo. Para começar, quero ter a certeza de que quando um NiftyCalc é criado, seu conteúdo atual começa com 0; Quando eu aperto o número na pilha de NiftyCalc, esse número é o que é exibido como atual; e se eu pressionar um número, empurrar outro número e pop da pilha, o primeiro número seja mantido, conforme mostrado no Figura 2.

Figura 2 testes NiftyCalc funcionalidade

using NSpec;
namespace Calculator
{
  class nifty_calc : nspec
  {
    void calculator_basics()
    {
      it["New NiftyCalc should have a Current of 0"] =
        () => new NiftyCalc().Current.should_be(0);
      it["Pushing 5 should show Current as 5"] =
        () =>
        {
          NiftyCalc nc = new NiftyCalc();
          nc.Push(5);
          nc.Current.should_be(5);
        };
      it["Push, push, pop, should have Current set to first pushed value"] =
        () =>
        {
          NiftyCalc nc = new NiftyCalc();
          nc.Push(5); nc.Push(7); nc.Pop();
          nc.Current.should_be(5);
        };
      }
    }
  public class NiftyCalc
  {
    private Stack<int> theStack = new Stack<int>();
    public NiftyCalc()
    {
      theStack.Push(0);
    }
    public int Current
    {
      get { return theStack.Peek(); }
    }
    public void Push(int value)
    {
      theStack.Push(value);
    }
    public int Pop()
    {
      return theStack.Pop();
    }
  }
}

Até aqui tudo bem. Na verdade, é quase redundante para descrever, em prosa, o teste que eu quero escrever e, em seguida, mostrar o código para o teste, porque a sintaxe muito do NSpec teste do registro (o "it" instância dentro de classe derivada de NSpec) realmente permite como verbose ou como lapidar uma descrição como eu gostaria. Isto é, como mencionado anteriormente, por design, para que testes e especificações são fáceis de ler e compreender — que, tendo em conta quantas vezes suítes de testes estão sendo usados como documentação hoje em dia, é uma coisa valiosa.

Exceções

Claro, há também a questão de alguns casos de borda a considerar: O que deve acontecer no evento um usuário tenta pop mais um valor fora do NiftyCalc do que alguma vez foi empurrado? É um problema comum com pilhas, mas NiftyCalc não está tentando ser uma pilha, mas prefiro uma calculadora funcional. Para esse fim, o método Pop e método de obter a propriedade atual devem verificar para ver se há alguma coisa na pilha para recuperar e se não, produzir um valor de zero.

No entanto, o negócio prefere que NiftyCalc lançar uma exceção quando popping uma pilha vazia, o NSpec código pode verificar para que, desta forma:

it["Should throw a NullRef if the NiftyCalc is null"] =
  expect<NullReferenceException>( () => {
    NiftyCalc nc = null;
    nc.Push(5);
  });

Novamente, isto não é muito longe do que uma estrutura de teste de unidade (como Microsoft Test Manager ou NUnit) parece ser.

Contexto

Há uma quantidade justa de repetição com o código anterior NSpec, a propósito; seria bom se você pudesse arranjar um "contexto" em que o teste é executado, assim você não precisa alocar o NiftyCalc e colocar alguns valores para testar constantemente. (Isto será inestimável para o próximo conjunto de testes, porque eu quero testar a funcionalidade de adicionar, subtrair e assim por diante.) NSpec permite que você crie nesse contexto, como mostrado em Figura 3.

Figura 3 criação de um contexto de teste

using NSpec;
namespace Calculator
{
  class nifty_calc : nspec
  {
    void calculator_basics()
    {
      // ...
void before_each()
      {
        Calc = new NiftyCalc(); //maybe move this line here
      }
      context["When NiftyCalc has a 2 and 3 pushed"] = () =>
      {
        before = () =>
        {
          calc.Push(3);
          calc.Push(2);
        };
        it["should Add to 5"] = () =>
        {
          calc.Add();
          calc.Current.should_be(5);
        };
        it["should Subtract to 1"] = () =>
        {
          calc.Subtract();
          calc.Current.should_be(1);
        };
      };
    }
    private NiftyCalc calc;
  }
  public class NiftyCalc
  {
    private Stack<int> theStack = new Stack<int>();
    public NiftyCalc() { }
    public int Current
    {
      get { return (theStack.Count != 0) ?
theStack.Peek() : 0; }
    }
    public void Push(int value) {
      theStack.Push(value);
    }
    public int Pop() {
      return (theStack.Count != 0) ?
theStack.Pop() : 0;
    }
    public void Add() { Push(Pop() + Pop()); }
    public void Subtract() { int top = Pop();     
    int bot = Pop(); Push(bot - top); }
  }
}

Este teste também mostra como fazer uso do "antes" gancho, para configurar cada exercício do teste individual antes de executá-lo — isso lhe dá a oportunidade de criar a instância de NiftyCalc e empurrar dois valores a ele antes de entregá-lo para o teste para execução.

Uso crescente

Há um montão mais sobre NSpec — e o maior assunto do BDD — além do que é discutido aqui, mas este obtém alguns dos princípios em jogo. Os entusiastas do BDD irão criticar o estilo e a abordagem que tirei para escrever meus testes, mas esse é um debate estilístico e estético nem quero participar nem tentar capturar neste artigo. NSpec em si é uma abordagem interessante para testes — e mais importante, um componente que está aparecendo em mais projetos de código aberto. Se você quer aprender mais sobre NSpec, Amir Rajan, codeveloper do quadro, gentilmente se ofereceu para responder perguntas e fornecer orientação. Você pode contatá-lo no Twitter em twitter.com/amirrajan ou através do seu site GitHub em github.com/amirrajan.

Boa codificação.

Ted Neward é o diretor da Neward & Associates LLC. Ele tem escrito mais de 100 artigos e autor e co-autor de vários livros, incluindo "Professional F # 2.0" (Wrox, 2010). Ele é um F # MVP e fala em conferências em todo o mundo. Ele consulta, mentores regularmente — contatá-lo em ted@tedneward.com se você está interessado em ter-lhe vir trabalhar com sua equipe, ou ler seu blog em blogs.tedneward.com.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Amir Ribeiro (melhorando a empresas)