principaiprincipais frameworks e documentação

Post on 18-Dec-2014

1.140 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

 

TRANSCRIPT

Cristina T. CerdeiralPeter P. Lupo

Treinamento TABA

PrincipaisPrincipais FrameworksFrameworks eeDocumentaDocumentaççãoão

Apache Log4jApache Log4j

Tópicos• Logging• Antes do Log4j• Exemplo de API de Logging• Log4j• Eventos de Log• Logger• Level• Exemplo• Appenders & Layouts• Appenders Disponíveis• Layouts Disponíveis• PatterLayout• Configuração

• BasicConfigurator• PropertyConfigurator• DOMConfigurator• Performance• Hierarquia de Loggers

LoggingLogging

• “Diário de bordo”� Histórico de execução

• Depuração� Vantagens

� Instruções persistentes

� Sessões persistentes

� Multithreading

� Procedimentos recursivos

Antes do Log4jAntes do Log4j

publicpublic staticstatic voidvoid mainmain (String[](String[] argsargs) {) {SystemSystem.out..out.printlnprintln((““Vai executar oVai executar o mainmain:: argsargs == ”” ++ argsargs););//...//...

}}

privateprivate staticstatic finalfinal booleanboolean DEBUG =DEBUG = truetrue;;

publicpublic staticstatic voidvoid mainmain (String[](String[] argsargs) {) {ifif (DEBUG) {(DEBUG) {

SystemSystem.out..out.printlnprintln((““Vai executar oVai executar o mainmain:: argsargs == ”” ++ argsargs););//...//...

}}}}

Exemplo de API deExemplo de API de LoggingLogging

publicpublic classclass LogLog {{protectedprotected staticstatic javajava..ioio..PrintStreamPrintStream streamstream;;protectedprotected staticstatic booleanboolean enabledenabled == truetrue;;

publicpublic staticstatic voidvoid printprint ((ObjectObject o) {o) {ifif ((enabledenabled))

streamstream..printlnprintln(o.(o.toStringtoString());());}}

}}

publicpublic staticstatic voidvoid mainmain (String[](String[] argsargs) {) {LogLog..printprint((““Vai executar oVai executar o mainmain:: argsargs == ”” ++ argsargs););

}}

Log4jLog4j

• Projeto da Apache

� http://logging.apache.org/log4j/

• API de Logging� Categorias de logging

� Níveis (prioridades)

� Formatação dos eventos de logging

� Saídas para os eventos de logging

� Informações de Contexto

� Configurável

Eventos deEventos de LogLog

• Horário• Prioridade• Categoria• Mensagem• Arquivo fonte• Número da linha• Nome da thread• ...

LoggerLogger

• Gerador de eventos de logging� org.apache.log4j.Logger

• Categorias de logging• Criando um Logger

LoggerLogger loglog == LoggerLogger..getLoggergetLogger((““categoriacategoria””););LoggerLogger loglog == LoggerLogger..getLoggergetLogger((AClassAClass..classclass););

LevelLevel

• Prioridade de Logging� Constantes definidas em org.apache.log4j.Level

� DEBUG < INFO < WARN < ERROR < FATAL

• Emitindo mensagens

loglog.debug(.debug(““mensagemmensagem””););loglog..infoinfo((““mensagemmensagem””););loglog..warnwarn((““mensagemmensagem””););loglog..errorerror((““mensagemmensagem””););loglog.fatal(.fatal(““mensagemmensagem””););

LevelLevel

• Uma requisição de log é dita habilitada se seu level for maiorou igual ao level do seu logger

• Caso contrário, a requisição é dita desabilitada

ExemploExemplo

LoggerLogger loggerlogger == Logger.getLogger("com.fooLogger.getLogger("com.foo");");

logger.setLevel(Level.INFOlogger.setLevel(Level.INFO););

////EssaEssa requisirequisiççãoão éé habilitadahabilitada,, porqueporque WARN >= INFO.WARN >= INFO.logger.warn("Lowlogger.warn("Low fuel level.");fuel level.");

////EssaEssa requisirequisiççãoão éé desabilitadadesabilitada,, porqueporque DEBUG < INFO.DEBUG < INFO.logger.debug("Startinglogger.debug("Starting search for nearest gas station.");search for nearest gas station.");

