Zebble provides automated UI testing support for the final application, whereby you invoke commands on the actual app UI. That style of testing is powerful, because it allows you to test the end to end functionality of the app, including logic and UI in one go. However, there are two problems with that:
Those tests are still useful and powerful and should be used. However, you should also write unit tests against the view model alone, even before you have created the UI. This has several advantages:
In the VM project, there is a folder called Tests.
Each automated test should be a class inside that folder, inheriting from Zebble.Mvvm.TestScript.
Use standard unit testing best practices.
{
class FailedLoginTest : TestScript
{
protected override async Task SetUp()
{
// This method is optional. If your test requires any setup work before getting to your main test logic, you can implement it here.
// In practice, this is more useful when creating helper TestScript base classes, from which your main test classes inherit.
}
protected override async Task Execute()
{
On<WelcomePage>().TapLogin();
Expect(On<LoginPage>.SampleProperty == "Blah blah");
On<LoginPage>.Email.Set("jack@gmail.com");
On<LoginPage>.TapLogin();
Expect("Please enter your password.");
On<LoginPage>.Password.Set("incorrect");
On<LoginPage>.TapLogin();
Expect("Invalid password. Try again.");
}
}
}
It serves two purposes:
If the current screen in the app state is anything other than what you specify as the generic argument of the ON method, then it throws an exception.
In VM.exe\Program.cs you can instantiate and run a test using any of the following methods:
{
static Intention Doing = Intention.Development;
public static async Task Main(string[] args)
{
Zebble.Console.Configure();
await ViewModel.StartUp.Run();
if (Doing == Intention.OneTest) await Test.Run<MyTest>();
if (Doing == Intention.AllTests) await Test.RunAll();
if (Doing == Intention.Development) await DevContext.Reach();
if (args.None()) Zebble.Console.Start(args);
}
}
Often your tests will have pre conditions. You can override the SetUp() method in your TestScript classes to execute any pre-condition, so that your test code's Execute() method is semantic, lean, and focused on the actual intention and purpose of the test.
If you want to share a pre-condition in several tests, then create abstract base classes. A common example is to do "login".
/// Inherit your user tests from this class.
/// </summary>
abstract class LoggedInUser : TestScript
{
protected override async Task SetUp()
{
// TODO: Do the basic user login.
}
}
In VM.exe project in Visual Studio, you will see a class named DevContext:
{
abstract class DevContext: TestScript
{
// Run quick steps to get you to the context you're developing.
public static async Task Reach()
{
// Run quick steps to get you to the context you're developing.
On<WelcomePage>().TapLogin();
On<LoginPage>().TapLogin();
On<ShoesPage>().Items[0].Tap();
}
}
}
This code runs as soon as your VM.exe process starts, during development. Its purpose is to automatically run a few steps to get you to the context you're interested in.
The idea is, that as you're developing an app, you start with a basic starting page and move your way up from there. Every page, or workflow step, is developed, which then allows you to implement the next logical app function. Typically, every time you run an app, you have to take a few actions (button clicks, inputs, etc) on the pages already developed, to finally arrive at the page you're developing, i.e. the "interesting page" at that time.
The Script.cs file is a temporary, working file. It is changed continuously, and does not form a part of the final app. It's purpose is to allow you to automate those steps, and thus get you right at the final page you're currently developing and testing, without wasting time on the repeatable clicks and inputs.