We've got an idea of what the tests are and what purpose they serve. I hope you are now eager to start creating them!
Before this can be possible, however, we need to identify the elements to test.
Tests and MVC
Using MVC architecture, we’ve got our code organized in 3 parts and have the Model separated from the View by the Controller.
Using MVC, the logic of the application is in the Model part. This is like the brain of the application, and, we must make sure that the brain works well! So, it's the model that we'll test in MVC!
And how about the View?
The view is a much simpler entity - it doesn't implement any logic. The view just displays what it's "told" to display. We'll won't be testing the view.
Ok, and the the Controller?
Well noted! The controller controls the view, so it does have some display logic to test! In large projects, view controllers can become very big and, at times, impossible to test effectively. This is, indeed, a limitation of MVC architecture.
In this course, we'll focus on testing the model as an example. The same principles are applicable to any type of code components that you can explore further.
Let’s now inspect our project for testability!
Tests and classes
In our model we have a few elements:
We are going to start with two classes - Game and Tournament.
The interface concept
As we established, a unit test tests only a small unit of code - a method of a class.
Does that mean we've got to test all methods of all classes? 😅
Of course, all the class logic needs to work properly. However, the purpose of any class code at all is to serve "the outside world" - code that utilizes that class. At the same time, a class may "hide" some of the implementation by implemented private members (properties and methods). That hidden functionality is of no interest to us.
What interests us here is the external functionality - class members (properties and methods) that can be utilized outside of that class. A set of those members accessible externally Is called a Class Interface. The interface of a class is what we'll be implementing the tests for!
Visualize the interface
To demonstrate the concept of the interface, let's visualize it in Xcode. For that, select a file of which you want to view the interface. Then, on the main menu, select Navigate ➡️ Jump to Generated Interface.
This will display the interface of selected class:
In this view, you can see all the properties and methods of your classes internally and publicly. All the others members are hidden, as well as the implementation details of all members.
Test only the interfaces
We only need to test the interface, not the private members.
So, we won't be testing everything after all? 🧐
Private members are designed to serve some purpose within a class. At some point they are utilized by interface members. By testing the interface, we'll ultimately ensure the private members work correctly!
Conditions to test
For the tests to be efficient, the code must allow for that. In other words, it needs to be testable. There are critical conditions for the code structure that allow for testability. A class must implement the following principles:
Single Responsibility: This principle ensures simplicity of code. The idea of it is very similar to creating functions. Each function needs to do only one thing. Classes, on the other hand, incorporate functions (methods) that revolve around a single subject. In a way, a class serves as a contextual umbrella for its properties and methods.
Decoupled classes: This principle is all about minimizing dependencies - tight connections between classes. For example, if you have a class responsible for processing payment and then multiple classes that could use placement services. Those classes should not require the payment class to be a "permanent resident." It need not be responsible for creating and servicing payment class in any way; the interconnection should be limited by processing a payment. This can be done by another (managing) class, like a Payment Manager (a type of payment controller).
The bottom line: to be able to test effectively, your code must be testable, and that's often the hardest part. So, follow the KISS principle and keep it simple! 😇
This may seems obvious, but let's make sure we're on the same page with "usefulness."
What are useful tests?
Useful tests are those that make sense. But sometimes the most common sense requires a spotlight!
A useful test is a test that can either pass or fail - in other words, a test that always passes or always fails is not useful at all.
For example, there is no point to test the fact that when you add an element to an array, the size of it gets incremented - it will always be true! (Unless the Swift language goes on strike, but then we'd have a bigger problem. 🙃)
On the other hand, if the game is over in your app and you need to change the score, it's not about a trivial increment anymore, it's about logical sequence in your code. Plus, what if we want to make it fancy and reward outstanding payers? Say, double their points on a number of consecutive wins? ☺️
The Model in MVC typically contains the majority of the app logic and therefore becomes the primary target for testing.
Testing the interface is sufficient to ensure the overall correctness of a class.
To test, the code must be testable. The primary criteria of testability are classes with single responsibility and decoupled classes.
A test is useful if the result of it is binary - fail or pass.