You are currently browsing the monthly archive for Março 2008.

Desde que trabalhei na Medialog, em que me ensinaram o valor de ter o código testado de forma automática, tentei sempre usar testes unitários nos projectos em que tive envolvido. Infelizmente, por vezes isto não é fácil… seja por cultura de empresa, etc, a verdade é que desde que vim para inglaterra, em quase todas as empresas em que estive envolvido, ninguém queria saber dos testes que produzia. Isto fez com que, para evitar a tarefa (muitas vezes) repetitiva de criar os testes, não testasse as classes ou métodos mais simples.

Mas ultimamente tenho lido um pouco sobre geração automática de testes num grupo do google e decidi experimentar um pouco com as soluções existentes.

A primeira, Pex, desenvolvido pela microsoft pareceu-me extremamente interessante. Retirado do site:

PexWeb Pex (Program EXploration) is an intelligent assistant to the programmer. From a parameterized unit test, it automatically produces traditional unit test suite with high code coverage. In addition, it suggests to the programmer how to fix the bugs.

Pex generates Unit Tests from hand-written Parameterized Unit Tests through Automated Exploratory Testing based on Dynamic Symbolic Execution.

Vendo o site (especialmente o walkthrough) vê-se que é uma abordagem extremamente interessante para a geração de testes. Como infelizmente ainda não está disponível… Passamos ao próximo.

O STDL. Apesar do Neville Grech, originário de Malta, ter criado o projecto como open source, este ainda é um ‘one man show’. Ainda assim é um producto que promete:

stdl

Automated testing has long been thought critical for large software development organizations, but is often considered to be too expensive and difficult to implement for smaller companies.

STDL tries to break this stereotype by offering a totally integrated package to automate your tests for no licensing costs (open source). The language is very easy to familiarise with so hopefully you can enjoy productivity gains right from the start.

STDL (Structured test description language) is a domain-specific testing language that is used to auto-generate unit test code.

Depois de ler o pequeno tutorial existente no site, decidi fazer download e testar. Como para a semana vou estar a fazer um validador de códigos postais e moradas usando uma API do Royal Mail, decidi começar a avançar nos testes que vou querer correr. Assim, fiz um stub para simular a minha futura classe:

public class PostCodeValidator
{
    private static readonly Regex ukPostCodeRegEx = new Regex(@"^(GIR 0AA)|([A-PR-UWYZ]((\d(\d|[A-HJKSTUW])?)|([A-HK-Y]\d(\d|[ABEHMNPRV-Y])?)) \d[ABD-HJLNP-UW-Z]{2})$");
 
    public bool Validate(string postCode)
    {
        Match match = ukPostCodeRegEx.Match(postCode);
        return match.Index == 0 && match.Length == postCode.Length;
    }
}

De seguida criei um ficheiro stdl. Apesar da documentação ainda ser não-existente, os exemplos providenciados quando se faz download do código são suficientes para começar a fazer qualquer coisa. Depois de uns 10 minutos, escrevi isto:

init:
    language:CS2
    classname:PostCodeValidator
    language_imports: System
    namespace:AutomatedTestGeneration
 
test postCodeChecker method <%Validate(%postCode%)%> returns <%bool%>:
    param <%string%> postCode:
        valid 0:
            postCode == <%"CV1 4DS"%>
            postCode == <%"CV4 4RT"%>
            postCode == <%"WC2B 5JF"%>
            out:
                returns == <%true%>
            error:
                returns == <%false%>
        valid 1:
            postCode == <%"Invalid postcode"%>
            out:
                returns == <%false%>
    

Que, após correr a ferramenta, me gerou os seguintes testes:

[TestFixture]
public class postCodeChecker
{
    public PostCodeValidator postcodevalidator;
    
    [SetUp()]
    public void Init()
    {
        this.postcodevalidator = new PostCodeValidator();
    }
 
    [Test]
    public void postCodeChecker0()
    {
        string postCode = "CV1 4DS";
        bool returns=this.postcodevalidator.Validate(postCode);
        Assert.That(returns, Is.EqualTo(true));
    }
 
    [Test]
    public void postCodeChecker1()
    {
        string postCode = "CV4 4RT";
        bool returns=this.postcodevalidator.Validate(postCode);
        Assert.That(returns, Is.EqualTo(true));
    }
 
    [Test]
    public void postCodeChecker2()
    {
        string postCode = "WC2B 5JF";
        bool returns=this.postcodevalidator.Validate(postCode);
        Assert.That(returns, Is.EqualTo(true));
    }
 
