Skip to content
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

How to use for model based testing? #4

Closed
t1ooo opened this issue Sep 29, 2021 · 1 comment
Closed

How to use for model based testing? #4

t1ooo opened this issue Sep 29, 2021 · 1 comment

Comments

@t1ooo
Copy link
Contributor

t1ooo commented Sep 29, 2021

How to use this package for model based testing?

Example: https://github.com/dubzzz/fast-check/blob/main/documentation/Tips.md#model-based-testing-or-ui-test

My current implementation:

import 'package:glados/glados.dart';

void main() {
  Glados<List<Command<IntModel, IntList>>>(any.nonEmptyList(any.command))
      .test('formatCountdown', (commands) {
    // print(commands);
    final model = IntModel(0);
    final real = IntList();
    for (final command in commands) {
      if (command.check(model)) {
        command.run(model, real);
      }
    }
  });
}

class IntList {
  final data = <int>[];

  void push(int v) {
    data.add(v);
  }

  int? pop() {
    if (data.isEmpty) {
      return null;
    }
    final lastElement = data.last;
    data.removeLast();
    return lastElement;
  }

  int size() => data.length;
}

class IntModel {
  late int num;
  IntModel(this.num);
}

abstract class Command<Model, Real> {
  bool check(Model model);
  void run(Model model, Real real);
}

class PushCommand implements Command<IntModel, IntList> {
  late int value;

  PushCommand(this.value);

  bool check(IntModel m) => true;

  void run(IntModel m, IntList r) {
    r.push(value); // impact the system
    m.num++; // impact the model
  }

  String toString() => 'push($value)';
}

class PopCommand implements Command<IntModel, IntList> {
  bool check(IntModel m) {
    // should not call pop on empty list
    return m.num > 0;
  }

  void run(IntModel m, IntList r) {
    expect(r.pop() is int, true);
    m.num--;
  }

  String toString() => 'pop';
}

class SizeCommand implements Command<IntModel, IntList> {
  bool check(IntModel m) => true;

  void run(IntModel m, IntList r) {
    expect(r.size(), m.num);
  }

  String toString() => 'size';
}

extension AnyCommand on Any {
  Generator<Command<IntModel, IntList>> get command {
    return (random, size) {
      return ShrinkableCombination(<Shrinkable<int>>[
        any.int(random, size),
      ], (List<dynamic> ints) {
        final commands = [
          ...ints.map((v) => PushCommand(v as int)).toList(),
          PopCommand(),
          SizeCommand()
        ];
        return commands[random.nextInt(commands.length)];
      });
    };
  }
}
@MarcelGarus
Copy link
Owner

All in all, this looks pretty much like I would approach this.
The generator can be a little simplified by lifting the code to one abstraction higher up (not dealing with random and size manually, but rather combine generators):

extension AnyCommand on Any {
  Generator<Command<IntModel, IntList>> get command {
    return either(
      always(SizeCommand()),
      either(
        any.int.map((it) => PushCommand(it)),
        always(PopCommand()),
      ),
    );
  }
}

A caveat is that in this case, 50 % of commands will be size commands, 25 % push, and 25 % pop.

This is a nice improvement idea for the package though: We could change either to optionally accept more arguments:

extension AnyCommand on Any {
  Generator<Command<IntModel, IntList>> get command => either(
    always(SizeCommand()),
    any.int.map((it) => PushCommand(it)),
    always(PopCommand()),
  );
}

At some point, I also planned on offering a chooseWithFrequency function so you could specify exactly how often each option is chosen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants