Vitor Pamplona

Innovation on Vision: Imaging , Enhancement and Simulation

Teste com Código Limpo: Wicket vs Struts 2 vs Rails

Farei uma análise rápida, comparando o código de testes funcionais - do Controller - entre Struts 2, Wicket e Rails. Eu não vou explicar o código dos testes que estou usando, porque acredito que você leitor, como estudante de computação, tem a obrigação de entender o que está acontecendo, mesmo se estas linguagens de programação não sejam de seu profundo conhecimento. Vamos lá.

Testes em Struts 2

Primeiramente, crie uma classe base para todos os teus testes. Ela vai mockear tudo o que o servidor web provê e, assim, garantir que você possa criar as actions.

import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ActionProxyFactory;
import junit.framework.TestCase;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.views.JspSupportServlet;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ContextLoader;

import java.util.HashMap;

/**
* @author Zarar Siddiqi
*/
public abstract class BaseStrutsTestCase extends TestCase {
private static final String CONFIG_LOCATIONS =
"META-INF/applicationContext-app.xml," +
"META-INF/applicationContext-security.xml";
private static ApplicationContext applicationContext;
private Dispatcher dispatcher;
protected ActionProxy proxy;
protected static MockServletContext servletContext;
protected static MockServletConfig servletConfig;
protected MockHttpServletRequest request;
protected MockHttpServletResponse response;

public BaseStrutsTestCase(String name) {
super(name);
}

/**
* Created action class based on namespace and name
* @param clazz Class for which to create Action
* @param namespace Namespace of action
* @param name Action name
* @return Action class
* @throws Exception Catch-all exception
*/
@SuppressWarnings("unchecked")
protected <T> T createAction(Class<T> clazz, String namespace,
String name) throws Exception {

// create a proxy class which is just a wrapper around the action call.
// The proxy is created by checking the namespace and name against the
// struts.xml configuration
proxy = dispatcher.getContainer().getInstance(ActionProxyFactory.class).
createActionProxy(
namespace, name, null, true, false);

// by default, don't pass in any request parameters
proxy.getInvocation().getInvocationContext().
setParameters(new HashMap());

// do not execute the result after executing the action
proxy.setExecuteResult(true);

// set the actions context to the one which the proxy is using
ServletActionContext.setContext(
proxy.getInvocation().getInvocationContext());
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
ServletActionContext.setRequest(request);
ServletActionContext.setResponse(response);
ServletActionContext.setServletContext(servletContext);
return (T) proxy.getAction();
}

protected void setUp() throws Exception {
if( applicationContext == null ) {
// this is the first time so initialize Spring context
servletContext = new MockServletContext();
servletContext.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,
CONFIG_LOCATIONS);
applicationContext = (new ContextLoader())
.initWebApplicationContext(servletContext);

// Struts JSP support servlet (for Freemarker)
new JspSupportServlet().init(new MockServletConfig(servletContext));
}
// Dispatcher is the guy that actually handles all requests. Pass in
// an empty. Map as the parameters but if you want to change stuff like
// what config files to read, you need to specify them here. Here's how to
// scan packages for actions (thanks to Hardy Ferentschik - Comment 66)
// (see Dispatcher's source code)
HashMap params = new HashMap();
params.put("actionPackages", "com.arsenalist.action");
dispatcher = new Dispatcher(servletContext, params);
dispatcher.init();
Dispatcher.setInstance(dispatcher);
}
}

Após esta classe básica criada, pode-se programar os testes. No caso abaixo, estaremos testando o comando de delete de um CRUD de Pessoa (PersonAction):

public class PersonActionTest extends BaseStrutsTestCase { 

/**
* Invoke all interceptors and specify value of the action
* class' domain objects directly.
* @throws Exception Exception
*/
public void testInterceptorsBySettingDomainObjects()
throws Exception {
PersonAction action = createAction(PersonAction.class,
"/site", "deletePerson");
action.setId(123);
String result = proxy.execute();
assertEquals(result, "success");
}
}

Testes com Struts 2 são simples, graças ao WebWork, diga-se de passagem. Como a action está disponível para você e ela já tem os métodos públicos que a view chamará, você pode utilizá-los para validar os resultados esperados. No caso do Struts, você trabalha apenas com código Java, usando as classes que você mesmo criou, com pouca participação do framework no código.

Lembre-se que " mockeamos " o servidor web, assim você tem os dados da seção - importantes para testar logins por exemplo. Se você usa Spring, lembre-se de adaptar também beans inseridos por DI, caso contrário o acesso ao banco, por exemplo, pode falhar. No caso acima, assume-se que a conexão é iniciada, de alguma forma, nas suas classes (com um Singleton segurando a conexão, por exemplo).

Fonte dos códigos: http://arsenalist.com/2007/06/18 /

Testes com Wicket

Wicket, o MVC java do momento, deixa um pouco a desejar com seu jeito de testar. O Wicket já vem com alguns mocks, por isso, basta escrever o seu teste. Por exemplo, para um relatório:

public void testGenerateQueryReport() throws UnsupportedEncodingException {
// this one is a bit more interesting, as this
// page does a download-on-submit
... prepare the data for the test
// prepare the form, fill the data, and submit
final FormTester form = prepareFormTester();
form.setValue("dataType", "0:form:dataType:query");
form.submit();
// check that the submit created a download link
final MockHttpServletResponse servletResponse =
tester.getServletResponse();
// in this case it's a CSV report, so just convert the whole thing to a string
final String report = new String(servletResponse.getBinaryContent(),
servletResponse.getCharacterEncoding());
// compare it
assertEquals("QUERY COUNT\n\n", report);
}

