tag:blogger.com,1999:blog-49462068394025602852024-03-05T02:15:09.567-03:00Fábio UechiB-side projects and thoughtsAnonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.comBlogger38125tag:blogger.com,1999:blog-4946206839402560285.post-12426054132893613862014-06-17T15:24:00.002-03:002014-06-17T15:24:34.299-03:00Pig Design Patterns: free book giveaway<span style="font-size: large;"><b>Book giveaway</b></span><br /> Hold a chance to win free copy of <a href="http://bit.ly/1iuZCUL">Pig Design Patterns</a>, just by commenting and sharing! For the contest we have 3 e-copies of <a href="http://bit.ly/1iuZCUL">Pig Design Patterns</a>, to be given away to 3 lucky winners.<br /><br /><span style="font-size: large;"><b>How you can win</b></span><br /> To win your copy of this book, all you need to do is come up with a comment below highlighting the reason <b>“why you would like to win this book”</b> and share a post about this book with this unique short link on your social media profiles: <a href="http://bit.ly/1iuZCUL">bit.ly/1iuZCUL</a><br /><br /> Don’t forget to drop your email address and the permalink of your social media post in your comment.<br /><br /><b> Note: Social Media post must contain this unique short link: <a href="http://bit.ly/1iuZCUL">bit.ly/1iuZCUL</a></b><br /><br /><b><span style="font-size: large;">Duration of the contest & selection of winners:</span></b><br /> The contest is valid till <b>30th June 2014</b>, and is open to everyone. Winners will be selected on the basis of their comment posted.<div style="margin-bottom: 0.07in; margin-top: 0.07in; orphans: 2; widows: 2;">
</div>
<div align="CENTER" class="western" style="margin-bottom: 0in;">
</div>
<br />
<div class="western" style="margin-bottom: 0in;">
<br />
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://www.packtpub.com/sites/default/files/5556OS.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://www.packtpub.com/sites/default/files/5556OS.jpg" height="320" width="259" /></a></div>
<div class="western" style="margin-bottom: 0in;">
<br /></div>
Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-70196627614689963702014-03-05T23:38:00.000-03:002014-03-06T22:13:23.366-03:00Hooking Bitbucket up with Jenkins parameterized jobsBitbucket repositories allow us to setup hooks which notify/trigger Jenkins' jobs about newly pushed code.
The process to create such a hook is documented <a href="https://confluence.atlassian.com/display/BITBUCKET/Jenkins+hook+management">here</a>.
However, it doesn't mention how to integrate with Jenkins parameterized jobs.<br />
After reading the <a href="https://wiki.jenkins-ci.org/display/JENKINS/Parameterized+Build#ParameterizedBuild-Launchingabuildwithparameters">Jenkins documentation</a> and a few trial and error I managed integrate Bitbucket and Jenkins parameterized jobs.<br />
The hook management form presents 4 fields:<br />
<br />
<ol>
<li><b>Endpoint:</b> Here, you’ll need to set your Jenkins URL in the following format — http://<b>username</b>:<b>apitoken</b>@<b>your.jenkins.url</b>/job/<b>your.job.name</b>/buildWithParameters </li>
<li><b>Module name:</b> (Optional)</li>
<li><b>Project name:</b> (Leave Empty)</li>
<li><b>Token:</b> It’s the authentication token you defined in your Jenkins job settings </li>
</ol>
<br />
The gotcha is leaving the <b>project name</b> field blank and include it in the endpoint URL appended with <b>buildWithParameters</b>.<br />
<br />
<br />Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-17199829616240615002014-03-02T09:32:00.001-03:002014-03-02T09:32:40.666-03:00Wakeboarding day at Naga Cable Park<iframe allowfullscreen="" frameborder="0" height="270" src="//www.youtube.com/embed/9rfZASIOyXE" width="480"></iframe>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-70006199241621917622013-07-27T19:24:00.001-03:002013-07-30T23:41:31.292-03:00Dynamically generating Zip files using Google Cloud Storage Client Library for AppengineLet's say you have 50 objects (15Mb each) stored in Google Cloud Storage. Now, you need to create a zip archive containing all of them and store the resultant file back into GCS. How can we achieve that from within an Appengine java application?<br />
Well, after some research, I wrote the method below using <a href="https://developers.google.com/appengine/docs/java/googlecloudstorageclient/" target="_blank">Google Cloud Storage Client Library</a> which does exactly that. Just don't forget to grant the appropriate permissions to your appengine service account so that it can read and write the objects.<br />
<br />
<br />
<pre class="brush: java">public static void zipFiles(final GcsFilename targetZipFile,
final GcsFilename... filesToZip) throws IOException {
Preconditions.checkArgument(targetZipFile != null);
Preconditions.checkArgument(filesToZip != null);
Preconditions.checkArgument(filesToZip.length > 0);
final int fetchSize = 4 * 1024 * 1024;
final int readSize = 2 * 1024 * 1024;
GcsOutputChannel outputChannel = null;
ZipOutputStream zip = null;
try {
final GcsFileOptions options = new GcsFileOptions.Builder()
.mimeType(MediaType.ZIP.toString()).build();
outputChannel = GCS_SERVICE.createOrReplace(targetZipFile, options);
zip = new ZipOutputStream(Channels.newOutputStream(outputChannel));
GcsInputChannel readChannel = null;
for (final GcsFilename file : filesToZip) {
try {
final GcsFileMetadata meta = GCS_SERVICE.getMetadata(file);
if (meta == null) {
LOGGER.warning(file.toString()
+ " NOT FOUND. Skipping.");
continue;
}
final ZipEntry entry = new ZipEntry(file.getObjectName());
zip.putNextEntry(entry);
readChannel = GCS_SERVICE.openPrefetchingReadChannel(file,
0, fetchSize);
final ByteBuffer buffer = ByteBuffer.allocate(readSize);
int bytesRead = 0;
while (bytesRead >= 0) {
bytesRead = readChannel.read(buffer);
buffer.flip();
zip.write(buffer.array(), buffer.position(),
buffer.limit());
buffer.rewind();
buffer.limit(buffer.capacity());
}
} finally {
zip.closeEntry();
readChannel.close();
}
}
} finally {
zip.flush();
zip.close();
outputChannel.close();
}
}</pre>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-89030175986517354632013-07-06T09:27:00.001-03:002013-07-08T02:12:45.099-03:00"Smart" Appengine Devserver restarts for faster development lifecycleWe just started a new AppEngine Java project using the <span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><b><a href="https://code.google.com/p/appengine-maven-plugin/" target="_blank">appengine-maven-plugin</a></b></span> and the Eclipse IDE (Juno). We are using the <b><span style="font-family: Courier New, Courier, monospace; font-size: x-small;">appengine:devserver</span></b> goal to start the devserver. It basically builds the entire project (compile, test and package) and after that launches the devserver pointing it to the generated webapp directory - which by default is: <b><span style="font-family: Courier New, Courier, monospace; font-size: x-small;">${project.build.directory}/${project.build.finalName}</span></b><br />
<h2>
<br /></h2>
<h2>
The Problem</h2>
Every edit made in the source directory is not recognized unless the server is restarted - <b>neither static content nor </b><b>newly compiled classes</b>. Which is obvious since the devserver is monitoring only the target directory.<br />
It's a very "bureaucratic" and nonproductive development environment - we need to stop and start the server even for a single CSS line change.<br />
<h2>
<br /></h2>
<h2>
The Dream</h2>
Achieve the same productivity level we have when working with dynamic languages based development environment (i.e. Python). Just hit F5 in the browser to see the changes in static files and automatic server reload every time a Java class or descriptor file is compiled/changed.<br />
<h2>
<br /></h2>
<h2>
The Solution </h2>
Using a little Ant-foo, we were able to create a target which synchronizes both directories.<br />
Thie snippet below uses the <a href="http://ant.apache.org/manual/Tasks/sync.html" target="_blank">sync Ant task</a> to perform the static content synchronization (lines 1-9). Notice that everything inside <b><span style="font-family: Courier New, Courier, monospace; font-size: x-small;">src.webapp.dir</span></b> is sync'ed except for the 3 directories declared in the <span style="font-family: Courier New, Courier, monospace; font-size: x-small;"><b>preserveintarget</b></span> element. We had to exclude them from the synchronization process because since they only exist in the target directory they'd be deleted otherwise. And finally a second sync to synchronize the compiled classes (lines 11-13).<br />
<br />
<pre class="brush: xml"><sync verbose="true" todir="${target.webapp.dir}" includeEmptyDirs="true">
<fileset dir="${src.webapp.dir}" />
<preserveintarget>
<!-- Ignore the directories below -->
<include name="WEB-INF/lib/**" />
<include name="WEB-INF/classes/**" />
<include name="WEB-INF/appengine-generated/**" />
</preserveintarget>
</sync>
<sync verbose="true" todir="${target.webapp.dir}/WEB-INF/classes">
<fileset dir="${basedir}/target/classes" />
</sync>
</pre>
<br />
Then, we attached it to an Eclipse builder, which is triggered every time a change is made in the project ("Build automatically" flag enabled).<br />
The same behavior can be achieved by creating a <a href="https://gist.github.com/fabito/5938537#file-maven-profile" target="_blank">special maven profile</a> and using a combination of the m2e lifecycle mappings and the maven antrun plugin.<br />
Something like this:<br />
<br />
<pre class="brush: xml"><profile>
<id>m2e</id>
<activation>
<property>
<name>m2e.version</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<property name="target.webapp.dir" value="${project.build.directory}/${project.build.finalName}" />
<property name="src.webapp.dir" value="${basedir}/src/main/webapp" />
<sync verbose="true" todir="${target.webapp.dir}" includeEmptyDirs="true">
<fileset dir="${src.webapp.dir}" />
<preserveintarget>
<include name="WEB-INF/lib/**" />
<include name="WEB-INF/classes/**" />
<include name="WEB-INF/appengine-generated/**" />
</preserveintarget>
</sync>
<sync verbose="true" todir="${target.webapp.dir}/WEB-INF/classes">
<fileset dir="${basedir}/target/classes" />
</sync>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!-- This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<versionRange>[1.6,)</versionRange>
<goals>
<goal>run</goal>
</goals>
</pluginExecutionFilter>
<action>
<execute>
<runOnIncremental>true</runOnIncremental>
</execute>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile></pre>
<br />Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0Jaguariúna - São Paulo, Brazil-22.7057713 -46.986242899999979-22.940183299999998 -47.308966399999981 -22.4713593 -46.663519399999977tag:blogger.com,1999:blog-4946206839402560285.post-80234938045195008402013-03-06T15:37:00.000-03:002013-03-06T15:45:59.532-03:00How to reference the current jmeter script base path?I use lots of javascript in my Jmeter's test plans. I usually keep the code in the "Script" text area, either in BSF or JSR223 assertions and/or processors.<br />
Sometimes however, I'd rather keep the scripts in a separate (external) file to ease maintenance.<br />
Differently from the "<a href="http://jmeter.apache.org/usermanual/component_reference.html#CSV_Data_Set_Config" target="_blank">CSV Data Set Config</a>" element, the "<a href="http://jmeter.apache.org/usermanual/component_reference.html#JSR223_Assertion" target="_blank">JSR223 Assertion</a>" "Script File" property <b>does not</b> use the current running script directory as the base path, it uses the <b>user.dir</b> system property instead.<br />
<br />
The problem is that I normally organize my test assets following this directory layout:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;"><b>/my_project/my_test.jmx</b> <span style="font-size: x-small;">$jmx files</span></span><br />
<span style="font-family: Courier New, Courier, monospace;"><b>/my_project/js/script.js</b> <span style="font-size: x-small;">$script files</span></span><br />
<span style="font-family: Courier New, Courier, monospace;"><b>/my_project/data/my_test.csv</b> <span style="font-size: x-small;">$csv files</span></span><br />
<div>
<br /></div>
In order to reference scripts with paths relative to the current JMX file I use the <a href="http://jmeter.apache.org/api/org/apache/jmeter/services/FileServer.html" target="_blank">FileServer</a> class, in conjunction with the <b>__javaScript</b> function, like this:<br />
<div>
<br /></div>
${__javaScript(org.apache.jmeter.services.FileServer.getFileServer().getBaseDir())}/js/script.js
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFoFSDS5tU_96BcKwcrwnNc1Irvddp6U5Lz8DYhH8CFQtdjAlBdANNCkyki5uefKLSglyqWOTwNqAwmXc0b89QnH9ACwwx9ccttxkBvWn9S1mLjdz1GdaABxUmRAnxRR_tLFzCd2mSdTI/s1600/jmeter_post_path.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFoFSDS5tU_96BcKwcrwnNc1Irvddp6U5Lz8DYhH8CFQtdjAlBdANNCkyki5uefKLSglyqWOTwNqAwmXc0b89QnH9ACwwx9ccttxkBvWn9S1mLjdz1GdaABxUmRAnxRR_tLFzCd2mSdTI/s640/jmeter_post_path.png" width="640" /></a></div>
Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-17017820718250975842012-11-10T23:23:00.002-02:002012-11-13T13:17:33.094-02:00Expondo uma API para busca de CEPs com Google Cloud EndpointsNo Google IO 2012 foi lançado (por enquanto só para <i>trusted testers</i>) o <a href="http://endpoints-trusted-tester.appspot.com/" target="_blank">Google Cloud Endpoints</a>.<br />
É um novo serviço do GAE que facilita (e muito) a publicação de APIs <a href="http://en.wikipedia.org/wiki/Representational_state_transfer" rel="nofollow" target="_blank">RESTful</a> ou <a href="http://en.wikipedia.org/wiki/JSON-RPC" rel="nofollow" target="_blank">JSON RPC</a>.<br />
Na verdade as facilidades vão muito além do servidor. Foi incorporado no <a href="https://developers.google.com/eclipse/" rel="nofollow" target="_blank">GPE (Google Plugin para Eclipse)</a> um "gerador" que dada uma API, gera o código necessário para acessá-la de clientes: <b>Android (Java) e/ou iOS (objective C)</b>. Além disso também é possível acessar os serviços via javascript usando o <a href="http://code.google.com/p/google-api-javascript-client/wiki/GettingStarted" rel="nofollow" target="_blank">Google APIs Client Library for Javascript</a> (mesma biblioteca utilizada para utilização das APIs Google).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4_k00AaIO1aBCSmPqseAu6JjmD1G2nyezOIqmCN1LGromGObuJkNFkkEZajkyxRJyxL7AWoTtm0A09CYkwtG1xwrUYIPyAqoTxked1uiSVRnHs8HlG3HJC-orQ8nHws197MYesF4lQrI/s1600/Desenho+sem+t%C3%ADtulo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="135" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4_k00AaIO1aBCSmPqseAu6JjmD1G2nyezOIqmCN1LGromGObuJkNFkkEZajkyxRJyxL7AWoTtm0A09CYkwtG1xwrUYIPyAqoTxked1uiSVRnHs8HlG3HJC-orQ8nHws197MYesF4lQrI/s400/Desenho+sem+t%C3%ADtulo.png" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
Para testar esse novo serviço, me inscrevi no programa de <i>trusted testers</i> e <a href="http://busca-cep.appspot.com/" rel="nofollow" target="_blank">criei uma aplicação</a> que expõe uma API REST para busca de CEPs - usando <a href="http://fabiouechi.blogspot.com.br/2012/07/api-java-para-busca-de-ceps-no-site-dos.html" target="_blank">uma biblioteca Java para busca de CEPs</a> que criei um tempo atrás.<br />
A aplicação possui apenas uma única classe: CepEndpoint. O gist abaixo mostra como o código é simples e como algumas simples anotações são suficientes para publicar um endpoint composto por alguns serviços.<br />
<div class="gistLoad" data-id="4053302" id="gist-4053302">
Loading ....</div>
<br />
Para testar a API publicada pode-se usar o <a href="https://developers.google.com/apis-explorer/?base=https://busca-cep.appspot.com/_ah/api#p/buscacep/v1/">Google APIs Explorer</a> ou o "<a href="http://busca-cep.appspot.com/" rel="nofollow">clientzinho web</a>" que criei que invoca esse mesmo endpoint usando a API javascript.<br />
<div class="gistLoad" data-id="4053310" id="gist-4053310">
Loading ....</div>
<br />
Se derem uma olhada no código fonte, verão que aproveitei também pra dar uma treinada no desenvolvimento de aplicações HTML5 usando <a href="http://angularjs.org/" target="_blank">Angular JS</a> e <a href="http://twitter.github.com/bootstrap/" target="_blank">Bootstrap</a>.<br />
<div>
<br /></div>
Use o link abaixo para acessar a aplicação:<br />
<a href="http://busca-cep.appspot.com/" rel="nofollow">http://busca-cep.appspot.com/</a>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>
<script src="https://raw.github.com/moski/gist-Blogger/master/public/gistLoader.js" type="text/javascript"></script>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-13050005815154394812012-11-10T00:58:00.000-02:002012-11-29T16:15:05.233-02:00Setting up Jenkins on EC2 using AWS CloudFormation (including nginx as a reverse proxy)I lost count on how many times I had to setup a CI server. Being a big fan of the <a href="http://www.infoq.com/presentations/infrastructure-as-code">concept of "Infrastructure as Code"</a> as I am, I promised myself that the next time I'd do it differently (I needed to have it automated!).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-Vmi71OxNxTc/TzvZH9OTjaI/AAAAAAAAAYM/DtN_4Fz8Kn0/s1600/jenkinsLogo1.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="200" src="http://1.bp.blogspot.com/-Vmi71OxNxTc/TzvZH9OTjaI/AAAAAAAAAYM/DtN_4Fz8Kn0/s200/jenkinsLogo1.png" width="144" /></a></div>
Well, the day has come.The stack (using Cloudformation terms) I decided to create is composed by a basic Jenkins installation (standalone + winstone) running as a daemon on an Amazon Linux based EC2 intance. It also includes Nginx as a reverse proxy and a dedicated volume for storing the JENKINS_HOME files and other artifacts.<br />
<br />
Obviously that a much easier, simpler and (I think) even cheaper alternative would be using some <a href="http://www.cloudbees.com/dev.cb" target="_blank">PaaS offering (Jenkins as a Service)</a>.<br />
<br />
However, I ended up using <a href="http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/cloudformation-waitcondition-article.html" target="_blank">AWS Cloudformation</a> to automate the provisioning of the AWS Resources (IAM User, SecurityGroup, EBS Volumes, EC2 instance) and its <a href="https://help.ubuntu.com/community/CloudInit" target="_blank">cloud-init</a> features to install and configure the necessary packages.<br />
<br />
External repository addition, yum based package installations, configuration files adjustments, EBS volumes setup, etc. Most of the installation and configuration logic is in a shell script embedded in the UserData property.<br />
<br />
The <a href="https://gist.github.com/4048829" target="_blank">resulting template</a> is right below, the input parameters are: <b>Instance type</b>, JENKINS_HOME <b>volume size</b> in gigabytes and the EC2<b> KeyName</b>. The output is the Jenkins server URL! <br />
<br />
<div class="gistLoad" data-id="4048829" id="gist-4048829">
Loading ....</div><br />
<br />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>
<script src="https://raw.github.com/moski/gist-Blogger/master/public/gistLoader.js" type="text/javascript"></script>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-42430292421998717492012-07-06T00:38:00.003-03:002012-07-07T22:56:28.935-03:00API Java para busca de CEPs (fachada para o serviços dos Correios)<a href="http://www.correios.com.br/images/cep/busca.gif" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="87" src="http://www.correios.com.br/images/cep/busca.gif" width="200" /></a>O site dos Correios disponibiliza gratuitamente um ótimo serviço para <a href="http://www.buscacep.correios.com.br/servicos/dnec/menuAction.do?Metodo=menuEndereco" rel="nofollow" target="_blank">busca online de CEPs</a>.<br />
Porém, essa é a única forma de acessá-lo: via página HTML.<br />
Não existe, nem é exposta, nenhuma API que facilite a integração desse serviço com outras aplicações.<br />
<br />
Para possibilitar esse tipo de integração, criei o <b><span style="font-family: 'Courier New', Courier, monospace;"><a href="https://github.com/fabito/busca-cep-java-client" target="_blank">busca-cep-java-client</a></span></b> que nada mais é que um componente Java (jar) cuja API abstrai a complexidade de:<br />
<ol>
<li><span style="background-color: white;">Fazer a requisição HTTP (GET) passando os parâmetros necessários e;</span></li>
<li><span style="background-color: white;">Processar a resposta - extraindo os dados de CEP do HTML retornado. </span></li>
</ol>
<div>
Internamente utilizo a biblioteca <a href="http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CFQQFjAA&url=http%3A%2F%2Fhtmlunit.sourceforge.net%2F&ei=61n2T-6jBYee8gT3yP3IBg&usg=AFQjCNHPMijLD6lsJhr_pTp_Ysz3XN3dHA" target="_blank">HtmlUnit</a> para auxiliar no <a href="http://en.wikipedia.org/wiki/Web_scraping" target="_blank">Web Scraping</a>. Basicamente o que eu faço é simular um usuário que entra na página de busca, preenche e posta o form. Depois, já na tela de resultados, percorro o HTML retornado em busca da tabela com os dados de CEP retornados.<br />
<br />
O trecho de código abaixo mostra o quão simples é a utilização da API:<br />
<br />
<pre class="brush: java">// Obtém uma instância de CEPService
CEPService buscaCEP = CEPServiceFactory.getCEPService();
// Obtém um CEP pelo número
CEP cep = buscaCEP.obtemPorNumeroCEP(13084440);
// Obtém todos os CEPs que contém "Flordalisa" no logradouro
List<CEP> ceps = buscaCEP.obtemPorEndereco("Flordalisa");</pre>
<br />
Para mais detalhes em como utilizar esse componente, sugiro dar uma olhada nos <a href="https://github.com/fabito/busca-cep-java-client/blob/master/src/test/java/org/talesolutions/cep/BuscaCepTest.java" target="_blank">testes unitários</a>.<br />
Todo o código fonte também está disponível no <a href="https://github.com/fabito/busca-cep-java-client" target="_blank">Github</a>.<br />
<span style="background-color: white;">Além disso, publiquei a versão 1.1 do componente num repositório Maven (também no Github).</span><br />
Para quem usa essa ferramenta basta alterar o <span style="font-family: 'Courier New', Courier, monospace;">pom.xml</span> para incluir o repositório:<br />
<br />
<pre class="brush: xml"><repository>
<id>Talesolutions</id>
<url>https://raw.github.com/fabito/talesolutions-mvn-repo/master/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</pre>
<br />
E adicionar essa dependência:<br />
<br />
<pre class="brush: xml"> <dependency>
<groupId>org.talesolutions</groupId>
<artifactId>busca-cep-client</artifactId>
<version>1.1</version>
</dependency>
</pre>
<br />
<h3>
Considerações Finais</h3>
O <b><span style="font-family: 'Courier New', Courier, monospace;">busca-cep-client</span></b> ajuda a integrar funcionalidades de busca de CEPs à aplicações Java. Principalmente porque dispensa a criação de uma base de CEPs local e como é só uma <i>façade</i> para o serviço oficial dos Correios temos a "garantia" de estarmos sempre acessando dados atuais e corretos.<br />
A principal desvantagem é a dependência da disponibilidade e formato desse serviço - alto acoplamento. Se o site dos Correios sair do ar sua aplicação certamente será impactada. Além disso, como os dados de CEPs são extraidos de uma página HTML, qualquer mudança no markup gerado afetará o funcionamento do componente.</div>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com1tag:blogger.com,1999:blog-4946206839402560285.post-18391662961878096232012-04-12T02:06:00.003-03:002012-04-12T11:48:20.632-03:00JMeter - Using BSF Assertion to fail samples based on JSON response<br />
I've tested some web applications whose unexpected errors or exceptions were handled by the application code and encoded in the response body as JSON.<br /><br />
A hypothetical response is shown below:<br />
<br />
<pre class="brush: javascript">{
data: null,
message: "TimeoutException: Transaction rolled back after 3000 seconds.",
success: false
}</pre>
<br />
The problem with this approach is that most of the times the HTTP response code is 200 (OK) which jmeter interprets as a successful sample.<br />
I'm not a big fan of this solution I'd rather have it returning 4XX or 5XX response codes, in which case jmeter would fail the sample.<br />
<br />
In order to workaround this, I normally use a <a href="http://jmeter.apache.org/usermanual/component_reference.html#BSF_Assertion" target="_blank">BSF Assertion</a> containing javascript code similar to:<br />
<br />
<pre class="brush: javascript">try {
eval('var response = ' + prev.getResponseDataAsString());
if (!response.success) {
prev.setSuccessful(false);
prev.setResponseMessage(response.message);
}
} catch(e) {
prev.setSuccessful(false);
prev.setResponseMessage("Invalid response. Expected a valid JSON.");
}</pre>
<br />
First I try to evaluate the response string into a valid javascript object. If that succeeds I check the success flag and handle it properly, otherwise, in the catch block, I force the sample to fail due to a bad response format.<br />
Note that in both failure scenarios I invoke the <b><span style="font-family: 'Courier New',Courier,monospace;">setSuccessful</span></b> and the <b><span style="font-family: 'Courier New',Courier,monospace;">setResponseMessage</span></b> methods to signal a sample failure and have better detailed error messages respectively.<br />
<br />
And that's it!<br />
Happy load testing!Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-91096466377399452132012-04-12T01:16:00.003-03:002012-04-12T11:48:55.314-03:00JMeter - Changing sample label/name at runtimeWhen creating jmeter's test plans, sometimes its useful to change the samples names in order to have more accurate and meaningful reports. To achieve this I normally use one of these two approaches:<br />
<br />
<h2>
Using variables (placeholders) in the sampler name</h2>
If the variables you need to form the new sample name are already available just fill the the name's text field using the syntax ${variable name}.<br />
The only problem with this approach is that the name shown in test plan tree (left panel) might end up being not so intuitive.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgw2ZX2wZKXyDYdpcv1_Nho2dodEARMpUAWzkZ78Rt8qaCMde8oJcxI9wxnnzUR_j9TmtnwogFz8ImCNbv5IzXzqKD_cQeSBldDQjy_FOD2_1Jn80pm0FOxc2mVcqNdsgoCa-uDXqF8AIY/s1600/jmeter-variable-in-name.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="388" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgw2ZX2wZKXyDYdpcv1_Nho2dodEARMpUAWzkZ78Rt8qaCMde8oJcxI9wxnnzUR_j9TmtnwogFz8ImCNbv5IzXzqKD_cQeSBldDQjy_FOD2_1Jn80pm0FOxc2mVcqNdsgoCa-uDXqF8AIY/s640/jmeter-variable-in-name.png" width="640" /></a></div>
<br />
<h2>
Dynamically setting it in <a href="http://jmeter.apache.org/usermanual/component_reference.html#BSF_PostProcessor" target="_blank">BSF Post Processors</a></h2>
Just use the variable <span style="font-family: 'Courier New',Courier,monospace;"><b>prev</b></span> (which <span style="background-color: white;">gives access to the previous <a href="http://jmeter.apache.org/api/org/apache/jmeter/samplers/SampleResult.html" target="_blank">SampleResult</a></span>) and use the method <span style="font-family: 'Courier New',Courier,monospace;"><b><a href="http://jmeter.apache.org/api/org/apache/jmeter/samplers/SampleResult.html#setSampleLabel%28java.lang.String%29" target="_blank">setSampleLabel</a> </b></span>to set the new label.<br />
In the example below I'm using the value of another variable (named url) previously put in the <b><span style="font-family: 'Courier New',Courier,monospace;">vars</span></b> object:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgehV8w1iGBmxH-jQbQ_NJJ2OzA9Bfeb2r0Cdiu89ER_cEziQIyPfTzfIGJwGzFqoBeIH9omWmZz2aX63EgwL_ZAAvvHocCqz55kBONxF-byW96Z5hCzwIuneEWM_GelPvrFqZ3V3aOeU/s1600/jmeter-change-label.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="211" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgehV8w1iGBmxH-jQbQ_NJJ2OzA9Bfeb2r0Cdiu89ER_cEziQIyPfTzfIGJwGzFqoBeIH9omWmZz2aX63EgwL_ZAAvvHocCqz55kBONxF-byW96Z5hCzwIuneEWM_GelPvrFqZ3V3aOeU/s640/jmeter-change-label.png" width="640" /></a></div>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-46607421074884060552012-02-09T23:51:00.000-02:002012-03-03T00:21:27.025-03:00Instalando a slackline (sem árvores) com auxílio de âncora - Parte 2Comecei a execução do <a href="http://fabiouechi.blogspot.com/2011/12/instalando-slackline-sem-arvores-com.html" target="_blank">meu projeto</a>.<br />
Abaixo algumas fotos do sistema de ancoragem que montei no meu quintal para instalar minha slackline.<br />
<br />
<span style="font-size: large;"><b>1) Escavação</b></span><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0MpWwyAjK31fUhFsjgSBzS-mn8dIqGZ1p6e7FXW-jkcsAEAcKcXdBv0Ph3BOK9sgypWkWzpWbvixESmqJs_1sssGEEz-HT7I29UD-itpO3MdA8fwj_1ruLRQ2Sa45TWpRMdm6xC7F0Wg/s1600/PICT0158.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: left;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0MpWwyAjK31fUhFsjgSBzS-mn8dIqGZ1p6e7FXW-jkcsAEAcKcXdBv0Ph3BOK9sgypWkWzpWbvixESmqJs_1sssGEEz-HT7I29UD-itpO3MdA8fwj_1ruLRQ2Sa45TWpRMdm6xC7F0Wg/s200/PICT0158.JPG" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHWIi4_yJXmxC4y_j-RJb2RyJUnmbaz6PQpKIaloosFN9jIut8ld4U2xZcxoJq678cYwAWJ8woVgByvuEzaWfkQwJHLHou2HrUhsbykxtOaaek_PaJz8V3g5f9aVbRodEjaS66kvX1I-4/s1600/PICT0160.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: left;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHWIi4_yJXmxC4y_j-RJb2RyJUnmbaz6PQpKIaloosFN9jIut8ld4U2xZcxoJq678cYwAWJ8woVgByvuEzaWfkQwJHLHou2HrUhsbykxtOaaek_PaJz8V3g5f9aVbRodEjaS66kvX1I-4/s200/PICT0160.JPG" width="200" /></a></div>
<br />
<br />
<span style="font-size: large;"><b>2) Preparando da âncora</b></span><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div style="margin-left: 1em; margin-right: 1em; text-align: left;">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieqziMcLret8AWAE_1H4FJdB0vDuaeQ3RmVHol4ZcSO0ZqdfqtD0w5QmUZcP_robZdV4rFzTDjx18cFXbHBshwAaGjcciw8SPcV32iGjwLVqQQKJabfequJMfIMVWYKbBVGulI9bhG4n8/s1600/PICT0163.JPG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"></a><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEieqziMcLret8AWAE_1H4FJdB0vDuaeQ3RmVHol4ZcSO0ZqdfqtD0w5QmUZcP_robZdV4rFzTDjx18cFXbHBshwAaGjcciw8SPcV32iGjwLVqQQKJabfequJMfIMVWYKbBVGulI9bhG4n8/s200/PICT0163.JPG" width="200" /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjU15K3tQuKn0r90BuuXs099xoCF4YXI5xLxAKzvqExWo6pW0GmNYdE4wDv6o30tjZoAsLt3T9IPFjTmvsUL56tYg6PXS7XxRawd3FT1bzkW9oVMggNiRWAe87OLkPnfAiLhtSuTc07-zI/s1600/PICT0161.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjU15K3tQuKn0r90BuuXs099xoCF4YXI5xLxAKzvqExWo6pW0GmNYdE4wDv6o30tjZoAsLt3T9IPFjTmvsUL56tYg6PXS7XxRawd3FT1bzkW9oVMggNiRWAe87OLkPnfAiLhtSuTc07-zI/s200/PICT0161.JPG" width="200" /></a></div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjU15K3tQuKn0r90BuuXs099xoCF4YXI5xLxAKzvqExWo6pW0GmNYdE4wDv6o30tjZoAsLt3T9IPFjTmvsUL56tYg6PXS7XxRawd3FT1bzkW9oVMggNiRWAe87OLkPnfAiLhtSuTc07-zI/s1600/PICT0161.JPG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_qte8Sc5m5Usx9GSunz3vkoCJUdSKXRMWEPsxcE7tpS9YlgLN_Dri8HFiVUi5gqDWhLY9uv1aB9qq5JVAKMnfG9whHzRxR_NTSt8ukTQiSxw68N8rLVuUUu9xJ4k1DOWFOnyIiA3Eda0/s1600/PICT0162.JPG" imageanchor="1" style="clear: left; display: inline !important; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_qte8Sc5m5Usx9GSunz3vkoCJUdSKXRMWEPsxcE7tpS9YlgLN_Dri8HFiVUi5gqDWhLY9uv1aB9qq5JVAKMnfG9whHzRxR_NTSt8ukTQiSxw68N8rLVuUUu9xJ4k1DOWFOnyIiA3Eda0/s200/PICT0162.JPG" width="200" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="font-size: large;"><b>3) Enterrando a âncora</b></span></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDUfea2wHRBJmT4uIS4i852gqM9dquiPuQ9ZPZpvY4D7LiNP8IOx6hmef2e-JJcAppvGzIXrZKLqCCRKa15L46CGhMNyiu0r5pHSBhDcwpOBiLIO_9_OmU-vBf2lK6Xvkn-HU_PG2TeYA/s1600/PICT0710.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDUfea2wHRBJmT4uIS4i852gqM9dquiPuQ9ZPZpvY4D7LiNP8IOx6hmef2e-JJcAppvGzIXrZKLqCCRKa15L46CGhMNyiu0r5pHSBhDcwpOBiLIO_9_OmU-vBf2lK6Xvkn-HU_PG2TeYA/s200/PICT0710.JPG" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXNX9xlYeWN-0ivX0c6nAeLgJi5RGuyivO_w6xeRRrbS4vwLxE6OT88XjMLD24oughiMhXdqphbEq7P8gbPogF4ShrP_AWJv5rUdO17Cf83r0upGkvFZCKarfJkZfcfDDM65KF7xClfSY/s1600/PICT0712.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXNX9xlYeWN-0ivX0c6nAeLgJi5RGuyivO_w6xeRRrbS4vwLxE6OT88XjMLD24oughiMhXdqphbEq7P8gbPogF4ShrP_AWJv5rUdO17Cf83r0upGkvFZCKarfJkZfcfDDM65KF7xClfSY/s200/PICT0712.JPG" width="200" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7lTJSu4jkT3vhslqrPEZsqzDBA3CXVG9QP7U-tcqkgMLSDJuB09QFmOWCU8fn2XHtqslbiiqvVLpA13r3UkF_fjMeyy7xFMrZO90W_c_mnyUgyjSvCvAuDCf1PGXOghmabonq8MGPt2s/s1600/PICT0713.JPG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"></a><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7lTJSu4jkT3vhslqrPEZsqzDBA3CXVG9QP7U-tcqkgMLSDJuB09QFmOWCU8fn2XHtqslbiiqvVLpA13r3UkF_fjMeyy7xFMrZO90W_c_mnyUgyjSvCvAuDCf1PGXOghmabonq8MGPt2s/s200/PICT0713.JPG" width="200" /> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxn8xbxuC4Tny4MbtpDFmLTBw-D9mUGbt3MCXYogiTZj4UEIbQi_2A6UjrUbPE0XKeHr1dDBpF-4T56B2n6KnP_uVp_VxbRClP_irQ9tJXdQSV2CjirzZCTnDBYIGqhEqeBPUHjp-38fI/s1600/PICT0714.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxn8xbxuC4Tny4MbtpDFmLTBw-D9mUGbt3MCXYogiTZj4UEIbQi_2A6UjrUbPE0XKeHr1dDBpF-4T56B2n6KnP_uVp_VxbRClP_irQ9tJXdQSV2CjirzZCTnDBYIGqhEqeBPUHjp-38fI/s200/PICT0714.JPG" width="200" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizUS8aJiMa4QyRFTFZbd9DHfHMZ8vspbO1NEmnogUhmxWuKsuGHGzHBpfqGOfR7Q7mXvht4SeKs61B47khJG_J4diXD1hPluTSv51jOsCxZ8MAniz_2xfMhu6v9bR4YLOv9Y9sdhZcjdw/s1600/PICT0715.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizUS8aJiMa4QyRFTFZbd9DHfHMZ8vspbO1NEmnogUhmxWuKsuGHGzHBpfqGOfR7Q7mXvht4SeKs61B47khJG_J4diXD1hPluTSv51jOsCxZ8MAniz_2xfMhu6v9bR4YLOv9Y9sdhZcjdw/s200/PICT0715.JPG" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN8z9Efa4DF0jG5ic-bVFhgg2p9qwExwg1Ko5EjiJV6eOskjAwMAKCKCQzuH8saPW2QReZksKTM0L9IJR61dATiM_KPiQZmionCzySVWkZenNVU5QIYZEz2qoUPpsvwHjiw9Vq_GGpdhw/s1600/PICT0716.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN8z9Efa4DF0jG5ic-bVFhgg2p9qwExwg1Ko5EjiJV6eOskjAwMAKCKCQzuH8saPW2QReZksKTM0L9IJR61dATiM_KPiQZmionCzySVWkZenNVU5QIYZEz2qoUPpsvwHjiw9Vq_GGpdhw/s200/PICT0716.JPG" width="200" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<b><span style="font-size: large;">4) Finalmente, armando a slack</span></b></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN9xlaXMfmpeQKC01UYdHycdMJsMPALTv0msMLm-4tr1T0MQNLOa2mbLcD_jBbQBlnQhpr6z2nKnNRfb9tIC9hfrifwvcUVo8rDiB52tc3N4FH94KoXUxDNW7lr_Kxio1gvtPAFsO_lrw/s1600/PICT0718.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN9xlaXMfmpeQKC01UYdHycdMJsMPALTv0msMLm-4tr1T0MQNLOa2mbLcD_jBbQBlnQhpr6z2nKnNRfb9tIC9hfrifwvcUVo8rDiB52tc3N4FH94KoXUxDNW7lr_Kxio1gvtPAFsO_lrw/s200/PICT0718.JPG" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbRnnaIp03t60TSx9gKp4tRB2Lx9uh9wZzTuNPbsdkc1a9WdcnhjCZiVOfaqsGdnh4pWSN7-9YWRf3JaMyJx7Ye-hvaKaoPV9JWetGkbrDQxtFMC1CUX8yxw10Yi1wIWKwsfmIbu5NH3o/s1600/PICT0717.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbRnnaIp03t60TSx9gKp4tRB2Lx9uh9wZzTuNPbsdkc1a9WdcnhjCZiVOfaqsGdnh4pWSN7-9YWRf3JaMyJx7Ye-hvaKaoPV9JWetGkbrDQxtFMC1CUX8yxw10Yi1wIWKwsfmIbu5NH3o/s200/PICT0717.JPG" width="200" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFipSUwJpVoKVw3g2fZVhZ0TcMpMQG03bE1lGxSHswtxN6cA_r4AKLDqLN7MwPb05M7stD3ZpUc0De7Nfya8aUsmS6HfTAG5qklBzZrYCm4rNtBCfFHQIPK5Ci5vW8QD1B-gha7vIBHGk/s1600/PICT0719.JPG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgFipSUwJpVoKVw3g2fZVhZ0TcMpMQG03bE1lGxSHswtxN6cA_r4AKLDqLN7MwPb05M7stD3ZpUc0De7Nfya8aUsmS6HfTAG5qklBzZrYCm4rNtBCfFHQIPK5Ci5vW8QD1B-gha7vIBHGk/s200/PICT0719.JPG" width="200" /></a></div>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com3tag:blogger.com,1999:blog-4946206839402560285.post-49710339427474543242012-02-09T14:52:00.002-02:002012-02-09T14:54:05.628-02:00Slackline / Waterline in the Colombian Caribbean<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: left;">
Slackline and waterline sessions during my last trip to the Colombian Caribbean. </div>
<div class="separator" style="clear: both; text-align: left;">
This video includes waterlining in the Lover's bridge (between Providencia and Santa Catalina Island) and slacklining in Johnny Cay - in San Andres Island. </div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/n3mKgBZa2jg?feature=player_embedded' frameborder='0'></iframe></div>
<br />Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0Puente De Los Enamorados # 1, Providencia (Santa Isabel), Archipelago of San Andrés, Colombia13.386947652895737 -81.37229919433593813.325164652895737 -81.451263194335937 13.448730652895737 -81.293335194335938tag:blogger.com,1999:blog-4946206839402560285.post-81768073318728746312011-12-30T00:04:00.002-02:002012-03-03T00:23:10.200-03:00Instalando a slackline (sem árvores) com auxílio de âncora - Parte 1<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="" style="clear: both; text-align: left;">
No meu quintal não existem árvores nem outra estrutura para instalar minha <b>slackline</b>.<br />
Pesquisando um pouco descobri o conceito de <i>dead men anchor </i>que nada mais é que uma <b>âncora</b>, no meu caso de madeira, enterrada a 1,5m de profundidade.<br />
Porém, diferentemente <a href="http://www.youtube.com/watch?v=zHJI64jNrTU" target="_blank">desses</a> <a href="http://www.youtube.com/watch?v=9h7g4ymyJx8" target="_blank">vídeos</a>, eu não quero enterrar a slackline. Quero uma solução permanente que permita instalar e desinstalar a fita quando quiser.<br />
<br />
Por isso resolvi usar cordas de nylon amarradas à âncora. A ideia é amarrar a slackline nessas cordas e usar algum outro tipo de estrutura bem firme: a-frame, cavalete, tronco ou caixa para erguê-la.<br />
<br />
Por enquanto, é só um plano. Segue meus esboços:</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwcv7x9aZ_8Y4oI5Z9qfRMu4WdANB3upKxSmDu87Be6_wNf1Bwmwr5OUQjsK0nGVThQirRuCKbehl6pGfXo2IJz_N7DBu5XzOEGu2cW_vXFQ5EVj3bLjDaMv-CJ8z8WhRVc2u4ZwxeGLQ/s1600/connector.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="78" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwcv7x9aZ_8Y4oI5Z9qfRMu4WdANB3upKxSmDu87Be6_wNf1Bwmwr5OUQjsK0nGVThQirRuCKbehl6pGfXo2IJz_N7DBu5XzOEGu2cW_vXFQ5EVj3bLjDaMv-CJ8z8WhRVc2u4ZwxeGLQ/s200/connector.jpg" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMGzL3OsJucgKwV8Ks-cAaJ3fW-BIHprOiGHxtjyCtd3wK1vymIRyaFBFRmzzXXmWj-8MJtvA3d4w06WICX0ybZwMFovDDvfgrAIgytMFINNDSR3_mJJf39jbmc215kzLr0MfvSBsMZUE/s1600/anchor-1.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="193" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMGzL3OsJucgKwV8Ks-cAaJ3fW-BIHprOiGHxtjyCtd3wK1vymIRyaFBFRmzzXXmWj-8MJtvA3d4w06WICX0ybZwMFovDDvfgrAIgytMFINNDSR3_mJJf39jbmc215kzLr0MfvSBsMZUE/s320/anchor-1.jpg" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjC76sGFqlWcHcKyzgew_5DA_auJ94Zck_ACDSjgMa-LBgHCm3f-3ocmYicRN7qE0Dz2UHiGWbSUDs6e4s0giAEFkBBhrlADLyBPB6tLvGOmmh5Ndx2RbPU6vTeVL4MrvzxEfqUYz1qlzQ/s1600/anchor.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjC76sGFqlWcHcKyzgew_5DA_auJ94Zck_ACDSjgMa-LBgHCm3f-3ocmYicRN7qE0Dz2UHiGWbSUDs6e4s0giAEFkBBhrlADLyBPB6tLvGOmmh5Ndx2RbPU6vTeVL4MrvzxEfqUYz1qlzQ/s320/anchor.jpg" width="220" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjanfF3H-s4JI6jrI9VPARIKZRZGRcmXc1CCzgRKZ6D41-OS5qlDeL4_NaDeox0JLN4eAw1lJIsvvk9_WM_EH3c4Kp9iIEUAm9zeMWgv0SaBabfxnPNWrSYxzCBtBXeLMTkCNI_WhgT7lM/s1600/a-board.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjanfF3H-s4JI6jrI9VPARIKZRZGRcmXc1CCzgRKZ6D41-OS5qlDeL4_NaDeox0JLN4eAw1lJIsvvk9_WM_EH3c4Kp9iIEUAm9zeMWgv0SaBabfxnPNWrSYxzCBtBXeLMTkCNI_WhgT7lM/s320/a-board.jpg" width="262" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Assim que começar o projeto posto por aqui!</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com1Campinas-22.8234785 -47.0906365-22.825308 -47.093104000000004 -22.821649 -47.088169tag:blogger.com,1999:blog-4946206839402560285.post-76898904131894713352011-12-03T17:13:00.001-02:002011-12-07T15:57:29.446-02:00Importing data from Oracle to Google App Engine Datastore with a custom bulkloader connectorI'm working on a project where we need to upload data from Oracle databases to Datastore very frequently.<br />
The <a href="http://code.google.com/appengine/docs/python/tools/uploadingdata.html" target="_blank">datastore bulkloader</a> bundled in the App Engine Python SDK works very well for data importing and exporting. It currently offers 3 different connector implementations: <b>csv</b>, <b>xml</b> and <b>simpletext</b>.<br />
CSV connector was working smoothly for us but I wanted to avoid the CSV file generation. I wanted to bulkload directly from Oracle.<br />
After some <a href="http://bulkloadersample.appspot.com/" target="_blank">study</a> I decided to write a new connector: the <b>oracle_connector</b>.<br />
The only pre requisite for using this connector is having the <a href="http://www.google.com.br/url?sa=t&rct=j&q=cx_oracle&source=web&cd=1&ved=0CCEQFjAA&url=http%3A%2F%2Fcx-oracle.sourceforge.net%2F&ei=2DDcToyPNsn8ggfb3azVCA&usg=AFQjCNFvr_SdfxIsHlv7uyvbPo_ziiBYcg" target="_blank">cx_Oracle</a> python module properly installed.<br />
Here are some details about my environment:<br />
<ul>
<li>Ubuntu 11.10 - 64 bits</li>
<li>Python 2.7.2+</li>
<li>cx_Oracle 5.0.4 unicode</li>
<li>Oracle Instant Client 11.2</li>
<li>Google App Engine Python SDK 1.6.0</li>
</ul>
<span style="font-size: large;">Some design decisions</span><br />
<br />
I wanted to specify a sql query for every kind/entity in the yaml config file and then map the returned (selected) column names or aliases to the properties via the <b>external_name</b> attribute. I didn't find a way to use a custom <b>connector_options</b> like "sql_query" so I used the existent <b>columns </b>option to inform the query.<br />
I also wanted to have the database connection properties to be outside the connector implementation. In order to achieve that I decided to store these configurations in an external file which is passed through the command line "<b>--filename"</b> parameter to the <b>appcfg.py</b> script.<br />
<br />
<span style="font-size: large;">The actual implementation</span><br />
<br />
It is basically comprised of 3 files:<br />
<ul>
<li><b style="font-weight: bold;">bulkloader.yaml - </b>imports and<b style="font-weight: bold;"> </b>utilizes the <b>oracle_connector</b> as well as maps queries to entities</li>
<li><b>oracle_connector.py - </b>connector implementation based on <b>cx_Oracle</b> (doesn't work for exports)</li>
<li><b>db_settings.py - </b>defines connection properties variables used by <b>oracle_connector</b></li>
</ul>
Invoking the bulkloader is very simple:<br />
<br />
<pre class="brush: shell">$APPENGINE_HOME/appcfg.py upload_data --config_file=bulkloader.yaml --kind=Table --filename=db_settings.py --email=user@gmail.com --url=http://app-id.appspot.com/remote_api</pre>
<br />
The <b>bulkloader.yaml</b> below shows how to import and use the <b>oracle_connector. </b>Also note the <b>connector_options</b> attribute.<br />
<br />
<pre class="brush: python">python_preamble:
- import: base64
- import: re
- import: oracle_connector
- import: google.appengine.ext.bulkload.transform
- import: google.appengine.ext.bulkload.bulkloader_wizard
- import: google.appengine.ext.db
- import: google.appengine.api.datastore
- import: google.appengine.api.users
transformers:
- kind: Table
connector: oracle_connector.OracleConnector.create_from_options
connector_options:
columns: "select TABLE_NAME, TABLESPACE_NAME, LAST_ANALYZED from user_tables"
property_map:
- property: __key__
external_name: TABLE_NAME
- property: tablespace
external_name: TABLESPACE_NAME
- property: last_analyzed
external_name: LAST_ANALYZED</pre>
<br />
There are some known issues here: 1) you must use <b>uppercase</b> strings in the <b>external_name</b> attribute and 2) I wasn't able to return <b>number</b> columns (had to use <b>to_char</b> oracle function) due to some problem in my cx_Oracle installation.<br />
<br />
Below, the <b>OracleConnector</b> class which implements the <b>connector_interface.ConnectorInterface. </b>This was my first piece of python code. Let me know if I can improve it!<br />
<br />
<pre class="brush: python">#!/usr/bin/env python
"""A bulkloader connector to read data from Oracle selects.
"""
from google.appengine.ext.bulkload import connector_interface
from google.appengine.ext.bulkload import bulkloader_errors
import cx_Oracle
import os.path
class OracleConnector(connector_interface.ConnectorInterface):
@classmethod
def create_from_options(cls, options, name):
"""Factory using an options dictionary.
Args:
options: Dictionary of options:
columns: sql query to perform, each selected column becomes a column
name: The name of this transformer, for use in error messages.
Returns:
OracleConnector object described by the specified options.
Raises:
InvalidConfiguration: If the config is invalid.
"""
columns = options.get('columns', None)
if not columns:
raise bulkloader_errors.InvalidConfiguration(
'Sql query must be specified in the columns '
'configuration option. (In transformer name %s.)' % name)
return cls(columns)
def __init__(self, sql_query):
"""Initializer.
Args:
sql_query: (required) select query which will be sent to database. The returned columns/aliases will be used as the connectors column names
"""
self.sql_query = unicode(sql_query)
def generate_import_record(self, filename, bulkload_state):
"""Generator, yields dicts for nodes found as described in the options.
Args:
filename: py script containing oracle database connection properties: host, port, uid, pwd and service.
bulkload_state: Passed bulkload_state.
Yields:
Neutral dict, one per row returned by the sql query
"""
dbprops = __import__(os.path.splitext(filename)[0])
dsn_tns = cx_Oracle.makedsn(dbprops.host, dbprops.port, dbprops.service)
connection = cx_Oracle.connect(dbprops.uid, dbprops.pwd, dsn_tns)
cursor = connection.cursor()
cursor.arraysize = dbprops.cursor_arraysize
cursor.execute(self.sql_query)
num_fields = len(cursor.description)
field_names = [i[0] for i in cursor.description]
for row in cursor.fetchall():
decoded_dict = {}
for i in range(num_fields):
decoded_dict[field_names[i]] = row[i]
yield decoded_dict
cursor.close()
connection.close()</pre>
<br />
And finally the contents of <b>db_settings.py</b>:<br />
<br />
<pre class="brush: python">uid=u'database_username'
pwd=u'database_password'
host="database_host"
port=1521
service="service"
cursor_arraysize = 50</pre>
<br />
<span style="font-size: large;">Get the code</span><br />
<br />
You can download the code here:<br />
<br />
<a href="https://github.com/fabito/gae_bulkloader_connectors">https://github.com/fabito/gae_bulkloader_connectors</a><br />
<br />
<span style="font-size: large;">Summary</span><br />
<br />
This post shows an alternative connector implementation for importing data directly from an Oracle database.<br />
This approach could be easily extended to support other RDBMS such as MySQL or Postgres.<br />
We still have to perform some tests to check how it will behave under different loads and data types but so far, it seems promising.<br />
<br />
<span style="font-size: large;">References</span><br />
<br />
<ul>
<li><a href="https://help.ubuntu.com/community/Oracle%20Instant%20Client" target="_blank">Installing Oracle Instant Client in Ubuntu</a></li>
<li><a href="http://boyombo.blogspot.com/2011/11/installing-cxoracle-on-64-bit-ubuntu.html" target="_blank">Installing cx_Oracle in Ubuntu 64 bits</a></li>
<li><a href="http://bulkloadersample.appspot.com/" target="_blank">Bulk loader resources</a></li>
</ul>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-90171749519897532902011-11-08T23:52:00.001-02:002011-11-09T23:10:36.057-02:00Using google-oauth-java-client to consume a Google App Engine OAuth protected resourceThis post shows how to write a <b>java</b> based OAuth client to make requests against a Google App Engine OAuth protected resource using the google-oauth-java-client.<br />
<br />
If you want to know how to create an OAuth provider or how to register your domain and get your consumer key and secret, I highly recommend you to read this <a href="http://ikaisays.com/2011/05/26/setting-up-an-oauth-provider-on-google-app-engine/" target="_blank">great blog post</a> written by Ikai Lan.<br />
Actually, you should read it anyway, because the piece of code below just replaces the python script provided by him. Since I needed a <b>java</b> version I decided to write my own client.<br />
<br />
The java class below contains a basic junit test which will do the 3-legged OAuth dance. The only thing I haven't implemented is the access token cache part - every time you run this test you will have to explicitly perform the authorization steps in the browser (again).<br />
<br />
In order to run it, you basically have to resolve 2 dependencies:<br />
<ul>
<li><a href="http://code.google.com/p/google-oauth-java-client/" target="_blank">google-oauth-java-client</a></li>
<li><a href="http://code.google.com/p/google-http-java-client/" target="_blank">google-http-java-client</a></li>
</ul>
I used the <a href="http://code.google.com/p/google-oauth-java-client/downloads/detail?name=google-oauth-java-client-1.6.0-beta.zip&can=2&q=" target="_blank">1.6.0-beta version</a>. Just download and put them in the classpath and you are good to go!<br />
<br />
And, of course, don't forget to change the <b>APP_ID</b> and <b>CONSUMER_SECRET </b>constants.<br />
<br />
<pre class="brush: java">package com.ciandt.oauth.client;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.junit.Test;
import com.google.api.client.auth.oauth.OAuthAuthorizeTemporaryTokenUrl;
import com.google.api.client.auth.oauth.OAuthCredentialsResponse;
import com.google.api.client.auth.oauth.OAuthGetAccessToken;
import com.google.api.client.auth.oauth.OAuthGetTemporaryToken;
import com.google.api.client.auth.oauth.OAuthHmacSigner;
import com.google.api.client.auth.oauth.OAuthParameters;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
public class OAuthClientTest {
private static final HttpTransport TRANSPORT = new NetHttpTransport();
private static final String APP_ID = "your_app_id_here";
private static final String CONSUMER_KEY = APP_ID + ".appspot.com";
private static final String CONSUMER_SECRET = "your_consumer_secret_here";
private static final String PROTECTED_SERVICE_URL = "https://" + APP_ID + ".appspot.com/resource";
private static final String REQUEST_TOKEN_URL = "https://" + APP_ID + ".appspot.com/_ah/OAuthGetRequestToken";
private static final String AUTHORIZE_URL = "https://" + APP_ID + ".appspot.com/_ah/OAuthAuthorizeToken";
private static final String ACCESS_TOKEN_URL = "https://" + APP_ID + ".appspot.com/_ah/OAuthGetAccessToken";
@Test
public void consumeProtectedResource() throws Throwable {
// this signer will be used to sign all the requests in the "oauth dance"
OAuthHmacSigner signer = new OAuthHmacSigner();
signer.clientSharedSecret = CONSUMER_SECRET;
// Step 1: Get a request token. This is a temporary token that is used for
// having the user authorize an access token and to sign the request to obtain
// said access token.
OAuthGetTemporaryToken requestToken = new OAuthGetTemporaryToken(REQUEST_TOKEN_URL);
requestToken.consumerKey = CONSUMER_KEY;
requestToken.transport = TRANSPORT;
requestToken.signer = signer;
OAuthCredentialsResponse requestTokenResponse = requestToken.execute();
System.out.println("Request Token:");
System.out.println(" - oauth_token = " + requestTokenResponse.token);
System.out.println(" - oauth_token_secret = " + requestTokenResponse.tokenSecret);
// updates signer's token shared secret
signer.tokenSharedSecret = requestTokenResponse.tokenSecret;
OAuthAuthorizeTemporaryTokenUrl authorizeUrl = new OAuthAuthorizeTemporaryTokenUrl(AUTHORIZE_URL);
authorizeUrl.temporaryToken = requestTokenResponse.token;
// After the user has granted access to you, the consumer, the provider will
// redirect you to whatever URL you have told them to redirect to. You can
// usually define this in the oauth_callback argument as well.
String currentLine = "n";
System.out.println("Go to the following link in your browser:\n"
+ authorizeUrl.build());
InputStreamReader converter = new InputStreamReader(System.in);
BufferedReader in = new BufferedReader(converter);
while (currentLine.equalsIgnoreCase("n")) {
System.out.println("Have you authorized me? (y/n)");
currentLine = in.readLine();
}
// Step 3: Once the consumer has redirected the user back to the oauth_callback
// URL you can request the access token the user has approved. You use the
// request token to sign this request. After this is done you throw away the
// request token and use the access token returned. You should store this
// access token somewhere safe, like a database, for future use.
OAuthGetAccessToken accessToken = new OAuthGetAccessToken(
ACCESS_TOKEN_URL);
accessToken.consumerKey = CONSUMER_KEY;
accessToken.signer = signer;
accessToken.transport = TRANSPORT;
accessToken.temporaryToken = requestTokenResponse.token;
OAuthCredentialsResponse accessTokenResponse = accessToken.execute();
System.out.println("Access Token:");
System.out.println(" - oauth_token = " + accessTokenResponse.token);
System.out.println(" - oauth_token_secret = " + accessTokenResponse.tokenSecret);
System.out.println("\nYou may now access protected resources using the access tokens above.");
// updates signer's token shared secret
signer.tokenSharedSecret = accessTokenResponse.tokenSecret;
OAuthParameters parameters = new OAuthParameters();
parameters.consumerKey = CONSUMER_KEY;
parameters.token = accessTokenResponse.token;
parameters.signer = signer;
// utilize accessToken to access protected resources
HttpRequestFactory factory = TRANSPORT.createRequestFactory(parameters);
GenericUrl url = new GenericUrl(PROTECTED_SERVICE_URL);
HttpRequest req = factory.buildGetRequest(url);
HttpResponse resp = req.execute();
System.out.println("Response Status Code: " + resp.getStatusCode());
System.out.println("Response body:" + resp.parseAsString());
}
}</pre>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com11tag:blogger.com,1999:blog-4946206839402560285.post-74937057095348984162011-10-11T15:21:00.003-03:002011-11-18T13:53:49.189-02:00Automating GAE's application deployment with JenkinsFollow below the <a href="http://en.wikipedia.org/wiki/Expect">expect script</a> I created to automate the deployment of applications on GAE.
It simply invokes/spawns the <b>appcfg.sh </b>and sends the account's password when prompted.<br />
Then I configured a Jenkins job which contains a "execute shell" step invoking this script.<br />
<br />
<span style="font-size: large;">Jenkins "execute shell" step</span><br />
<br />
<pre class="brush: bash">expect $WORKSPACE/appcfg.exp "$APPENGINE_HOME" "myuser@gmail.com" "mypassword" "update" "$WORKSPACE/war"
</pre>
<br />
<span style="font-size: large;">appcfg.exp expect script</span><br />
<br />
<pre class="brush: bash">#!/usr/bin/expect -f
# Expect script to supply GAE's account password for appcfg.sh
#
# This script needs four arguments:
# username = GAE's google account email
# password = GAE's google account password
# warDir = war directory to deploy to GAE
# gaeHome = GAE's SDK home dir
#
# For example:
# expect appcfg.exp myemail@gmail.com mypassword ./war /usr/share/appengine-sdk-1.5.3
if {[llength $argv] == 0} {
puts "usage: appcfg.exp {-index|#}"
exit 1
}
set gaeHome [lrange $argv 0 0]
set username [lrange $argv 1 1]
set password [lrange $argv 2 2]
set cmd [lrange $argv 3 3]
set warDir [lrange $argv 4 4]
set timeout -1
# spawns appcfg.sh
spawn $gaeHome/bin/appcfg.sh --enable_jar_splitting --passin --email=$username $cmd $warDir
match_max 100000
expect {
default {exit 0}
# Look for passwod prompt
"*?assword*"
}
# Send password aka $password
send -- "$password\r"
# send blank line (\r) to make sure we get back to gui
send -- "\r"
expect eof
</pre>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-24252630800069855692011-10-11T14:46:00.001-03:002011-11-10T12:07:31.817-02:00Using XmlSlurper to update appengine-web.xmlI'm currently working on a project which is using GAE (Google Application Engine).<br />
I wanted to setup Jenkins' jobs to update the different stages (acceptance, qa, uat) in our build pipeline.<br />
To achieve that I created 3 different GAE's applications - one for each stage - and 4 Jenkins jobs: 1 template which holds the common steps and 3 others for the respective stages.<br />
The first step of the template job updates <b style="font-family: "Courier New",Courier,monospace;">appengine-web.xml</b> target <b style="font-family: "Courier New",Courier,monospace;">application</b> and <b style="font-family: "Courier New",Courier,monospace;">version<span style="font-family: inherit;"> </span></b><span style="font-family: inherit;">based </span><span style="font-family: inherit;">on parameters passed to the job. </span><b style="font-family: "Courier New",Courier,monospace;"><br /></b><br />
<br />
See the groovy script below:<br />
<br />
<br />
<pre class="brush: groovy">import groovy.xml.StreamingMarkupBuilder
//getting parameters values from environment variables
def env = System.getenv()
def workspace = env["WORKSPACE"]
def applicationName = env["TARGET_APPLICATION_NAME"]
def versionName = env["TARGET_VERSION_NAME"]
def ant = new AntBuilder()
ant.echo(message:"Opening $workspace/war/WEB-INF/appengine-web.xml")
def file = new File("$workspace/war/WEB-INF/appengine-web.xml")
def root = new XmlSlurper().parse(file)
ant.echo(message:"Updating appengine-web.xml with application: $applicationName and version: $versionName")
root.application=applicationName
root.version=versionName
def outputBuilder = new StreamingMarkupBuilder()
String result = outputBuilder.bind{
mkp.declareNamespace("": "http://appengine.google.com/ns/1.0")
mkp.yield root
}
ant.echo(message:"Writing appengine-web.xml")
file.write(result)</pre>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-2527013489395831792011-03-31T00:04:00.000-03:002012-11-12T11:51:57.486-02:00JMeter - Processing and handling JSON responses using BSF post processorsIn one of my Jmeter posts I talked about <a href="http://fabiouechi.blogspot.com.br/2011/03/jmeter-posting-json-data-to-web-service.html" target="_blank">how to post JSON data</a> using a HttpSampler.<br />
Now I want to show some cool stuff we can do using Jmeter's javascript/BSF support for handling JSON responses.<br />
<br />
Suppose we have the following JSON response:<br />
<br />
<pre class="brush: js">{ success: true, data:[ 1,2,3,4,5 ] }</pre>
<br />
This string can be easily converted to a javascript object by using the eval function like this:<br />
<br />
<pre class="brush: js">eval( 'var myObj = ' + prev.getResponseDataAsString() )</pre>
<br />
After the evaluation we can use the variable "<b>myObj</b>" as we wish and do things like accessing the success property:<br />
<br />
<pre class="brush: js">log.info(myObj.success)</pre>
<br />
Or iterate over the list in the data property:<br />
<br />
<pre class="brush: js">for ( var i = 0; i < myObj.data.length; i++ ) {
log.info(myObj.data[i])
}</pre>
<br />
We can also put the object in the implicit vars object so it can be used in another sampler, for example:<br />
<br />
<pre class="brush: js">vars.putObject('myObj', myObj)</pre>
Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-19498102049005292622011-03-25T21:05:00.002-03:002011-03-25T21:39:05.188-03:00Generating CSV files using sqlcmd and groovyJust sharing a simple groovy script I wrote for generating a CSV file from the database.<br />
It depends on SQLServer's sqlcmd command line utility.<br />
It takes 4 input parameters - needed to open a database connection, scans the current directory for .sql files then executes each of them using sqlcmd generating a .temp file. The temp file is then processed - the first and last 2 lines are removed - and renamed to a .csv file.<br />
<br />
<pre class="brush: groovy">def username = args[0]
def password = args[1]
def host = args[2]
def database = args[3]
def dir = './'
def ant = new AntBuilder()
def p = ~/.*\.sql/
new File( dir ).eachFileMatch(p) { f ->
def sqlFile = f.name
def tempCsvFile = sqlFile.replaceAll(/.sql/,'')
def cmd = "sqlcmd -S $host -U $username -P $password -d ${database} -i ${sqlFile} -W -o ${tempCsvFile}.temp -s ;"
def process = cmd.execute()
process.waitFor()
ant.move(file: "${tempCsvFile}.temp", tofile:"${tempCsvFile}.csv", overwrite: true ) {
filterchain(){
headfilter(lines:"-1", skip: "2")
tailfilter(lines: "-1", skip: "2" )
ignoreblank()
}
}
}
</pre><br />
This could probably be achieved in many other ways.<br />
But it worked like a charm for me!!Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-63156608154992602552011-03-22T23:32:00.021-03:002012-11-19T13:29:17.830-02:00JMeter - POSTing / Sending JSON data<ul>
<li><a href="http://fabiouechi.blogspot.com/2011/03/jmeter-handling-json-responses.html" target="_blank">Processing and handling JSON responses using BSF post processors</a></li>
<li><a href="http://fabiouechi.blogspot.com/2012/04/jmeter-using-bsf-assertion-to-fail.html" target="_blank">Using BSF Assertion to fail samples based on JSON response</a></li>
<li><a href="http://fabiouechi.blogspot.com/2012/04/jmeter-changing-sample-label-at-runtime.html" target="_blank">Changing sample label/name at runtime</a></li>
</ul>
Recently I was load testing a RESTful mobile API where all the transmitted data were encoded as JSON. It took me some time to figure out how to simulate that in JMeter using a regular HTTPRequest sampler so I decided to share it with others.<br />
<br />
<b>NOTE</b>: I successfuly used this technique for webservices exposed by the Jersey library as well as ASP.NET asmx webservices<br />
<br />
It's quite simple. Assuming you need to send POST request to the <b><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">/login</span></b> URI passing two parameters: <b>email</b> and <b>password</b> encoded as JSON, like this:<br />
<br />
<pre class="brush: js">{ email: 'myemail@email.com', password: 'mypassword' }
</pre>
<br />
Just follow two basic steps:<br />
<br />
<span class="Apple-style-span" style="font-size: large;">Step 1 - </span>Setting the "ContentType" http header attribute to "application/json"<br />
<ul>
<li>Add an <a href="http://jakarta.apache.org/jmeter/usermanual/component_reference.html#HTTP_Header_Manager">HTTP Header Manager</a> to your test plan;</li>
<li>Add a new attribute: set name to "Content-Type" and value to "application/json"</li>
</ul>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJPh2E8ZVWFnDBt9jVV-Jemy41S3yc8itT_QxqVf4MbMO6wYQMzl0pI1oBH6pRPefR7OybtlsvzvUbICb-qEq76l89eqBxqdem8ID15-R-bsPpxYMbFKVaApBJU8Fj0Fzh5Up1wOPsePw/s1600/json_http_header_manager.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="232" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJPh2E8ZVWFnDBt9jVV-Jemy41S3yc8itT_QxqVf4MbMO6wYQMzl0pI1oBH6pRPefR7OybtlsvzvUbICb-qEq76l89eqBxqdem8ID15-R-bsPpxYMbFKVaApBJU8Fj0Fzh5Up1wOPsePw/s320/json_http_header_manager.png" width="320" /></a></div>
<span class="Apple-style-span" style="font-size: large;"><br />
</span><br />
<span class="Apple-style-span" style="font-size: large;">Step 2 - </span>Setting the JSON object as an unnamed http request parameter<br />
<ul>
<li>Add a HttpRequest sampler</li>
<li>Add a new parameter, leave the <b>name</b> blank and set the <b>value</b> to the JSON string: <b>{ 'email': 'myemail@email.com', 'password': 'mypassword' }</b></li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYBLfm43SCu-fpVEaeEe6J3TNiUW6GKdGx416Cbtb52oj_3nvuwl7d4v9ulwlyYxVZW9EURXMKy51NdBJgCM8Fl7wP1T-wrIYQ6mvw6sg9fTwQw0HBxTt_KZf3KFE-CKzuifOkNKg0Q1o/s1600/json_http_request_sampler.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="232" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYBLfm43SCu-fpVEaeEe6J3TNiUW6GKdGx416Cbtb52oj_3nvuwl7d4v9ulwlyYxVZW9EURXMKy51NdBJgCM8Fl7wP1T-wrIYQ6mvw6sg9fTwQw0HBxTt_KZf3KFE-CKzuifOkNKg0Q1o/s320/json_http_request_sampler.png" width="320" /></a></div>
<br />
And last but not least: you can use variables and properties as you wish in the JSON string. Suppose you have a <a href="http://jakarta.apache.org/jmeter/usermanual/component_reference.html#CSV_Data_Set_Config">CSV Data Set Config</a> which defines to external variables: email e pwd. You could use them in your sampler like this:<br />
<br />
<pre class="brush: js">{ email: '${email}', password: '${pwd}' }
</pre>
Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com5tag:blogger.com,1999:blog-4946206839402560285.post-82377655449482935142010-11-09T02:05:00.008-02:002010-11-11T12:06:15.967-02:00A arte de desenvolver sem branches<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPA1Mu5LOJX7aMuo_zlHhw8lH55jf_YZIZjNkw1-1dHZjrf1FxqN_5T7tkq5Zu9Mq48qvB453h0_9rybTiK1EKJuOpziorLB5xIk3j6dq8Wz4Xbjc_83qb2a-isQdkHxc1_Xjw2xYqTPM/s1600/tracks01%5B1%5D.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPA1Mu5LOJX7aMuo_zlHhw8lH55jf_YZIZjNkw1-1dHZjrf1FxqN_5T7tkq5Zu9Mq48qvB453h0_9rybTiK1EKJuOpziorLB5xIk3j6dq8Wz4Xbjc_83qb2a-isQdkHxc1_Xjw2xYqTPM/s200/tracks01%5B1%5D.jpg" width="200" /></a></div>Ultimamente eu e meu time estamos evitando ao máximo a criação de branches no SVN. Nosso objetivo é comitar apenas no trunk e tentar manter a aplicação sempre <i>releaseable. </i>Acreditamos que isso ajuda a manter o fluxo de entregas constante e o caminho aberto para o sonhado continuous <i>deployment</i> e <i>delivery</i>.<br />
<div><br />
</div><div>Nossas últimas experiências com <a href="http://martinfowler.com/bliki/FeatureBranch.html">feature branch</a> foram desagradáveis. Além dos (muito) chatos, às vezes longos, e arriscados <i>merges</i> envolvidos na criação de um novo <i>branch</i>, o que mais incomoda é a falta de integração contínua e o "tumulto" causado quando o <i>branch</i> é reintegrado ao <i>trunk</i> - dependendo do tamanho e o grau de intrusão é necessária uma longa fase de estabilização.</div><div><br />
Para evitar esse transtorno, uma prática que começamos a adotar é, ao invés de criar branches no controlador de versão, fazer as ramificações no próprio código fonte - <a href="http://paulhammant.com/blog/branch_by_abstraction.html">branch by abstraction</a>. Basicamente o que fazemos é criar <i>flags</i> em arquivos de propriedades da aplicação e utilizá-las para esconder/desabilitar uma <i>feature</i> incompleta (GUI) ou para chavear entre uma implementação ou outra.<br />
<br />
Características do <i>design,</i> por exemplo utilização de patterns como <a href="http://martinfowler.com/articles/injection.html">Dependency Injection</a>, <i><a href="http://en.wikipedia.org/wiki/Strategy_pattern">Strategy</a></i> e <i><a href="http://en.wikipedia.org/wiki/Factory_method_pattern">Factory</a>,</i> facilitam bastante o trabalho. Mais que isso, já vi casos de <i>refactorings</i> para suportar esse tipo de chaveamento, melhorar o <i>design </i>através da redução do acoplamento, e nascimento de novos componentes!</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpyU5f4XtUpoYZYJTKO4L9_0lCWIqBv10C7hZy8S0GsmYQkzuAZ3gtdBrtZ0CbwmmlLcHPZVkd9q0pXFIwzENBlVaorAld1iNfoOHdECMfEhQrbSLgXzl-0swhNR-cCRf9fHyCivq6TKk/s1600/control_yourself.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpyU5f4XtUpoYZYJTKO4L9_0lCWIqBv10C7hZy8S0GsmYQkzuAZ3gtdBrtZ0CbwmmlLcHPZVkd9q0pXFIwzENBlVaorAld1iNfoOHdECMfEhQrbSLgXzl-0swhNR-cCRf9fHyCivq6TKk/s200/control_yourself.jpg" width="200" /></a></div><br />
É uma estratégia bem simples de adotar e que traz alguns benefícios. Como continuamos a comitar no <i>mainline,</i> o código segue sendo continuamente integrado pelo Hudson juntamente com as demais <i>features</i>. Se bem planejado e estruturado, é possível entregar a feature aos poucos, em pequenas partes, reduzindo o risco das implantações. A cada release pouquíssimo código novo é implantado e consequentemente a identificação de eventuais bugs ou efeitos colaterais indesejados é antecipada, reduzindo o custo de investigação e correção.<br />
<br />
Para ilustrar: suponha que temos uma nova demanda que alterará drasticamente o cerne da aplicação. Pior que isso, a estimativa para terminá-la é de alguns dias, podendo chegar até uma semana. Em paralelo várias outras melhorias e correções de bugs menores irão acontecer. O objetivo é entregar o quanto antes tudo o que for ficando pronto e evitar ao máximo o "represamento" de código.<br />
<br />
Enxergo três opções: A primeira, e pior de todas, é sair implementando tudo no trunk deixando a aplicação <i>unreleaseable</i> por um longo período de tempo. A segunda é criar uma feature branch para a alteração maior e desenvolver as demandas menores no trunk, dessa forma conseguimos manter o ritmo de entregas, mas o código da alteração maior é "estocado". A última é criar um code flip, implementar todas as demandas no trunk só que deixando a grande alteração dormente e escondida. Apesar de incompleto e invisível para o usuário o código faz parte dos binários entregues, passou pela integração contínua e por QA - já está sendo aceito, está implantado!<br />
<br />
Alguns pontos de atenção caso opte pela <i>feature flag</i>:<br />
<ul><li>Aposentar os code flips e garantir a remoção do código obsoleto;</li>
<li>Manter ambos os caminhos (code paths) enquanto as duas versões coexistirem.</li>
</ul>Volto a mencionar que esse tipo de decisão precisa ser bem pensada. Já tivemos períodos tensos com bloqueamento de implantações - tanto com branches normais como abstratas. Se seu cliente estiver "mal acostumado" com várias entregas por semana, por exemplo, e você começar a faze-lás quinzenalmente, pode ter certeza que ele vai reclamar. Além do que, a sensação de receber software concreto quase que diariamente, é mil vezes melhor que um "<i>burndown</i> na pinta".</div><br />
É importante ressaltar que apesar do título, não estou dizendo que nunca criarei branches ou que é sempre melhor evitá-los. Sei que existem casos onde não há outra saída. O importante é saber tomar esse tipo de decisão - focar nas entregas mais frequentes.<br />
<br />
<div><span class="Apple-style-span" style="font-size: x-large; font-weight: bold;">Para se aprofundar melhor:</span><br />
<ul><li>Leia o <a href="http://continuousdelivery.com/">Continuous Delivery</a>.</li>
<li>A definição de <a href="http://paulhammant.com/blog/branch_by_abstraction.html">branch by abstraction de Paul Hammant</a>.</li>
<li>Como o time do <a href="http://code.flickr.com/blog/2009/12/02/flipping-out/">Flickr implementa as feature flags e feature flippers</a>. </li>
<li>Martin Fowler falando sobre as <a href="http://martinfowler.com/bliki/FeatureToggle.html">feature toggles</a>.</li>
<li>Uma palestra do InfoQ sobre uma solução mais sofisticada de <a href="http://www.infoq.com/presentations/Feature-Bits">feature bits</a>.</li>
</ul></div><div><div></div></div>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-56074469218326491962010-10-29T00:27:00.005-02:002011-11-10T12:08:00.001-02:00Using Text instead of String to store large texts in GAE's datastoreI just started playing with <a href="http://gaelyk.appspot.com/">Gaelyk</a> and <a href="http://code.google.com/appengine/">GAE</a>. The goal is to learn the GAE APIs and services and improve my groovy foo!<br />
So, in order to practice it I've chosen to develop a simple pastebin like application using Gaelyk.<br />
<br />
I started it by modeling the main entity: Snippet, which is a very simple POGO with only 3 properties:<br />
<br />
<pre class="brush: groovy">class Snippet implements Serializable {
String name
String text
String language
}
</pre><br />
The groovylet below ilustrates how we can easily instantiate a new entity from the posted request parameters and then persist it into the data store.<br />
<br />
<pre class="brush: groovy">def snippet = new Snippet(params);
// coerce a POJO into an entity
def snippetEntity = snippet as Entity
snippetEntity.save()
forward "/snippet.gptl"
</pre><br />
So far so good. After some tests I got the following error:<br />
<br />
<pre>Error: GroovyServlet Error: script: '/WEB-INF/groovy/snippet/create.groovy': Script processing failed.name: <span class="Apple-style-span" style="color: #990000;"><b>String properties must be 500 characters or less. Instead, use com.google.appengine.api.datastore.Text, which can store strings of any length</b></span>.com.google.appengine.api.datastore.DataTypeUtils.checkSupportedSingleValue(DataTypeUtils.java:192)
</pre><br />
The message is very self explanatory. I changed the Snippet and the groovylet respectively to:<br />
<br />
<pre class="brush: groovy">import com.google.appengine.api.datastore.Text
class Snippet implements Serializable {
String name
Text text
String language
}
// create.groovy
def snippet = new Snippet();
snippet.name = params.name;
snippet.language = params.language;
snippet.text = params.text as Text;
def snippetEntity = snippet as Entity
snippetEntity.save()
</pre><br />
Note that I changed property type from String to Text and had to set each property separately in the groovylet because there is no autocasting support for Text types.<br />
<br />
You can check the last deployed version in AppEngine here: <a href="http://snippetr.appspot.com/">Snippetr</a><br />
And the source code here: <a href="http://code.google.com/p/snippetr/">http://code.google.com/p/snippetr/</a>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-22453332114175024452010-07-22T10:12:00.002-03:002010-07-24T12:20:11.896-03:00Great Tool for "Cyber" Dojos!Just tried cyber dojo.<br />
Great tool!<br />
<br />
Check it out:<br />
<br />
<a href="http://www.cyber-dojo.com/">http://www.cyber-dojo.com/</a>Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0tag:blogger.com,1999:blog-4946206839402560285.post-7802635309564629552010-07-17T13:04:00.352-03:002010-07-27T23:29:26.530-03:00Powerful Excel Data Driven Tests with NUnitCreating a good suite of automated data driven tests calls for an easier and more maintainable way for inputing data and asserting output results - it should be easy for everyone (team members and even customers) to modify and create new scenarios.<br />
<br />
Often the complexity involved on scenario creation is not technical at all, the business rules normally makes it much more difficult. That´s why we need better tools/GUI for creating and maintaining these scenarios.<br />
<br />
Turns out spreadsheet softwares like Excel happens to be the right tool for the job - well known GUI, capable of performing complex calculations and very good for organizing and structuring data. Then, why not take advantage of such powerful tools integrating them with an XUnit framework? Well that´s what this post is about.<br />
<br />
<h3>Integrating NUnit Parameterized Tests and Excel Data Reader</h3><br />
Recently I had the same need and since I´m on a .NET project, I came up with a helper class for creating <a href="http://www.nunit.org/">NUnit</a>´s data driven testcases from Excel spreadsheets: <b><a href="http://exceldatadriventests.codeplex.com/">ExcelTestCaseDataReader</a></b> (this could probably be achieved by using any XUnit framework and Excel library in any language or plataform).<br />
<br />
<b><a href="http://exceldatadriventests.codeplex.com/">ExcelTestCaseDataReader</a></b> basically provides a fluent interface (this was my first time applying the builder pattern for creating such an interface so don´t be too demanding) which encapsulates the complexity for reading excel spreadsheets - either from an embedded resource or from the file system, using the (very good) <a href="http://exceldatareader.codeplex.com/">Excel Data Reader</a> library - and turns them into <a href="http://www.nunit.org/">NUnit</a>´s testcases.<br />
<br />
Fortunately <a href="http://www.nunit.org/">NUnit</a> has some very good and flexible built-in features which help us creating this kind of <a href="http://www.nunit.org/index.php?p=parameterizedTests&r=2.5">parameterized tests</a>. Combining <a href="http://www.nunit.org/index.php?p=testCaseSource&r=2.5.5">NUnit´s TestCaseSource attribute</a> and the ExcelTestCaseDataReader does the magic.<br />
<br />
<h2>Setting things up</h2><br />
The example below shows how to create and configure an <b>ExcelTestCaseDataReader</b> instance which will load the <em>tests.xls</em> workbook from the file system and include the content of Sheet1 and Sheet2.<br />
<br />
<pre class="brush: c#">var testCasesReader = new ExcelTestCaseDataReader()
.FromFileSystem(@"e:\testcases\tests.xls")
.AddSheet("Sheet1")
.AddSheet("Sheet2");
</pre><br />
Then , to get the <a href="http://www.nunit.org/index.php?p=testCaseSource&r=2.5.5">TestCaseData</a> list, you should invoke the GetTestCases method passing it a delegate which should know how to extract the details from each row and transform them into a <a href="http://www.nunit.org/index.php?p=testCaseSource&r=2.5.5">TestCaseData</a>.<br />
<br />
<pre class="brush: c#">var testCases = testCasesReader.GetTestCases(delegate(string sheetName, DataRow row, int rowNum)
{
var testName = sheet + rowNum;
IDictionary testDataArgs = new Hashtable();
var testData = new TestCaseData(testDataArgs);
return testData;
}
);
</pre><br />
<h2>Adding to NUnit</h2><br />
Now you just need a test method which uses the TestCaseSource attribute. There are numerous options for yielding the data, and one of them is to define an IEnumerable<testcasedata> as a public method of your test class. In the example below the MyTest method takes its TestCases from a static method called "SampleTestCaseData".<br />
<br />
<pre class="brush: c#">[Test]
[TestCaseSource("SampleTestCaseData")]
public void MyTest(IDictionary testData)
{
//do the assertions here
}
</pre><br />
And finally the SampleTestCaseData snippet. It just iterates on the testCases list we created earlier (with <b>ExcelTestCaseDataReader</b>) and yields each of them.<br />
<br />
<pre class="brush: c#">public static IEnumerable<TestCaseData> SampleTestCaseData
{
get
{
foreach (TestCaseData testCaseData in testCases)
{
yield return testCaseData;
}
}
}
</pre><br />
<h2>Conclusion</h2><br />
NUnit is a great testing tool and has great data driven tests capabilities. Sometimes you need to provide non tech people the ability to maintain test scenarios or maybe just need a better tool - with a good and user friendly GUI - for scenario creation.<br />
<br />
The <b><a href="http://exceldatadriventests.codeplex.com/">ExcelTestCaseDataReader</a></b> is just a basic example of what can be achieved by integrating excel (front end) and nunit (back end). And how it could help alleviating the burden of such boring tasks.<br />
<br />
This small framework has been very useful for me and my team and although is´s very limited it can be easily extended to achieve your own needs. <br />
<br />
Looking forward to hear about different approaches on data driven tests automation!Anonymoushttp://www.blogger.com/profile/01404664849725816336noreply@blogger.com0