-
Notifications
You must be signed in to change notification settings - Fork 517
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extend PR #77 - Add Test Data Builders #100
Extend PR #77 - Add Test Data Builders #100
Conversation
Add ability to build existing or new in-memory, persist single builder to DB or register multiple builders for persistence to DB
@jondavis9898 thanks for giving my idea a new chance of being merged by adressing some of the problems. I get the rationale behind your changes. Your changes (actually a rewrite ;-) are way to massive to understand them without really digging in for hours ;-). But..what I can say from looking at the tests for 5 minutes is that the resulting test don't look like I would like to have them. I don't care if I don't understand the implementation anymore (thats why I use a library). But...if the result doesn't make my test more readable and concise and tech free I won't use it. There are TestXYZBuilders and pattern-related names all over the place. I think some renaming in your classes public interface could probably do the job. Would you mind rewriting my sample tests (https://github.com/up2go-rsoesemann/fflib-apex-common-samplecode/commit/adab15414782cff1ffb914f89d211c5e7b283d2e) with your implementation but sticking to my naming style (hiding all implementation, naming the builders after the result objects,...)? |
Sample code to demonstrate usage of fflib_DomainObjectBuilder from apex-enterprise-patterns/fflib-apex-common#100
@up2go-rsoesemann Thanks for reviewing the proposed changes and providing your feedback! Regarding the tests for fflib_DomainObjectBuilder itself and the TestXYZBuilder naming convention, these are intended to be tests for the Builder itself and not meant to represent what a naming convention in actual use might be. That said, it does demonstrate that the name of the actual "builders" can be anything that the consumer of the builder desires as it doesn't influence the functionality. For example, your example used Opportunity_t while someone else could use OpportunityBuilder, etc. In short, apologize if the naming convention in the test class itself made things more challenging to understand. I have updated the sample code repo with examples using the naming convention that you chose. I wrote the same test that you did in there as well as several others to demonstrate all the functionality of fflib_DomainObjectBuilder. Please have a look and let me know if this helps clarify usage. I agree that the naming convention on the public interface of the fflib_DomainObjectBuilder is a little cumbersome. When writing a test, other than the persistBuilders(uow) method, you would never call a method on fflib_DomainObjectBuilder directly. Instead, you would call methods on the derived classes which, as you'll see in the sample, are shorter and easier to follow. The reason I chose to do it this way is two-fold:
Keeping with trying to make tests as easy to read/follow as possible, I've also implemented an Object Mother pattern to avoid having "new" keywords all over the tests. In addition to adding the sample code, I've added MavensMate templates at joeferraro/MavensMate-Templates#18. By creating a new DomainObjectBuilderBase and then specific DomainObjectBuilder classes, the boiler plate code really leaves you only to have to add "withXXX" methods to the build classes themselves and then write your tests. Here's the approach:
Static Methods on Derived Builders Static Methods on Base I'm hopeful that once you review the sample code, you'll see how all this plays out and how clean the actual test itself is. Hope this helps, let me know what you think and any questions you might have. Thanks! |
Thanks for clarifiying you concepts.
anAccount().add(Opportunity.new()) |
@up2go-rsoesemann Thanks for reviewing the samples. To answer your questions:
Please keep in mind that fflib_DomainObjectBase is simply an abstract base class. How the consumer of the library chooses to use it is up to them. They could name their builder Opportunity_t or OpportunityBuilder, they could use a derived class like Domain_t or choose not to, they could use Application class or choose not to. The samplecode is just one example of how the library could be used, it doesn't require that everyone use it that way. This is one of the reasons why there is test coverage for fflib_DomainObjectBuilder, because there is no right or wrong way to use it. It's up to the consumer of the base class to choose how to use it and up to the base class to provide the functionality to satisfy most (if not all) use cases. |
@afawcett How is decided wether a pull request is going to be merged in, rejected or just ignored? I am just asking as I think all of your and @daveespo's concerns have been addressed by the rewrite of @jondavis9898 The last concern I heard of is the question if Test Data Builders are a broad enough use case in the dev community to make them part of such a widely used library. I would argue no but yes ;-) No, because the core of the library (SOC and trigger handling logic) is something basically everybody needs in the Salesforce world. But only a few percent will ever need patterns for getting their test code clean. Yes, because Apex Commons has started to introduce advanced concepts into ist core that are definitely not used by the majority of its user. There are even attempts and discussions to remove it as a dependency from the library. I'd go with a yes, because users should not care about the size of a library at all. If the library allows me to ignore features like Mocks or Builder why not just have them in there. (If I care about footprint I can even delete them in my org). The beauty of this library is that it combines concepts that have been around before (even in the Apex space) but now have been compiles in a concise fashion. And by being so powerful they have attracted critical mass of developers and attention to be safe not to be discontinued in the next month. |
Do we need this kind of thing? Ok so i've been reviewing this finally, so sorry for the delay. Firstly i want to progress this for sure! I agree for certain developers that expect this type of facility on the platforms they work on its a great addition. I'm happy and honoured to support the great community around this library to lead the charge as you guys are doing. ApexMocks parallel? For me this feels much like the time Paul Hardaker and I first discussed introducing ApexMocks. We worried about the bloat and who would really understand it and i'm pleased to see its really taken off. I think the same can be true here for sure! Some more specific thoughts...
Proposal: I think on balance i'm going to recommend we add this fine work into a fflib-apex-testbuilder repo. As far a test builder classes go perhaps contain these in the sample app in a TestBuilders class, hand maintained for now, but then perhaps think about a generator much like ApexMocks. |
Thanks for reviewing Andrew, glad to here you feel a builder concept like this has merit. I'm in full support of a separate repo. I think using the builder pattern is completely optional and doesn't need to be in apex common core. Personally, I'd like to have it in the core library but in my use cases, I'm using everything in fflib so it's nice to just have one place to manage everything from. That said, I think there is very good merit to having thinks like Mocks, Builder and a few others in separate repos since they aren't "core". Regarding inner classes vs. separate classes, I feel that decision is completely up to the consumer of the pattern/library and the library itself doesn't need to dictate. Demonstrating both options makes sense and provides flexibility. In the test cases for the builder I used inner classes, however in apex sample code, I used separate classes. Along these same lines, I feel that naming convention should also be at the consumers discretion (e.g. Account_t vs. AccountBuilder, etc.). Regarding a generator, I actually started writing one when I updated this PR but unfortunately, I didn't get very far and finishing it has been put on the backburner do to other priorities. My design calls for a flag that would emit the classes as inner classes or separate so I think that is in-line with your suggestion. The design also called for options for naming pattern, what the "Object Mother" default method should be named, etc. If someone wants to jump in and write the generator, I'd be all for that! In the meantime, I did update mavensmate templates with templates for the builders (also updated Domain template since it was a little out of date). For those using MM at least, creating a builder is very simple. I agree that there is a lot of boiler plate code (but there's a lot of goodies in there :)). If we can work to complete a generator, I think that will reduce concerns folks would have on the amount of code required to use the builder. In the meantime, the MM templates should hopefully help. |
Per discussion in #77, a new repo was created for this. Submitted apex-enterprise-patterns/fflib-apex-common-builder#2 to the new repo. This issue can be closed. |
This is an extension of the concepts originally introduced in PR #77 by @up2go-rsoesemann. A big Thank You to him for getting this started!
Enhancements from #77:
** build(Boolean) on Base / build() & buildNew() on derived
** Single - persistBuilder(fflib_ISObjectUnitOfWork) on base / persist(fflib_ISObjectUnitOfWork) on derived
** Multiple - persistRegistered(fflib_ISObjectUnitOfWork) on base
Comments:
** Rather than have builder create Unit Of Work, decided to have it be a method parameter. The reason for this is a couple of fold but ultimately, every test data situation is slightly different and if list of SObjectTypes is maintained in the builder itself, it limits what SObject can be built during persist operations. By passing in as a parameter, the test setup code can ensure the UOW has the correct SObjectTypes. Also, the derived class can choose to have a persist() and a persist(fflib_ISObjectUnitOfWork) convenience methods that both wrap persistBuilder(uow). The persist method can create a 'default' UOW internally and when necessary, persist(uow) can be called when the default of the builder does not satisfy the needs. In short, get the best of both worlds here I think.
** Persistance to the DB when operating on registered builders held in static is done via a static method
** Persistance to the DB on an individual builder instance (and it's related data) is done via instance method
** Decided not to add @istest to fflib_DomainObjectBuilder since this class can be used for non-test purposes (see future enhancements below as well) and has 100% test coverage.
** Regarding a single file per builder or one file with lots of inner builders, this is really the choice of the implementer and I don't think having to choose one way or the other as it relates to fflib itself is required.
Future Enhancements:
Updates to fflib-apex-samplecode demonstrating pattern (samples already exist in fflib_DomainObjectBuilderTest)- See SamplesMavensMate template- See TemplatesFeedback encouraged and welcome. Look forward to everyone's thoughts!