AppendersAppenders && LayoutsLayouts

• Saída para eventos de logging� Interface org.apache.log4j.Appender

• Formatação de eventos de logging� Interface org.apache.lo4j.Layout

AppendersAppenders DisponDisponííveisveis

Servidor acessível via telnetorg.apache.log4j.net.TelnetAppender

UNIX syslog daemonorg.apache.log4j.net.SyslogAppender

NT Event Logorg.apache.log4j.nt.NTEventLogAppender

E-mailorg.apache.log4j.net.SMTPAppender

Arquivo (após um certo tamanho,faz backup do antigo e começaum novo)

org.apache.log4j.RollingFileAppender

Arquivoorg.apache.log4j.FileAppender

Console (System.out ouSystem.err)

org.apache.log4j.ConsoleAppender

SaídaAppender

LayoutsLayouts DisponDisponííveisveis

XMLorg.apache.log4j.xml.XMLLayout

HTMLorg.apache.log4j.HTMLLayout

Depende da string de formataçãoorg.apache.log4j.PatternLayout

“176 [thread] INFO categoria –mensagem”

org.apache.log4j.TTCCLayout

“DEBUG – mensagem”org.apache.log4j.SimpleLayout

FormatoLayout

PatternLayoutPatternLayout

• As seguintes opções estão disponíveis:� c: Categoria do evento de log� C: Nome da classe que fez a rwuisição de log� d: Data do evento de log. Deve ser seguido de um formater de data

entre chaves. Por exemplo, %d{HH:mm:ss,SSS} ou %d{dd MMMyyyy HH:mm:ss,SSS}. Se nenhum formater for fornecido, o formatoda ISO8601 será utilizado

� F: Nome do arquivo da requisição do evento de log� l: Localização da classe que invocou a requisição que gerou o evento de

log� L: Número da linha da qual a requisição do evento de log foi disparada� n: Separador de linha dependente de plataforma� M: Nome do método no qual o evento de log foi lançado� p: Prioridade do evento de log� t: Nome da thread que gerou o evento de log� m: Mensagem de log

ConfiguraConfiguraççãoão

• Programática

loglog..addAppenderaddAppender((appenderappender););loglog..setLevelsetLevel((levellevel););

• Configurator� Básico (BasicConfigurator)� Arquivo de Properties (PropertyConfigurator)� Arquivo XML (DOMConfigurator)

• Automática� log4j.xml� log4j.properties

BasicConfiguratorBasicConfigurator

• BasicConfigurator� org.apache.log4j.BasicConfigurator

publicpublic classclass MyAppMyApp {{staticstatic LoggerLogger loggerlogger == LoggerLogger..getLoggergetLogger((MyAppMyApp..classclass););publicpublic staticstatic voidvoid mainmain(String[](String[] argsargs) {) {

BasicConfiguratorBasicConfigurator.configure();.configure();loggerlogger..infoinfo("("EnteringEntering applicationapplication.");.");

BarBar barbar = new Bar();= new Bar();bar.bar.doItdoIt();();loggerlogger..infoinfo("("ExitingExiting applicationapplication.");.");}}

}}

0 [main] INFO MyApp - Entering application.51 [main] INFO MyApp - Exiting application.

PropertyConfiguratorPropertyConfigurator

• PropertyConfigurator� org.apache.log4j.PropertyConfigurator

• Arquivo de Configuração

log4j.log4j.appenderappender..nomeDoAppendernomeDoAppender=classe.do.=classe.do.appenderappenderlog4j.log4j.appenderappender..nomeDoAppendernomeDoAppender..properiedadeproperiedade=valor=valor......log4j.log4j.appenderappender..nomeDoAppendernomeDoAppender.layout=classe.do.layout.layout=classe.do.layoutlog4j.log4j.appenderappender..nomeDoAppendernomeDoAppender.layout..layout.properiedadeproperiedade=valor=valor......log4j.log4j.appenderappender..nomeDaCategorianomeDaCategoria=n=níível,vel, nomeDoAppendernomeDoAppender, ..., ...

PropertyConfiguratorPropertyConfigurator