    [Test]
    public void postCodeChecker3()
    {
        string postCode = "Invalid postcode";
        bool returns=this.postcodevalidator.Validate(postCode);
        Assert.That(returns, Is.EqualTo(false));
    }
 
}

Excelente! Daqui, se quiser continuar com o desenvolvimento usando TDD, basta-me adicionar condições ao ficheiro stdl, tornando o processo muito mais rápido!

Do pouco que experimentei, e não sabendo todas as suas capacidades dado ainda não existir documentação, fiquei a desejar que tivesse algumas funcionalidades como:

  • Podermos definir no ficheiro stdl o nome que queremos dar ao teste a ser criado. Nomes como postCodeChecker1, postCodeChecker2, …, são pouco uteís quando o build falha e queremos rápidamente saber o que se está a passar. Um bom nome num teste é essencial.
  • No caso das strings, poder definir uma expressão regular para ser testada. O STDL poderia então criar uma bateria de testes, gerando strings a partir da expressão regular.
  • Dar uma lista de Getters/Setters que o STDL testasse automáticamente, colocando um valor random e depois vendo que o valor retirado seria igual.

Para uma release 0.1… extremamente recomendável!

Para conseguir achar um problema com uma rede de computadores na empresa em que estou a trabalhar, criei um pequeno programa que achei que deveria partilhar.

Apesar de ser extremamente pequeno, o programa mostra como usar um TcpListener para aceitar ligações de um cliente, respondendo com alguma coisa:

listener = new TcpListener(IPAddress.Any, port);
listener.Start();
client = listener.AcceptTcpClient();
Byte[] bytes = Encoding.ASCII.GetBytes(reply);
client.GetStream().Write(bytes, 0, bytes.Length);
client.GetStream().Close();
client.Close();

Num ambiente em que criamos threads e temos o cuidado de usar o Invoke para actualizar a interface pois, sem isso, iriamos obter o erro:

Cross-thread operation not valid: Control $name accessed from a thread other than the thread it was created on.

private delegate void AnonymousDelegate();
 
 
new Thread(MainLoop).Start();
 
 
Invoke(new AnonymousDelegate(delegate
{
    if (!logTxt.IsDisposed)
    {
        logTxt.Text += message + Environment.NewLine;
    } 
}));

E claro, com um pequeno cliente que se liga ao servidor e lê uma string do socket:

TcpClient client = new TcpClient();
client.Connect(addressTxt.Text, port);
NetworkStream stream = client.GetStream();
StreamReader reader = new StreamReader(stream);
string result = reader.ReadToEnd();

Podem fazer download do código aqui (façam “guardar como” e depois mudem a extensão de pdf para zip)

Se quiseres consumir web services que estejam alojados num servidor https programaticamente em c# basta, antes da chamada ao serviço, alterar a politica de certificados usada pelo método:

System.Net.ServicePointManager.CertificatePolicy = new MyCertificatePolicy();

Isto faz com que a chamada ao serviço use a nossa classe, MyCertificatePolicy, para saber se aceita os certificados usados pelo servidor. Assim, se quiseres que o programa aceite todos os certificados, mesmo que tenham problemas de segurança (mal assinados, etc) basta definir a classe como:

public class MyCertificatePolicy : System.Net.ICertificatePolicy

{

    public bool CheckValidationResult(System.Net.ServicePoint sp,

        System.Security.Cryptography.X509Certificates.X509Certificate cert,

        System.Net.WebRequest req,

        int problem)

        {

            return true;

        }

}

Simples.

Update:

A property CertificatePolicy está marcada como obsoleta. A forma correcta é passar um delegado para o ServerCertificateValidationCallback. No meu caso, fiz um delegado anónimo:

ServicePointManager.ServerCertificateValidationCallback += delegate
    {
        return true;
    };

 

mario

Retirado daqui. Se alguém souber quem é o artista para dar os devidos créditos..

Se isto funciona:

IList<FileInfo> list = new List<FileInfo>();
IList<List<FileInfo>> listOfList = new List<List<FileInfo>>();

Porque é que isto tem de dar erro?

IList<IList<FileInfo>> listOfGenericList = new List<List<FileInfo>>();
Cannot implicitly convert type ‘System.Collections.Generic.List<System.Collections.Generic.List<FileInfo>>’ to ‘System.Collections.Generic.IList<System.Collections.Generic.IList<FileInfo>>’. An explicit conversion exists (are you missing a cast?)

Apesar de saber que isto é algo que acontece por desenho da linguagem… Não percebo porque raio é que o cast não funciona, nem mesmo quando o fazemos explicitamente. Será que alguém me pode explicar como se eu fosse uma criança de 5 anos?