Trabalhar com estes forms com parâmetros malucos não é nada simples, ter que declarar um servletResponse é meio chato, mas o código até que fica enxuto. No caso de validar uma saída, o código não muda muito.

public void testRender() {
// add some values directly to the database, to check the rendering
// (services are not flexible enough for me)
// template here is a Spring JDBCTemplate, which is quite useful for
// running direct SQL queries without much red-tape
template.execute(
"update config set value='sampleuser' where name='userName';" +
"update config set value='samplepass' where name='password';" +
"update config set value='true' where name='allowOffsiteAccess';"+
"update config set value='sampleiprange,anotherip'" +
" where name='trustedIPRange';" +
"commit;");
// start the page - my page is package protected for better
// encapsulation, so I need an ITestPageSource
// if the page takes parameters, just pass them to the
// constructor instead of setting them
// through the database (that allows for better reuse)
// [tester] here is an instance of my AppTester described above
tester.startPage(new ITestPageSource() {
public Page getTestPage() { return new ChangeOffSiteAccessPage(); }
});
// check that the right page was rendered
// (no unexpected redirect or intercept)
tester.assertRenderedPage(ChangeOffSiteAccessPage.class);
// assert that there's no error message
tester.assertNoErrorMessage();
// check that the right components are in the page
tester.assertComponent("feedback", FeedbackPanel.class);
tester.assertComponent("form", Form.class);
// ok, now check not only that the component is present,
// but also that the model object
// contains the correct value (was correctly bound)
tester.assertComponent("form:username", TextField.class);
final TextField usernameField = (TextField) tester.
getComponentFromLastRenderedPage("form:username");
assertEquals("sampleuser", usernameField.getModelObject());
... other components
}

Infelizmente, o desenvolvedor do teste precisa fazer uso do framework várias vezes. Os asserts deixam de ser " padrões " para serem os asserts do Wicket, fato que prejudica a adaptação de novos desenvolvedores e até mesmo a simpatia do código.

Fonte: http://cwiki.apache.org/WICKET/testing-pages.html

Testes em Rails

Em rails não há muito o que fazer.

# this test proves that fetching a movie works
def test_successfully_finding_a_movie
get :movie, "id" => "1"
assert_not_nil assigns["movie"]
assert_equal 1, assigns["movie"].id
assert flash.empty?
end

Para quem está acostumado com Ruby, esta sintaxe não é estranha. Em uma associação simples, este código está mais agradavelmente similar ao código do Struts 2 do que o do Wicket para programadores java. O Código abaixo mostra um teste de uma action de login.

def test_login_with_remember_me
post :login, :username => 'herry', :password => 'passwd',
:rememberme => '1'
assert session[:user]
assert cookies['auth_token']
assert_response :redirect
assert_tag :tag => 'div',
:child => /[replace with your error message]/

# reset user session and set request cookie
session[:user] = nil
@request.cookies['auth_token'] = cookies['auth_token']

get :protected_page
assert_response :success
assert session[:user]
end

Fontes: http://www.pluitsolutions.com/2006/08/02/rails-functional-test-with-cookie / e http://manuals.rubyonrails.com/read/chapter/28

Conclusões

Como vocês mesmos viram, o tamanho do código de teste é o que menos importa, pois é muito semelhante nos três frameworks. O trabalho do programador é o mesmo nos três casos, e é concluído em tempos muito próximos. Ou seja, a produtividade para a criação de testes comparando Wicket, Struts 2 e Rails é a mesma.

A performance de execução dos testes, nos casos apresentados, também é a mesma, visto que grande parte do tempo gasto durante o processamento não foi nas classes de testes, mas sim naquelas que estão sendo testadas. Portanto, criar o framework mais rápido para execução da classe de testes não faz muito sentido.

O último quesito é o código claro, limpo, bonito e facilmente entendido. Pode-se colocar o Wicket em último lugar, visto que tem o código mais inchado e preso ao framework dos três. É dífícil olhar para aquele código e, em menos de um segundo, saber exatamente o que está sendo testado. Você precisa quase que debugar mentalmente até saber exatamente o que o código faz.

Desconsiderando aquela classe base do Struts, o código fica muito bonito e claro, assim como o código do Rails. Como eu conheço bem ambos, posso afirmar que os testes tem uma estrutura muito próxima, sendo difícil, se possível, escolher o melhor entre eles. A única linha que não fica muito boa é aquela onde é criado a PersonAction, que você precisa passar a classe, a namespace e o método. No caso do Rails não é necessário criar nada, e basta informar o método a ser invocado, mas não é tão feio quanto o Wicket.

Posted in Aug 8, 2008 by Vitor Pamplona - Edit - History

Showing Comments

Olá,

Legal, eu prefiro fazer testes funcionais com o selenium, mas cada um é cada um.
Uma coisa que não captei direito foi o pq foi criado essas classes no teste do struts MockServletContext, MockServletConfig, MockHttpServletRequest, MockHttpServletResponse, sendo que cada uma delas tem uma interface e vc pode e acho que é o certo " mockar " as interfaces??

Valeu.
Paulo.

- - Paulo

- - Posted in Feb 23, 2008 by 189.26.8.85

Cara. vc está comparando maçãs com laranjas... Se você não entendeu os testes do Wicket então diga que não entendeu, ao invés de falar que ficaram bonitos...

Acho que antes de postar sobre alguma coisa tem que aprender antes

- -. net

- - Posted in May 15, 2009 by 189.61.49.34

concordo com o colega acima... no caso do wicket, sua opiniao é tipica de alguem que sequer botou a mao na ferramenta.


- - wicketMan

- - Posted in Aug 11, 2009 by 189.4.78.45

Add New Comment

Your Name:


Write the code showed above on the text below.