publicpublic classclass MyAppMyApp {{

staticstatic LoggerLogger loggerlogger == LoggerLogger..getLoggergetLogger((MyAppMyApp..classclass..getNamegetName());());

publicpublic staticstatic voidvoid mainmain(String[](String[] argsargs) {) {//// BasicConfiguratorBasicConfigurator substitusubstituíído pordo por PropertyConfiguratorPropertyConfigurator..PropertyConfiguratorPropertyConfigurator.configure(.configure(argsargs[0]);[0]);loggerlogger..infoinfo("("EnteringEntering applicationapplication.");.");BarBar barbar = new Bar();= new Bar();bar.bar.doItdoIt();();loggerlogger..infoinfo("("ExitingExiting applicationapplication.");.");

}}}}

PropertyConfiguratorPropertyConfigurator

log4j.log4j.rootLoggerrootLogger=DEBUG, A1=DEBUG, A1log4j.log4j.appenderappender.A1=org.apache.log4j..A1=org.apache.log4j.ConsoleAppenderConsoleAppenderlog4j.log4j.appenderappender.A1.layout=org.apache.log4j..A1.layout=org.apache.log4j.PatternLayoutPatternLayoutlog4j.log4j.appenderappender.A1.layout..A1.layout.ConversionPatternConversionPattern=%d [%t] %=%d [%t] %--5p %c5p %c --

%m%n%m%nlog4j.log4j.logger.comlogger.com..foofoo=WARN=WARN

2000-09-07 14:07:41,508 [main] INFO MyApp - Entering application.2000-09-07 14:07:41,529 [main] INFO MyApp - Exiting application.

DOMConfiguratorDOMConfigurator

• DOMConfigurator� org.apache.log4j.xml.DOMConfigurator

• Arquivo de Configuração

<<configurationconfiguration>><<appenderappender namename==““nomeDoAppendernomeDoAppender”” classclass==““classe.do.classe.do.appenderappender””>> <param<paramnamename==““propriedadepropriedade”” valuevalue==““valorvalor”” />/>

<layout<layout classclass==““classe.do.layoutclasse.do.layout””>><param<param namename==““propriedadepropriedade”” valuevalue==““valorvalor”” />/>

</layout></layout></</appenderappender>><<categorycategory>>

<<prioritypriority namename==““nníívelvel”” />/><<appenderappender--refref refref==““nomedoAppendernomedoAppender”” />/>

</</categorycategory>></</configurationconfiguration>>

DOMConfiguratorDOMConfigurator

<?xml version="1.0" encoding="UTF<?xml version="1.0" encoding="UTF--8" ?>8" ?><!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"><!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"><log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">j/"><<appenderappender name="name="appenderappender""

class="org.apache.log4j.FileAppender">class="org.apache.log4j.FileAppender"><<paramparam name="File" value="name="File" value="IndentifyIndentify--Log.txtLog.txt"/>"/><<paramparam name="Append" value="false"/>name="Append" value="false"/><layout class="org.apache.log4j.PatternLayout"><layout class="org.apache.log4j.PatternLayout"><<paramparam name="name="ConversionPatternConversionPattern" value="%d [%t] %p" value="%d [%t] %p -- %%m%nm%n"/>"/>

</layout></layout></</appenderappender>><root><root><priority value ="debug"/><priority value ="debug"/><<appenderappender--ref ref="ref ref="appenderappender"/>"/>

</root></root></log4j:configuration></log4j:configuration>

PerformancePerformance

• Evitando o custo da construção da mensagem:

loglog..isDebugEnabledisDebugEnabled();();

loglog..isInfoEnabledisInfoEnabled();();

......

loglog..isFatalEnabledisFatalEnabled();();

• Quando a requisição está habilitada, duplica o esforço

Hierarquia deHierarquia de LoggersLoggers

• Membros separados por “.”• br.ufrj.cos.lens.Processo

� Logger br� Logger br.ufrj� Logger br.ufrj.cos� Logger br.ufrj.cos.lens� Logger br.ufrj.cos.lens.Processo

Hierarquia deHierarquia de LoggersLoggers

• Herança de nível entre loggers� Se um logger não tem um nível explicitamente atribuído, é assumido o

nível explicitamente atribuído ao logger ancestral mais próximo

• Herança de appenders entre loggers� Um evento de logging é emitido para todos os appenders registrados no

logger, e para todos appenders registrados nos loggers ancestrais

JUnitJUnit

