Saturday, 12 December 2015

Approaches to Testing Software

TL;DR - Knowing the different ways and strategies to test software will give you the ability to define your approach depending on your project limitations
Over the last few weeks we've been exploring less technical topics around software development (Designing reusable componentsThe Art of Estimation and Separating data from logic in Swift). In this article I discuss the various approaches I take around testing software. I'll focus on testing of mobile applications, specifically iOS apps but most concepts are applicable to other software.
When I think of testing an app 3 approaches come to mind:
  • Basic manual testing
  • Automated testing
  • Exploratory testing

Basic Manual Testing

The basic and most common testing carried out in software is manual testing. Specifically, this involves a person going through the app manually tapping buttons, filling in textfields and so forth.
To make manual testing work it's important to define very clearly test scenarios. This way any other tester joining the project can follow these test cases to ensure that all the edge-cases known are tested every time. More often than not these test cases are carried out by a click engineer. Someone that will blindly follow a set of instructions tapping buttons without much thought.

Blindly following test cases is of limited value. The tester testing should understand the app in a way that when a bug is found, they can provide insights and hypothesis on the underlining cause of the issue.

Automated Testing

Manual testing will work on projects that have a lower budget and focus on short term goals. However, it'll be less valuable in the long term as the time taken to manually complete all the test cases is always the same. This is where automation testing is useful since there is a high setup time/cost to write the automated tests but running them is very fast.
The value of automation is sometimes misinterpreted specially when people rely on test coverage percentage as a measure of testing quality or they have a blind obsession with it. Some aspects can be tested using automation and some others can't, and so should be manually tested.
There are some of the types of automated tests I have written:
  • Logical unit tests
  • Performance unit tests, this one is tricky to test because most of the times we run out test-suite in our CI server or development laptop but it is key this is run on the slowest of the devices that your app is meant to run
  • Snapshot tests, which are useful to ensure the UI is consistently the same
  • Sensor related tests (e.g. gps location, accelerometer, gyroscope, barometer and so forth), where mocking of the signals is necessary
  • UI tests, which ensure the flow between screen does not break
  • Server integration tests
  • Static content validation tests (Check spelling of content, check all fields expected for the content exist and are of the expected format)
This list is not exhaustive but it will give you an indication of what is possible.

Exploratory Testing

The real value of automation tests is that it allows for time spent doing exploratory testing. This literally involves using the app in different ways as user would. By doing this it'll be possible to catch edge-cases that were not found by previous manual and/or automated tests.
This is where the human value comes into play. We make apps for people. Therefore, it's best if other people use the app to pick up unexpected and/or undesired behaviour.

Conclusion

Generally speaking it's best to automate the monotonous part of a an application where it's time efficient to do so. Basic manual testing should be left to cases where automating something would be very time consuming. This is very valuable in the long term as regression testing before a release is reduced. Thus, the focus can be on a more real-life exploratory testing approach.
The testing strategy depends very much on the team developing the software and their limitations (skills, time and so forth). Hopefully, after reading this article you'll be able to make more educated choices about how you test your software.

Saturday, 5 December 2015

Separating data from logic in Swift

TL;DR Try to separate data from logic for better code reusability. Ideally, data structures and their properties should be immutable.
We've recently been discussing how we can design reusable components and how we can provide useful estimates for our work. 
Today, I want discuss a key concept that can help with writing reusable code: Separating the data from the program's logic. This is nothing more than implementing the Separation of concerns. The idea is to illustrate this with the onboarding component we've discussed in the prior post about designing reusable components. The examples will be in Swift, so while some of the language facilities might be unavailable in different languages, the concepts should still be useful. 

Identify what is data

The first step to separate the data from the logic is to identify what is data. In our onboarding example, the data is composed of all the attributes that must be customizable (identified in our design phase) for our onboarding to be reusable in other projects. These were:
  • Button placement
  • Fonts
  • Background image
  • Animation speed
  • Corner radius of body copy box
  • Line width of body copy box
  • Border colour
  • Text aligment (justified, centred)
  • Logo image
The logic in this example will mainly boil down to presenting the different onboarding pages and UI elements based on the data provided. User interaction with the onboarding would also be part of the program's logic.

Modelling the data

Ideally, we want the data to be inmmutable in order to reduce the errors that happen when one part of the program modifies the data affecting another section that didn't expected that data to change.
The first step is therefore to use immutable properties wherever possible. This means using constants instead of variable (let instead of var in Swift), and copying data rather than passing a reference to it. In Swift, structs are ideal for modelling data because they use value-semantics. When a struct is passed to a method or a function, a copy is made. This method can then alter the data without affecting other methods that might have a copy of the struct.
To illustrate this point, we can have a look at the struct used to contain the attributes of our onboarding example: 
struct FlexiOnboardContent {
    let backgroundImage: UIImage?
    let logoImage: UIImage?
    let title: String
    let text: String
    
    let titleFont: UIFont?
    let titleColor: UIColor?
    let textFont: UIFont?
    let textColor: UIColor?
    let isTextCentered: Bool?
    
    let hasBorder: Bool?
    let borderWith: CGFloat?
    let borderColor: UIColor?
    
    let hasRoundedCorner: Bool?
    
    let action: (() -> (Void))? 
}
The FlexibleOnboardContent structure can then be passed to the main onboarding class to instantiate a new onboarding component. All of the properties in this example are optionals because none are required.
There might be situations, such as when the data is managed by Core Data, where it's not possible to use structs. In such situations, reducing the parts of the code where data is accessed might be the best option. 

Conclusion

We've seen through this brief post how we should attempt to separate the program's logic from its data by using inmmutable properties and value-semantics. A process of trial and error might be required in some instances, so it's definately worth using Swift's playground to try out your ideas.