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.