Tópicos• Tipos de Testes• O que é JUnit?• Para que serve?• Como usar o JUnit?• JUnit 4• Exemplo• Asserções• Fixtures• Exceções Esperadas• Outras Anotações• Teste Siutações de Falha• Limitações do JUnit• Como escrever bons testes?• Como descobrir testes?• Testes como documentação

Tipos de TestesTipos de Testes

• Testes de Unidade� Testam unidades de lógica. Em linguagens orientadas a objetos,

unidades geralmente representam métodos, mas podem tambémrepresentar um objeto ou ainda um estado de um método

� Ignoram condições ou dependências externas. Testes de unidade usamdados suficientes para testar apenas a lógica da unidade em questão

• Testes de Integração� Testam como uma coleção de unidades interage entre si ou com o

ambiente onde executam

• Testes Funcionais (caixa-preta)� Testam casos de uso de uma aplicação. Validam a interface do usuário,

operações requisitadas, etc.

O queO que éé JUnitJUnit??

• Um framework que facilita o desenvolvimento e execução detestes de unidade em código Java� Uma API para construir os testes: junit.framework.*� Aplicações para executar testes: TestRunner

Para que serve?Para que serve?

• “Padrão” para testes de unidade em Java� Desenvolvido por Kent Beck e Erich Gamma� Design muito simples

• Testar é uma boa prática, mas é chato. JUnit torna as coisasmais agradáveis, facilitando:� A criação e execução automática dos testes� A apresentação dos resultados

• JUnit pode verificar se cada unidade de código funciona daforma esperada� Permite agrupar e rodar vários testes ao mesmo tempo� Na falha, mostra a causa em cada teste

• Serve de base para extensões

Como usar oComo usar o JUnitJUnit??

• Há várias formas de usar o JUnit. Depende da metodologia detestes que está sendo usada� Código existente: precisa-se escrever testes para classes que já foram

implementadas� Desenvolvimento guiado por testes (TDD): código novo só é escrito se

houver um teste sem funcionar• Onde obter o JUnit?

� www.junit.org• Como instalar?

� Incluir o arquivo junit.jar no classpath para compilar e rodar osprogramas de teste

• Extensões do JUnit� Permitem usá-lo para testes funcionais e de integração

JUnitJUnit 44

• A versão 4 do JUnit acabou de ser lançada (abril de 2006)• Ela usa as ferramentas disponíveis na versão 5 do Java para

fazer com que desenvolver os testes seja ainda mais fácil

• Vamos apresentar o JUnit 4• É usado com adaptadores em aplicativos antigos• Em breve poderemos abandonar os adaptadores

ExemploExemplo

AsserAsserççõesões

• Asserções são métodos de junit.framework.Assert� Afirmam que certas condições são verdadeiras� Causam AssertionFailedError se falharem

• Principais asserções

assertEqualsassertEquals((objetoEsperadoobjetoEsperado,, objetoRecebidoobjetoRecebido))assertTrueassertTrue((valorBooleanovalorBooleano))assertNotNullassertNotNull(objeto)(objeto)assertSameassertSame((objetoUmobjetoUm,, objetoDoisobjetoDois))failfail()()

FixturesFixtures

• São dados reutilizados por vários testes• Devem conter apenas dados suficientes• O JUnit 4 provê duas novas anotações para os métodos de set

up e tear down:� @Before: Método anotado com @Before executa antes de todo teste� @After: Método anotado com @After executa depois de cada teste

• Podemos ter quantos @Before e @After quantos foremnecessários

• É possível herdar dos métodos @Before e @After. O JUnitexecuta os métodos @Before da superclasse antes dos dasubclasse

FixturesFixtures

FixturesFixtures

• One-time set up e tear down� JUnit4 provê as anotações @BeforeClass and @AfterClass para one-

time set up e tear down� Estas anotações rodam o código do set up uma vez antes de todos os

testes e do tear down uma vez depois de todos os testes

FixturesFixtures

ExceExceçções Esperadasões Esperadas

• É tão importante testar o cenário de falha do seu código quantoo de sucesso

• O JUnit 4 facilita o teste quando uma exceção é esperada. Aanotação @Test recebe a exceção que deve ser lançada pelométodo de teste como parâmetro

ExceExceçções Esperadasões Esperadas

