Em minha app, todas as classes de domínio seguem a padronização:
IEntityprotected*IList também são protected inicializadas no construtorAbaixo segue um exemplo clássico de uma entidade de domínio:
public class CheckListItemTemplate : IEntity
{
public virtual int Id { get; protected set; }
public virtual string Text { get; set; }
public virtual CheckListItemTemplate Parent { get; set; }
public virtual IList<CheckListItemTemplate> Itens { get; protected set; }
public CheckListItemTemplate()
{
Itens = new List<CheckListItemTemplate>();
}
public void AddItem(CheckListItemTemplate item)
{
item.Parent = this;
Itens.Add(item);
}
}
*Isto porque o Id é gerado pelo banco e para não correr o risco de algum desenvolvedor tentar setar esta propriedade definimos os Ids como protected.
Temos um repositório fake genérico utilizado nos testes:
public class Repository<T> : IRepository<T>
where T : class, IEntity
{
private readonly IDictionary<int, T> _context = new Dictionary<int, T>();
public void Delete(T obj)
{
_context.Remove(obj.Id);
}
public void Store(T obj)
{
if (obj.Id > 0)
_context[obj.Id] = obj;
else
{
var generateId = _context.Values.Any() ? _context.Values.Max(p => p.Id) + 1 : 1;
var stub = Mock.Get<T>(obj);
stub.Setup(s => s.Id).Returns(generateId);
_context.Add(generateId, stub.Object);
}
}
// ..
}
Como podem observar no método Store*, todos os objetos de testes (do tipo IEntity) devem ser um Mock**. Isto porque no projeto real, quando salvamos um objeto o NHibernate se encarrega de atualizar a propriedade Id. Já no projeto de testes temos de fazer isto manualmente, porém não temos como setar a propriedade Id com um novo valor, então a solução encontrada foi mocar o objeto inteiro para que o Get da propriedade Id correspondesse ao novo Id. Exatamente o que faz esta linha stub.Setup(s => s.Id).Returns(generateId).
*Por convenção, objetos com Id <= 0 são novos e com Id > 0 são objetos já existentes no banco de dados.
**Como mock utilizo o Moq.
Id como protectedO maior problema ocorre por causa da propriedade Id e o fato dela estar como protected Quando falamos a nível de designer é uma ótima abordagem porém isto traz enormes transtornos quando vamos testar nossa aplicação.
Por exemplo, em um teste que estou escrevendo preciso que meu repositório Fake tenha alguns dados já populados.
Acompanhe comigo. Tenho as seguintes classes (+ a classe CheckListItemTemplate mostrada acima.)
public class Passo : IEntity
{
public int Id { get; protected set; }
public virtual IList<CheckListItemTemplate> CheckListItens { get; protected set; }
}
public class Processo : IEntity
{
public virtual int Id { get; protected set; }
public virtual Passo Passo { get; set; }
public virtual IList<CheckListItem> CheckListItens { get; protected set; }
}
Após salvar o processo, o primeiro Passo (ordenado pelo campo Ordem seguindo do campo CreateAt) é associado ao processo:
model.Passo = PassoRepositorio.All().OrderBy(p => p.Ordem).ThenBy(p => p.CreateAt).First();
model.CheckListItens.Clear();
Parallel.ForEach(Mapper.Map<IList<CheckListItem>>(model.Passo.CheckListItens), (it) => model.AddCheckListItem(it));
Este código é executando sempre que salvamos um novo Processo.
Para qualquer teste que crie um novo Processo, este código será executado!
Se temos de criar algum teste que crie um novo processo, nosso primeiro objetivo é popular o repositório com alguns dados fictícios*, especificamente Passos e CheckListItemTemplates para que o código acima não falhe**.
*Para popular objetos com dados fictícios utilizo o AutoFixture.
**Irá falhar caso nenhum Passo seja encontrado no repositório.First()ou este passo não tenha nenhum checklistMapper.Map<IList<CheckListItem>>(model.Passo.CheckListItens).
Então precisaremos de um repositório de passos e cada passo com uma lista de CheckListItens.
Lembre-se de que todo objeto IEntity deve ser um Mock<> para que possamos mocar a propriedade Id
Primeiro configuro meu TestInitialize para popular meu repositório com alguns dados fictícios:
var fix = new Fixture();
var listPassos = fix.Build<Mock<Passo>>()
.Do((passo) => {
passo.SetupProperty(x => x.Nome, fix.Create<string>());
passo.SetupGet(x => x.CheckListItens).Returns(
fix.Build<CheckListItemTemplate>() // Precisa ser necessariamente Mock<>, porém como fazer isto?
.With(p => p.Texto)
.OmitAutoProperties()
.CreateMany(5).ToList()
);
})
.OmitAutoProperties()
.CreateMany(10);
foreach (var item in listPassos)
passoRepository.Store(item.Object);
Em seguida posso executar os testes:
[TestMethod]
public void Salvar_novo_processo_modificar_data_atendimento_passo_atual()
{
// Arrange
var fix = new Fixture();
var vm = fix.Create<ProcessoViewModel>();
//Act
Controller.salvar(vm); // Problema
var processo = Repository.Get(p => p.DataEntrada == vm.DataEntrada && p.ProximoAtendimento == vm.ProximoAtendimento);
//Asserts
processo.Should().NotBeNull();
processo.Passo.Should().NotBeNull();
}
Conseguimos criar uma lista de 10 passos onde cada passo é na verdade um Mock<Passo>, ótimo! Mas:
Para cada passo existe uma lista de Mock<CheckListItens> com 5 itens onde o Id de cada deve ser 1, 2, 3, 4 e 5 (nesta ordem). Como conseguir isto?
Como obter esta lista de IList<Mock<>> dentro de um Mock<> com o Id já preenchido?
Ou seja, a configuração de passo.SetupGet(x => x.CheckListItens).Returns( ???
O responsável por criar objetos no meu controller, basicamente utiliza o AutoMapper para converter meu objeto ViewModel para um objeto Model que possa ser persistido em meu repositório:
model = Mapper.Map<TModel>(vm);
O problema é que meu repositório Fake não consegue salvar um objeto IEntity, apenas um Mock<IEntity>. Como configurar o AutoMapper para sempre retornar um Mock<>?
Web Developer | MCPD Certify
Skype.: 4042-6002 | Cel.: (31) 8681-1986
bindsolution.comMicrosoft Parner Network
Estou quase chegando a conclusão de que não use private/protected setters, simplesmente porque isto irá dificultar os testes.
Na minha opinião, faz todo sentido as propriedades Id, CriadoAs e AtualizadoAs serem protected set porque são readonly.
Se deixo esta responsabilidade para a camada de domínio atualizar estas propriedades, como vou saber quando atualizar a propriedade AtualizadoAs por exemplo? Ela só deve ser preenchida no update!
Em todos os casos fico sem saída:
Se for public setter, isto é uma quebra de designer e todos terão acesso ao setter destas propriedades podendo causar erros no futuro.
Se for protected setter, tenho de lidar com isto no repositório de testes.
Se alguém tiver alguma ideia, acha que o designer está errado ou que estas propriedades não precisam ser protected setter estou aberto a opiniões.
Web Developer | MCPD Certify
Skype.: 4042-6002 | Cel.: (31) 8681-1986
bindsolution.comMicrosoft Parner Network
--
Você recebeu esta mensagem porque faz parte do grupo .Net Architects hospedado no Google Groups.
Para postar envie uma mensagem para dotnetar...@googlegroups.com
Para sair do grupo envie uma mensagem para dotnetarchitec...@googlegroups.com
Para mais opções visite o grupo em http://groups.google.com/group/dotnetarchitects?hl=pt-br
---
Você está recebendo esta mensagem porque se inscreveu no grupo ".Net Architects" dos Grupos do Google.
Para cancelar a inscrição neste grupo e parar de receber seus e-mails, envie um e-mail para dotnetarchitec...@googlegroups.com.
Para obter mais opções, acesse https://groups.google.com/groups/opt_out.
Guid como PK. Isto resolveria o problema do Id, mas como ficaria a propriedade UpdateAt AtualizadoAs?protected setter e deve ser atualizada assim que um registro é atualizado.
Concorda comigo que resolvemos um problema mas a essência continua. Simplesmente mudamos o foco?
Hoje, nas apps que você desenvolve (ou nas empresas que você presta consultoria) como isto é gerenciado? De quem é a responsabilidade de atualizar esta propriedade?
Cara, isto fritou tanto minha mente hoje que cheguei a escrever um post em meu blog: propriedades-private-protected-setter-e-seus-problemas)
Em fim, Id como Guid irá resolver parte do problema.
Marcelo, não estou mocando o AutoMapper.
Mocar o AutoMapper teria de configurar todos os mapeamentos novamente no meu projeto de testes. Não sei se é isto que está querendo dizer.
Em minha app, tenho alguns Profiles de configuração como ViewModelToDomainProfile : Profile e DomainToViewModelProfile : Profile.
Uma classe static no projeto UI configura o AutoMapper:
public static void Configure(Func<Type, object> getService)
{
Mapper.Initialize(x =>
{
x.ConstructServicesUsing(getService);
x.AddProfile<DomainToViewModelProfile>();
x.AddProfile<ViewModelToDomainProfile>();
});
}
Meus controllers dependem da interface IMappingEngine do AutoMapper, desta forma no meu projeto de testes tudo que tenho de fazer é executar o .Configure(...) para que as configurações (Profiles) sejam aplicadas ao AutoMapper. Ninject se encarrega de todo o resto.
No profile ViewModelToDomainProfile tenho um Map para ProcessoViewModel -> Processo. Este Map (para minha aplicação UI) está correto, porém no projeto de testes teria de retornar um Mock<Processo> para que o repositório Fake não gere uma exception e eu consiga setar o Id.
Sim! Meu controller possui uma dependência ao IRepository<Processo>. Ninject se encarrega de resolver estas dependências.
Repare na linha Controller.salvar(vm); em meu teste, o controller utuliza o AutoMapper para converter ViewModel (objeto vm) para um objeto de domínio, neste caso um Processo. Porém preciso que retorne um Mock<Processo>, é obrigatório para o repositório fake, caso contrário irá gerar uma exception na linha var stub = Mock.Get<T>(obj);
Todo novo processo deve ter o primeiro passo associado e a data de atendimento configurada.
Rafael Ponte, ter estas propriedades setters é um erro de designer.
Id é gerenciado pelo banco de dados, não se pode setar o Id na aplicação. Ter esta propriedade como setter dá esta abertura e pode causar erros ou no mínimo confusões no futuro
Na aplicação UI não preciso (nem posso) definir um valor para o Id, porém na aplicação de testes tenho de setar o Id, não tenho nenhum ORM/base dedados para definir este valor.
Rafael Ponte, ter estas propriedades setters é um erro de designer.
Idé gerenciado pelo banco de dados, não se pode setar o Id na aplicação. Ter esta propriedade como setter dá esta abertura e pode causar erros ou no mínimo confusões no futuro
Na aplicação UI não preciso (nem posso) definir um valor para o
Id, porém na aplicação de testes tenho de setar oId, não tenho nenhum ORM/base dedados para definir este valor.
Olá Riderman,2013/6/3 Riderman Sousa <rider...@bindsolution.com>
Rafael Ponte, ter estas propriedades setters é um erro de designer.
Idé gerenciado pelo banco de dados, não se pode setar o Id na aplicação. Ter esta propriedade como setter dá esta abertura e pode causar erros ou no mínimo confusões no futuroTer um setter não vai impedir Id de ser gerado pelo banco de dados, ou vai?
Confusões? Se você trabalha sozinho é bem improvável de isso acontecer; se trabalha com uma equipe, então existem técnicas para compartilhar o conhecimento do sistema e código entre os membros da equipe.
Na aplicação UI não preciso (nem posso) definir um valor para o
Id, porém na aplicação de testes tenho de setar oId, não tenho nenhum ORM/base dedados para definir este valor.Se não precisa, simplesmente não o defina. O ORM, para gerenciar o Id, usa de reflections. Nos seus testes, quem tem a responsabilidade de gerenciar o Id é você, logo, ou você cria um setter (mais simples) ou você também se utiliza de reflections.Não é só porque você é obrigado a ter métodos setters que você tem que utiliza-los no código de produção.
É um teste unitário. Nos testes de integração não tenho este problema pois o ORM gerencia isto para mim.
Não tenho como mocar o AutoMapper. No momento ele funciona, converte um objeto ViewModel ProcessoViewModel para um objeto de domínio Processo, o problema é que para o repositório fake preciso de um Mock<Processo>.
Sobre o repositório fake vs Mock isto acredito ser outra discussão. Mas recomendo a leitura deste artigo). Em resumo admito que se estivesse usando um repositório Mock facilitaria este teste, mas foi uma estratégia utilizar fake:
Não tenho de mocar o repositório para cada teste.
Não tenho de configurar o comportamento do meu repositório para cada teste
De qualquer forma, foi bom você levantar este ponto, se for o caso vou mocar o repositório. (Não gostaria de fazer isto).
Em minha app, tenho vários profiles para o AutoMapper, todas devem ser configuradas.
Mocar o AutoMapper (como você faz na linha new Mock<IMappingEngine>(); me traz um Mapper.Engine sem nenhum profile configurado. Quando você faz: mocker.Setup(m=>m.Map<Entity>(vm)).return(entity); eu realmente não entendi.
Isto irá me retornar uma entidade vazia, toda a configuração que deve ser aplicada para esta conversão não será executada! Quer dizer, você abstraiu algo que não deveria ser abstraído. Se por exemplo nesta conversão tivesse alguma AfterMapAction configurada na UI, nos testes ela simplesmente não seria executada.
Juan, entendi como você trabalha. No meu caso não utilizo Guid, e para trabalhar com Int tenho de pegar o max(id) + 1.
O problema é que na camada de domínio não tem acesso ao repositório! O que leva a outro problema, pois só posso setar o Id internamente na classe de domínio.
Se você entende que pelo fato do Id ser gerado pelo banco de dados é ter a lógica da aplicação no banco, então não tenho como usar Id como int. Afinal como o domínio irá gerar o id sem acesso ao repositório?
Não entendi: ... pois ter uma dependência para um "Clock" não ficaria bonito nos objetos do domínio.
O que é um Clock?
Em meu projeto, estas datas fazem parte da interface IAuditable que indica um objeto é aditável. Porém não vejo como setar estas datas sem usar mock/reflection. Ter um método setter ou uma propriedade public setter é quase a mesma coisa, no final são métodos.
Não entendi: ... e implementá-las implicitamente. Qualquer usuário daquela classe não vai conseguir usar esses métodos a menos que faça o cast para a devida interface.
Em uma interface todos os methods são public, não tenho que fazer cast para poder usá-los.
Se alguém se interessar, fiz um projeto de testes
Acredito ser uma discussão bastante saudável para um grupo de arquitetura.
Lembre-se que o objetivo é utilizar Id como int. A responsabilidade e a forma de gerenciar o id é que está sendo discutida
Get(int id).. (Lembre-se que o repositório é fake, e não um Mock<>)Company adicionar entrada no histórico. A única forma de identificar um novo objeto de um existente é pelo IdCompany tem vários Employees, porém a única property indicando este relacionamento está em Employees.Company. Um requisito é que exista apenas 1 gerente para cada empresa. Como minha camada de serviço irá filtrar?
Então Fabrício vamos la:
Sempre ajuda, cara. Qualquer discussão é saudável! :)
Eu gostaria que a responsabilidade de gerenciar estes campos ReadOnly (na aplicação) fossem da base de dados
Guid, desvincular qualquer lógica relacionada ao banco de dados (inclusive objetos IAuditable).Guid, posso inicializa-lo no construtor correto?Guid é gerado no construtor nem preciso.
Usar Guid como PK resolve o problema da PK, mas e para os objetos IAuditable?
Propriedade CreateAt: Só deve ser atualizada no insert posso coloca-la no construtor. Resolvido!
Propriedade UpdateAt: A responsabilidade agora é da aplicação, o repositório deverá atualizar esta propriedade sempre que um Update for realizado. Como a propriedade é protected, novamente um proxy/reflection terá de ser usado?
Como você lida com a propriedade UpdateAt ?
Então Fabrício vamos la:
Sempre ajuda, cara. Qualquer discussão é saudável! :)
Eu gostaria que a responsabilidade de gerenciar estes campos ReadOnly (na aplicação) fossem da base de dados
, porémestá causando muita dor de cabeça então vamos mudar esta abordagem. Segue as respostas:
- Concordo plenamente!
- Tudo bem, vou mudar a implementação para
Guid, desvincular qualquer lógica relacionada ao banco de dados (inclusive objetosIAuditable).
Toda a responsabilidade de setar estas datas e o Id será da aplicação.
- Perfeito. Mas como pretendo usar
Guid, posso inicializa-lo no construtor correto?
- Ok
- Sem problemas, se o
Guidé gerado no construtor nem preciso.
Segundo pontoUsar
Guidcomo PK resolve o problema da PK, mas e para os objetosIAuditable?Propriedade
CreateAt: Só deve ser atualizada noinsertposso coloca-la no construtor. Resolvido!
Propriedade
UpdateAt: A responsabilidade agora é da aplicação, o repositório deverá atualizar esta propriedade sempre que um Update for realizado. Como a propriedade é protected, novamente um proxy/reflection terá de ser usado?Como você lida com a propriedade
UpdateAt?
Uma duvida... O id sendo setado em minha camada de dominio (seja la por qual tecnica) esta vinculando o meu id ao meu negocio (estamos falando de ddd). Assim, caso o especialista do negocio nao veja sentido em um numero unico que identifica a entidade, esta decisao eh puramente para "agradar" o banco de dados, ou seja, eh uma decisao tecnica tendo em vista a tecnologia que uso para guardar os dados... Entao, pq estaria errado eu deixar isso acontecer automaticamente em meu bd ou no meu prm? Pq eu preciso me preocupar com isso em meu dominio?
Ps: nao quero entrar na discussao "ah, mas se vc nao tem id vai fazer buscas como?" Estou apenas argumentando sobre o pq nao deixar o bd ou o orm se virar com esta questao tecnica
Entendo o ponto de haver uma maneira de identificar a entidade unicamente, porém, o negócio pode ter a solução, como simplesmente usar o CPF, algo que traria todos os benefícios de não termos chaves primárias monótonas e nem mesmo a discussão sobre onde gerar a chave, já que esta resposta seria bem mais óbvia.
Mas, de qualquer forma, pq não deixar que o NHibernate crie para mim uma chave GUID automaticamente se esta questão não envolver o negócio? Talvez seja uma solução válida, apesar de criar a dependencia para o NHibernate... e, por outro lado, quis levantar a questão de que nem sempre precisamos de uma propriedade chamada ID para identificar unicamente minha entidade, apenas quis abrir as mentes para novas ideias! :)
--
--