Li no blog do Hugo Ribeiro sobre uma nova funcionalidade do .net 3.0, chamada ‘Automatic Properties’. Isto permite-nos definir getters e setters numa classe sem termos de escrever a implementação do método, ficando tal e qual como quando definimos as propriedades numa interface:

public class MusicFileDetails
{
    public string Group
    {
        get;
        set;
    }
        
    public string Album
    {
        get;
        set;
    }
 
    public string Title
    {
        get;
        set;
    }
 
    public FileInfo FileInfo
    {
        get;
        set;
    }
}

Neste caso, o compilador é simpático o suficiente para nos gerar os membros privados da classe e as implementações dos métodos… Genial!

 

imagePara quem anda à procura de uma forma de integrar SVN com o Visual Studio 2008, talvez seja boa ideia experimentar o AnkhSVN. É uma ferramenta grátis que  integra perfeitamente no Visual Studio, com explorador de repositório, indicações visuais do estado do ficheiro no solution explorer no qual podemos, usando o menu de contexto, fazer commit, etc. Obviamente que não é substituto do essencial TortoiseSVN.

Para soluções de hosting grátis para projectos usando SVN, recomendo o Assembla. Não obriga a que os projectos sejam open-source, oferecendo ainda imensas ferramentas para ajudar a organizar a (possivelmente espalhada pelo mundo) equipa de desenvolvimento.

 

As ‘custom sections’ dos ficheiros de configuração permitem-nos configurar as configurações dos nossos programas, adicionando ao ficheiro de configuração (ex app.config) elementos feitos à medida da nossa aplicação.

Numa aplicação que estou a desenvolver, quero que seja possível mudar os forms sem ter de recompilar a aplicação, para que, num outro passo, possa usar o mesmo código para correr o programa em mobile/linux/mac/etc. Assim desenhei o seguinte ficheiro de configuração:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="applicationForms" type="UI.Factory.Configuration.FormsSection, UI.Factory" />
  </configSections>
  <appSettings>
  </appSettings>
  <applicationForms>
    <forms>
      <add name="ISearchingForm" type="UI.WinForms.SearchingForm, UI.WinForms" />
      <add name="IWizardWelcomeForm" type="UI.WinForms.FirstRun.StartForm, UI.WinForms" />
      <add name="IWizardChooseDirectoryForm" type="UI.WinForms.FirstRun.ChooseDirectoryForm, UI.WinForms" />
      <add name="IWizardThankYouForm" type="UI.WinForms.FirstRun.ThankYouForm, UI.WinForms" />
    </forms>
  </applicationForms>
</configuration>

Este ficheiro contém dois elementos de interesse. O primeiro define um nome de uma secção nova, configurando também a classe que vai conseguir ler essa nova secção:

<configSections>
    <section name="applicationForms" type="UI.Factory.Configuration.FormsSection, UI.Factory" />
</configSections>

Depois, o elemento ‘applicationForms’, que é definido pela classe ‘FormsSection’:

public class FormsSection : ConfigurationSection
{
    [ConfigurationProperty("forms")]
    public FormsElementCollection Forms
    {
        get { return (FormsElementCollection)base["forms"]; }
        set { base["forms"] = value; }
    }
}
 
public class FormsElementCollection : ConfigurationElementCollection
{
    protected override ConfigurationElement CreateNewElement()
    {
        return new FormElement();
    }
 
    protected override object GetElementKey(ConfigurationElement element)
    {
        return ((FormElement)element).Name;
    }
 
    public void Add(FormElement element)
    {
        BaseAdd(element);
    }
}
 
public class FormElement : ConfigurationElement
{
    [ConfigurationProperty("name", IsKey = true, IsRequired = true)]
    public string Name
    {
        get { return (string)base["name"]; }
        set { base["name"] = value; }
    }
 
    [ConfigurationProperty("type", IsRequired = true)]
    public string Type
    {
        get { return (string)base["type"]; }
        set { base["type"] = value; }
    }
}

A secção pode ser acedida no código assim:

FormsSection section = (FormsSection)ConfigurationManager.GetSection("applicationForms");

Qualquer um dos atributos ou sub-elementos do elemento ‘applicationsForms’ pode ser configurado de acordo com a especificidade do problema, podendo mesmo ser possível ter elementos recursivos ou, um pouco mais interessante, com aptidão para lerem atributos não esperados:

public class FormElement : ConfigurationElement
{
    ...
 
    protected override bool OnDeserializeUnrecognizedAttribute(string name, string value)
    {
        ConfigurationProperty property = new ConfigurationProperty(name, typeof(string));
        Properties.Add(property);
        SetPropertyValue(property, value, false);
        return true;
    }
}
View João Caxaria's profile on LinkedIn