Outras AnotaOutras Anotaççõesões

• Ignorando um teste� A anotação @Ignore faz com que o teste não seja rodado e seja

relatado que não foi rodado� Pode-se passar uma String como parâmetro para a anotação @Ignore

que explique porque o teste foi ignorado.

• Determinando o tempo limite de um teste� Você pode passar um tempo limite como parâmetro para a anotação test

para especificar um período em limissegundos� Se o teste levar mais que esse tempo, ele falha

Teste SituaTeste Situaçções de Falhaões de Falha

• É tão importante o cenário de falha do seu código quanto o desucesso

• Método fail() provoca uma falha� Use para verificar se exceções ocorrem quando se espera que elas

ocorram

@@TestTest publicpublic voidvoid entityNotFoundExceptionentityNotFoundException() {() {resetEntityTableresetEntityTable();();trytry {{

ParameterEntityTagParameterEntityTag tagtag == parserparser..resolveEntityresolveEntity((““bogusbogus””););failfail((““Devia ter causado a exceDevia ter causado a exceççãoão EntityNotFoundExceptionEntityNotFoundException!!””););

} catch (} catch (EntityNotFoundExceptionEntityNotFoundException e) {e) {}}

}}

LimitaLimitaçções doões do JUnitJUnit

• Acesso aos dados de métodos sob teste� Métodos private e variáveis locais não podem ser testados com JUnit� Dados devem ser pelo menos friendly (package-private)

Como escrever bons testes?Como escrever bons testes?

• JUnit facilita bastante a criação e execução de testes,mas elaborar bons testes exige mais� O que testar? Com saber se testes estão completos?

• Teste tudo o que pode falhar� Métodos triviais (get/set) não precisam ser testados� E se houver uma rotina de validação no método set?

• É melhor ter testes a mais que testes a menos� Escreva testes curtos (quebre testes maiores)� Use assertNotNull() (reduz drasticamente erro de NullPointerException

difíceis de encontrar)� Reescreva e altere o design de seu código para que fique mais fácil de

testar: promove design melhor!

Como descobrir testes?Como descobrir testes?

• Listas de tarefas (to-do-list)� Comece implementando os testes mais simples e deixe os testes

“realistas” para o final� Requisitos, use-cases, diagramas UML: reescreva s requerimentos em

termos de testes� Quebre requisitos complexos em pedaços menores

• Bugs revelam testes� Achou um bug? Não conserte sem antes escrever um teste que o pegue

(se você não o fizer, ele volta!)• Descoberta de testes é atividade de análise e design

� Sugerem nomes e estrutura de classes da solução� Permitem que se decida sobre detalhes de implementação após a

elaboração do teste

Testes como documentaTestes como documentaççãoão

• Testes são documentação executável� Execute-os periodicamente para mantê-los atualizados� Use nomes significativos� Mantenha-os simples!

• As asserções do JUnit possuem um argumento para descrever o que estásendo testado� Quando presente é o primeiro argumento� A mensagem passada será mostrada em caso de falha� Use, sempre que possível

assertEqualsassertEquals((““ArrayArray deve coincidir!deve coincidir!””, esperado,, esperado, testArraytestArray););assertNotNullassertNotNull((““obj deve serobj deve ser nullnull!!””, obj);, obj);assertTrueassertTrue((““xyzxyz() deve retornar() deve retornar truetrue!!””, a., a.xyzxyz());());

DocumentaDocumentaççãoão

Tópicos• Comentários e Documentação• Tags do JavaDoc• Tipos de Documentação• Tags Comuns• Tags de Classes• Tags de Variáveis• Tags de Métodos

ComentComentáários e Documentarios e Documentaççãoão

• É possível gerar documentação automaticamente em Java pelouso de Comentários de Documentação

• O Java possui uma ferramenta para gerar a documentação aaprtir dos comentários inseridos no código fonte chamadaJavaDoc

• O JavaDoc extrai os comentários /** ... */, especiais dedocumentação embutidos no código, gerando um arquivo noformato html convencional

• O JavaDoc processa apenas comentários de documentaçãopara membros de classe declaradas como public ou protected

• Comentários em membros do tipo private ou friendly serãoignorados a menos que se explicite sua inlcusão

