-
Notifications
You must be signed in to change notification settings - Fork 132
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
[QA] UI testing #58
Comments
Here it is a little summary of the discussions/thoughts @felix-schwarz and I had about how to make UI testing in the app more straightforward: Host SimulatorThere is a mechanism called Host Simulator in the SDK that intercepts previously selected network calls and mock the response for this particular call with a set response. There are examples of how to use this mechanism in some SDK tests, like here The problem using the Host Simulator is that is going to be impossible to test particular app states in a scalable way. Key-Value ObservingSet the values of the SDK objects directly to that they need to be to test the app. For read-only properties or private variables, we can leverage the Method SwizzlingSwizzle individual methods to replace them with versions to return the values needed for testing. Modified SDKCreate a modified version of the SDK, taking advantage of the C preprocessor, like so: // ModifiedOCQuery.m
// Turn OCQuery into BaseOCQuery
#define OCQuery BaseOCQuery
#import "OCQuery.m"
#undef OCQuery
// Subclass BaseOCQuery
@interface OCQuery : BaseOCQuery
@end
@implementation OCQuery
- (void)methodToOverride;
@end Private ownCloud ServerAnother thing that came up to my mind is to have a private ownCloud server only set up for app testing and, through some script, or OCcomands, leave the server in the state we want for the whole app testing or even for each specific test-suit. These ideas are the result of some light discussions, there could be more suitable options than what is summarized in this comment. Also if I miss something or I've written something wrong @felix-schwarz you can correct me. |
the options that seem to be more suitable are KVO and modified SDK, but both cases will need to develop testable code and estimate efforts. The case of private oC server fits for the case of E2E testing, in which we are testing app + sdk, where the sdk is already covered with unit tests. We also lose the perspective of the app testing, so one failure can be caused in the sdk making wrong positives in the app. |
After check the options and see examples of swift projects provided by @pablocarmu (thanks!) IMHO the best solution it is:
if @jesmrec @pablocarmu and @felix-schwarz agree with this implementation I can start with it 👍
|
Here's a solution that enables mocking, but requires no changes to existing code (which is something I'd like to avoid TBH): Create a new build configuration: ios-sdk
That makes sure that the classes are actually called Create a new build configuration: ios-app Also, add a define in the build settings for this new configuration Add type aliases or mocking subclasses to ios-app #if MOCK
typealias OCCore = MOCCore
class OCQuery : MOCQuery {
// Subclass the methods you want to mock
}
#endif Change ios-app project settings to use the Mock build configuration for testing -- Please let me know if you agree @pablocarmu @jesmrec @javiergonzper. I can make these changes relatively quickly then. |
@felix-schwarz for me it is ok 👍 it should works |
WFM |
I just tried to implement my suggestion but had to realize
Ok, so with that approach (and typealiasing in general - for this purpose) off the table, what else could be done? I see three options:
Personally, I'd avoid 1), consider 3), but likely go for 2) because it gives the most flexibility and possibly the cleanest code. |
The Linking against it provides access to Check out https://github.com/owncloud/ios-sdk/blob/137792e14b888de51cb0eef45de7684d2f9a8f41/ownCloudMockingTests/ownCloudMockingTests.m for a simple example of how mocking works for this class: https://github.com/owncloud/ios-sdk/blob/137792e14b888de51cb0eef45de7684d2f9a8f41/ownCloudMockingTests/OCMockTestClass.h . |
I created the branch |
I am happy to say that we have our first UI Tests on the app that mock the response of the server. Right now we can develop the next test using as example the mock of the class Before merge the PR of the ios-app we need to fix the compilation issues on the branch |
After develop several tests we are discovering the limitations of EarlGrey: The app is not rebooted after each test It is not possible to access to the real objects in the screen I would recommend to start using Kif (https://github.com/kif-framework/KIF) and remove EarlGrey from the project. If no one have any inconvenience about it I want to start to do the change as soon as possible |
This is really scary. Each test must be independent from all other tests, and should start from a clean status of the app, not depending on the way they are executed. If Does KIF work in that way? |
On relaunches / resets:The EarlGrey FAQ explains:
func testSomething() {
self.createBookmark()
self.loginToBookmark(atPosition: 0)
// UI test
self.logout()
self.deleteLastBookmark()
}
func testSomething() {
self.createBookmark() // adds block with self.deleteLastBookmark() to array
self.loginToBookmark(atPosition: 0) // adds block with self.logout() to array
// UI test
self.resetUI() // runs blocks in reverse order (might even move this into tearDown()).
}
On accessing specific views:
On switching to KIF:
ConclusionI see no reason or advantage to switch to KIF and strongly recommend staying with EarlGrey. CC: @javiergonzper @jesmrec @michaelstingl @mneuwert @pablocarmu |
more that relaunching the app, we need to launch every test from scratch. I guess KIF allows to restart the app (not reinstalling), so every test will start from the same point, regardless of the status in which the previous test finished. This is that Earlgrey lacks, and make all test fails after the first failure. From QA pov, each test should be as much scoped as posible. Browsing through views before/after the main actions will increase the risk of failures that are no caused by the target of the test, and then, not-desirable false positives.
i think that tests are developed to avoid such things. One test is intended to cover one scenario. When the test fail, you know that something is broken in the scenario and then, research about it to get the bug or whatever. Discussing about I will not deep dive in the technical stuff, so i trust in all of you, but what i wrote here #58 (comment) needs to be studied because is one of the first premises to get good and optimal tests. |
I don't see how KIF should pull that off, as it runs inside the app itself. As soon as it terminates the app, it terminates itself and can no longer execute code. The KIF developers say pretty much the same:
There's no difference between KIF and EarlGrey in this regard.
I agree. However, as long as we want tests to run inside the apps themselves, so we can also verify internal state, there's really no alternative to this. Xcode UI tests would supposedly allow relaunching the app between tests, but run in a separate process. Introspection and verifying internal state (like @javiergonzper said he wants to do with row counts of table views if I understood correctly) is then no longer possible.
It's certainly desirable, but not available with frameworks like EarlGrey or KIF. As I wrote above, Xcode UI tests are the only ones offering this, but will limit the tests purely to UI elements.
Yeah. That's my point, too.
There's no difference between KIF and EarlGrey in this regard. |
I am trying to add more test for the login view but I am having problems due the coupled code between the I see that the Things like that make the project difficult to understand and maintain and of course extremely difficult to test. Is it possible to avoid those kind of practices? |
From a QA pov, the most desirable property is the module isolation, so we can test app and SDK independent each other by mocking, making tests more accurate, useful and maintainable. Please, take it in account in development stage, so mocks will be developed easily and coverage will increase faster. Any solution for the problem @javiergonzper mentioned? |
After add several UI Tests using The Example
ProblemThe problem using Remember that when we make I talked with @felix-schwarz about this issue and he propose mock the http requests using ConclusionThe problem of make Stubbing using
cc: @jesmrec @pablocarmu @felix-schwarz @michaelstingl @mneuwert |
This is really a point i did not want to reach. If we go to the
I really hope that a better solution can be found here. |
I do not share these conclusions or fears. Here's why: We have tools to match every need
By the exampleGoing through the example: Preparations
From this point on, you can replace all calls to the Core and Query with your own in the subclasses. Implementation
This goes to your own implementation in
These steps are SDK-internal and - following Jesus' requirement to have isolated tests and best practices - can be skipped.
cc: @jesmrec @pablocarmu @javiergonzper @michaelstingl @mneuwert |
Let me add, too, that all actions - including create folder - are encapsulated in extensions by now, and therefore usable on their own. That's makes even more focused tests possible. cc: @jesmrec @pablocarmu @javiergonzper @michaelstingl @mneuwert |
Right now the only way to use the MockCore or the MockQuery is checking if we are on For example: if IS_TESTING {
core = MockCore.shared.requestCore(for: bookmark, completionHandler: { (_, error) in
if error == nil {
self.coreReady()
}
openProgress.localizedDescription = "Connected.".localized
openProgress.completedUnitCount = 1
openProgress.totalUnitCount = 1
self.progressSummarizer?.stopTracking(progress: openProgress)
})
} else {
core = OCCoreManager.shared.requestCore(for: bookmark, completionHandler: { (_, error) in
if error == nil {
self.coreReady()
}
openProgress.localizedDescription = "Connected.".localized
openProgress.completedUnitCount = 1
openProgress.totalUnitCount = 1
self.progressSummarizer?.stopTracking(progress: openProgress)
})
} Is it right for you? I do not like it because it is very ugly add To do not add What do you think? If you have a better idea, please create full example of a real test for us in order to follow it. |
Right now as talk on one of our last call I am trying to Subclass the OCCore and OCQuery to create a simple test of creation of a folder. |
Using a subclass of Thanks to that I implemented a test of a creation of the folder 👍 Now the problem that I have is that if I just show |
Subclassed:
With those change now we can show the file list with the ClientRootViewController (tabs and navigation bar) I added 6 tests about "Create folder" |
This issue is intended to be an epic to discuss about automatic testing.
Earlgrey is the chosen tool to develop UI tests. The branch earlgrey-login contains a first draft of a couple of tests.
We can face two different targets using Earlgrey:
UI testing: check how the app reacts to the SDK's responses. Abstracting dependencies from SDK and mocking responses make posible to isolate the app. This kind of testing complements the unit test in the SDK to have a greater coverage through the full stack of the app.
E2E testing: the scope is the whole app, by using the SDK itself. In this case the whole stack is tested, but is more difficult to abstract tests, and positive results will not give the accurate point of the issue/bug. By using a real server, network/core performance issues can happen causing wrong positives and making the tests flaky.
The point is that we need to abstract dependencies in the app for UI testing, by making testable code.
Currently, the app calls directly the SDK interfaces to get info. We should be able to use the same calls with mocked objects, in a way similar to the following one:
for each test. Options to achieve it:
All additional and related ideas are welcome.
@felix-schwarz @javiergonzper @pablocarmu @michaelstingl
The text was updated successfully, but these errors were encountered: