Objetivo
Nesta parte, veremos como configurar todo o ambiente para executar os testes apresentados na primeira parte.
Configurando banco de dados
Para realizarmos os testes necessários, será necessário primeiro criar um ambiente. O primeiro passo é criar um novo database no SQL Server e as tabelas necessárias:
create table ServiceTable ( ServiceTableID int not null, DescServiceTable varchar(200) not null, Value float not null, CreationDate datetime not null, StringField1 varchar(200), StringField2 varchar(200), constraint PK_ServiceTable primary key (ServiceTableID) ) create table ClientTable ( ClientTableID int not null, DescClientTable varchar(200) not null, Value float not null, CreationDate datetime not null, StringField1 varchar(200), StringField2 varchar(200), constraint PK_ClientTable primary key (ClientTableID) )
Usaremos duas tabelas bem simples, somente para ilustrar o problema. Nosso objetivo não é exercitar conceitos de modelagem de banco de dados.
Esse database e tabelas devem ser criados tanto no cliente quanto no servidor.
Criando uma massa de dados
Para simplificar todo o controle e execução dos programas utilizados nestes exemplos, utilizei o MSBuild, devido à grande quantidade de parâmetros e configurações necessárias.
Para criar a massa de dados, criei uma task MSBuild “DataGeneration”:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data.SqlClient; using System.Data; using Microsoft.Build.Utilities; using Microsoft.Build.Framework; namespace IntegrationTests.TestClasses.Util { public class DataGeneration : Task { [Required] public string ConnString { get; set; } public override bool Execute() { SqlConnection conn = new SqlConnection(ConnString); conn.Open(); SqlCommand cmd = new SqlCommand("insert into ServiceTable (ServiceTableID, DescServiceTable, Value, CreationDate, StringField1, StringField2)" + "values (@ServiceTableID, @DescServiceTable, @Value, @CreationDate, @StringField1, @StringField2)", conn); using (conn) { SqlParameter p1 = cmd.Parameters.Add("@ServiceTableID", SqlDbType.Int); SqlParameter p2 = cmd.Parameters.Add("@DescServiceTable", SqlDbType.VarChar, 200); SqlParameter p3 = cmd.Parameters.Add("@Value", SqlDbType.Float); SqlParameter p4 = cmd.Parameters.Add("@CreationDate", SqlDbType.DateTime); SqlParameter p5 = cmd.Parameters.Add("@StringField1", SqlDbType.VarChar, 200); SqlParameter p6 = cmd.Parameters.Add("@StringField2", SqlDbType.VarChar, 200); Random r = new Random(); int count = 0; for (int i = 1; i <= 2000000; i++) { p1.Value = i; p2.Value = "Server Value " + i.ToString(); p3.Value = r.Next(); p4.Value = DateTime.Now; p5.Value = "Useless Field 1: " + r.Next().ToString(); p6.Value = "Useless Field 1: " + r.Next().ToString(); cmd.ExecuteNonQuery(); count++; if (count >= 1000) { count = 0; Log.LogMessage("Generated " + i.ToString() + "/2000000 records"); } } return true; } } } }
O objetivo aqui é simples, gerar 2 milhões de registros para realizar o teste. Esse procedimento precisa ser executado somente na máquina servidora. O batch “generatedata” executa este procedimento. Não esqueça de atualizar a propriedade “ConnString”. Pode ser tanto via parâmetro no arquivo .bat ou dentro do arquivo IntegrationTests.build.
Caso tenha alguma dúvida sobre MSBuild: MSBuild in a Nutshell.
Configurando o serviço WCF
O serviço precisa ser configurado apenas na máquina servidora. Entre na console de administração do IIS (inetmgr). Dentro do seu Default Web Site, crie um novo aplicativo.
- Alias: integrationtests
- Pool de aplicativos: DefaultAppPool (v.4.0, Integrated)
- Caminho físico: Apontar para a raiz do projeto IntegrationTests.WCFServiceApp
- Habilitar endpoint net.tcp: Entrar em configurações avançadas da nova aplicação, na propriedade “protocolos habilitados”, preencher com http,net.tcp
As configurações do serviço WCF modificadas. Foram criados os bindings net.tcp e http e a segurança foi desabilitada para evitar problemas entre as duas máquinas. A configuração proposta foi:
<system.serviceModel> <bindings> <netTcpBinding> <binding name="nettcp1" closeTimeout="00:10:00" maxReceivedMessageSize="65536000" transferMode="Buffered"> <security mode="None"> <transport clientCredentialType="None" protectionLevel="None" /> <message clientCredentialType="None" /> </security> </binding> </netTcpBinding> </bindings> <services> <service name="IntegrationTests.WCFServiceApp.IntegrationTestsService"> <endpoint binding="netTcpBinding" bindingConfiguration="nettcp1" name="nettcp1" contract="IntegrationTests.WCFServiceApp.IIntegrationTestsService" /> <endpoint binding="basicHttpBinding" bindingConfiguration="" name="basicHTTP" contract="IntegrationTests.WCFServiceApp.IIntegrationTestsService" /> </service> </services> <behaviors> <serviceBehaviors> <behavior> <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment --> <serviceMetadata httpGetEnabled="true"/> <!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information --> <serviceDebug includeExceptionDetailInFaults="false"/> </behavior> </serviceBehaviors> </behaviors> <serviceHostingEnvironment multipleSiteBindingsEnabled="true" /> </system.serviceModel>
Nada impede dessas configurações serem modificadas para atender requisitos do seu ambiente.
Outro ponto importante é corrigir a conexão de banco de dados na web.config do servidor:
<appSettings> <add key="ConnString" value=""/> </appSettings>
Propriedades do MSBuild
O script do MSBuild criado funciona tanto para o servidor quanto o cliente, mas algumas propriedades precisam ser modificadas para refletir as configurações do cliente e do servidor:
- Máquina servidor
- ConnString: Configurar apontando para a base criada no início do post, na máquina servidora
- InputDir: Local onde o servidor vai ficar ouvindo os arquivos de requisição enviados pelo cliente. Configurar um diretório temporário local (Ex.: C:\Shared
- OutputDir: Local onde o servidor vai jogar os arquivos para o cliente. Configurar para um diretório temporário local (Ex.: C:\Shared)
- Máquina cliente
- ConnString: Configurar apontando para a base criada no início do post, na máquina cliente
- OutputDir: Local onde o cliente vai jogar os arquivos de requisição para o servidor. Configurar para um diretório de rede mapeado para o InputDir da máquina servidora.
- InputDir: Local onde o cliente vai esperar a resposta do servidor. Configurar para um diretório de rede mapeado para o OutputDir da máquina servidora.
- WebServiceUri: http://server-ip-address/integrationtests/IntegrationTestsService.svc
- NetTcpUri: net.tcp://server-ip-address/integrationtests/IntegrationTestsService.svc
Executando testes
Antes de executar, sempre execute o bat runservers (internamente target RunServers) na máquina server antes, senão obviamente o cliente vai ficar esperando um arquivo que nunca vai chegar. Implementei o lado servidor também como uma task MSBuild para o caso dos arquivos. É totalmente inapropriada a implementação que eu fiz, mas pra esse propósito (testar os tempos), funciona bem.
A target MSBuild RunClientTests executa os testes (file transfer, web service linha a linha e web service em lotes) num cenário de 20.000 registros. É bom para saber se está tudo funcionando.
A target MSBuild RunAllClientTestes executa todos os testes, 5 vezes, em cenários de 20.000, 50.000, 500.000 e 1 milhão de registros. É óbvio que o RunAllClientTests vai demorar bem mais, porém, o objetivo dele é tirar métricas mais precisas em execuções constantes dos testes para obter resultados médios. Por questões dos caches de banco de dados, consumo memória, etc., as execuções podem variar.
Código fonte
O código fonte está disponível no git hub: https://github.com/ericlemes/IntegrationTests.
Conclusão
Este post tem objetivo somente fornecer o código fonte e explicar todo o setup de ambiente. Nos posts posteriores, vou explicar em mais detalhes cada uma dos testes realizados para que o método fique mais claro.