ComentComentáários e Documentarios e Documentaççãoão

• Há duas maneiras de se trabalhar com o JavaDoc:� Embutindo-se código HTML� Usando tags de documentação

• Embutindo HTML� O JavaDoc permite o uso de comandos HTML diretamente nos

comentários de documentação� É permitido o uso de qualquer comando de formatação, tal como <tt> e

<b>, mas não se pode fazer uso de comandos estruturais, tais como<h2> e <hr>; pois o JavaDoc já insere seus próprios comandosestruturais

ComentComentáários e Documentarios e Documentaççãoão

/**/**** ÉÉ posspossíível <b> atvel <b> atéé </b> gerar uma lista</b> gerar uma lista* <* <olol>>* <li> item um* <li> item um* <li> item dois* <li> item dois* <li> item três* <li> item três* </* </olol>>*/*/

• Os asteriscos iniciais das linhas são opcionais e ignorados peloJavaDoc

TagsTags dodo JavaDocJavaDoc

• Tags são comandos que permitem formatação adicional dadocumentação e são sempre iniciados por @:� @author� {@code}� {@docRoot}� @deprecated� @exception� {@inheritDoc}� {@link}� {@linkplain}� {@literal}� @param

� @return� @see� @serial� @serialData� @serialField� @since� @throws� {@value}� @version

Tipos de DocumentaTipos de Documentaççãoão

• Há três tipos principais de documentação: o de classes, o devariáveis e o de métodos

• Devido a diferenças entre esses tipos, algumas tags sãoexclusivas para cada um deles

TagsTags ComunsComuns

• Apenas as seguintes tags são utilizadas para qualquer tipo:� @see� @since� @deprecated� {@link}� {@linkplain}� {@docroot}

TagsTags de Classesde Classes

• As tags abaixo podem ser utilizadas para documentação declasses e interfaces:� @see� @since� @deprecated� @serial� @author� @version� {@link}� {@linkplain}� {@docRoot}

TagsTags de Classesde Classes

/**/*** A class representing a window on the screen.* A class representing a window on the screen.* For example:* For example:* <pre>* <pre>* Window win = new* Window win = new Window(parentWindow(parent););** win.showwin.show();();* </pre>* </pre>*** @author* @author SamiSami ShaioShaio* @version %I%, %G%* @version %I%, %G%* @see* @see java.awt.BaseWindowjava.awt.BaseWindow* @see* @see java.awt.Buttonjava.awt.Button*/*/

class Window extendsclass Window extends BaseWindowBaseWindow {{......

}}

TagsTags de Varide Variááveisveis

• As tags abaixo podem ser utilizadas para documentação devariáveis:� @see� @since� @deprecated� @serial� @serialField� {@link}� {@linkplain}� {@docRoot}� {@value}

TagsTags de Varide Variááveisveis

/**/*** The X* The X--coordinate of the component.coordinate of the component.*** @see #* @see #getLocationgetLocation()()*/*/

intint x = 1263732;x = 1263732;

TagsTags de Mde Méétodostodos

• As tags abaixo podem ser utilizadas para documentação demétodos e construtores:� @see� @since� @deprecated� @param� @return – não pode aparecer em construtores� @throws� @exception� @serialData -� {@link}� {@linkplain}� {@inheritDoc}� {@docRoot}

TagsTags de Mde Méétodostodos

/**/*** Returns the character at the specified index. An index* Returns the character at the specified index. An index* ranges from <code>0</code> to <code>length()* ranges from <code>0</code> to <code>length() -- 1</code>.1</code>.*** @* @paramparam index the index of the desired character.index the index of the desired character.* @return the desired character.* @return the desired character.* @exception* @exception StringIndexOutOfRangeExceptionStringIndexOutOfRangeException* if the index is not in the range <code>0</co* if the index is not in the range <code>0</code>de>* to <code>length()* to <code>length()--1</code>.1</code>.* @see* @see java.lang.Character#charValuejava.lang.Character#charValue()()*/*/public charpublic char charAt(intcharAt(int index) {index) {

......}}

FimFim

Parabéns!! Chegamos ao fim daapresentação de Frameworks eDocumentação!!

Agora sabemos criar aplicações queutilizam esses frameworks para torná-las mais robustas!

Próximos passos:Padrões de Projeto!

top related