As soon as you have even a vague idea about what your feature is all about you can start defining your Logical Functional Model. Ideally your entire feature team will take part in this exercise, but you can certainly do it on your own as well. Ask yourself "What are the user features here?" Don't think about UI or implementation but rather try to see your app through the user's eyes.

For example, I see the user actions in Surveyor as:

  • Start and stop the app
  • Select an answer for the current question
  • Clear the selected answer for the current question
  • Move to the next or previous question
  • Indicate that the user is done with the survey
  • Skip the current question
  • Switch between the various question navigation sets

Additional user actions may become available after the user completes the survey, but as Surveyor itself is not concerned with that its LFM won't cover any of the myriad possibilities.

Next organize these actions however makes sense to you and your feature team. This organization is mostly just an aid to navigating the LFM, but if you group things similarly to how your users might your LFM will be more intuitive. For example:

  • Application
    • Start and stop the app
  • Navigation
    • Switch between the various question navigation sets
    • Move to the next or previous question
    • Indicate that the user is done with the survey
  • Question
    • Select an answer for the current question
    • Clear the selected answer for the current question
    • Skip the current question

Next convert this to code:

public static class Logical
{
  public static class Application
  {
    public static void Close() { }
  }

  public static class Navigation
  {
    public static void SwitchToNotYetAnsweredQuestions() { }
    public static void SwitchToSkippedQuestions() { }
    public static void SwitchToAnsweredQuestions() { }
    public static void MoveToNextQuestion() { }
    public static void MoveToPreviousQuestion() { }
    public static void IndicateAmDone() { }
  }

  public static class Question
  {
    public static void SelectAnswer() { }
    public static void ClearAnswer() { }
    public static void SkipQuestion() { }
  }
}
I used nested classes as a navigation aid, and static classes because you only act on the "active" item (e.g., the current question or the current application), but these are minor implementation details. More important is:

  • The active voice used to name the methods, which directly describe the actions your user will take.
  • Each method's implied target of the active item.

Although these are somewhat orthogonal as well - the main point is to embed a user action-focused point of view into your LFM.

This is all you need to write test cases!
[TestMethod("TestMethodSetup", "TestMethodTeardown")]
public void CanSwitchBetweenAllQuestionSetsAfterAnsweringAllQuestions()
{
  this.AnswerAllQuestions();
  this.SwitchBetweenAllQuestionSets();
}

private void AnswerAllQuestions()
{
  this.MoveToFirstQuestion();
  do
  {
    Logical.Question.SelectAnswer(this.SurveyNavigator.CurrentQuestion.PossibleAnswers[1].Label);
    Logical.Navigation.MoveToNextQuestion();
  } while (this.SurveyNavigator.ThereIsANextQuestion());
}
private void SwitchBetweenAllQuestionSets()
{
  Logical.Navigation.SwitchToAnsweredQuestions();
  Logical.Navigation.SwitchToSkippedQuestions();
  Logical.Navigation.SwitchToNotYetAnsweredQuestions();
}
You can't actually run the test cases of course (well, not and have them actually do anything anyway), but they will compile. They don't contain any verification or UI or other implementation details, so you won't have to edit your test cases as those details change. The intent of each test is immediately clear - you don't have to hack through thickets of UI manipulation, application internal data structure accesses, and verification code to figure out what their points are. It is quite likely that once your LFM methods are filled out and the application under test implements the corresponding functionality your test cases will just "light up" and start working. The only reason you should need to modify a test case, in fact, is if the user feature it is testing radically changes! If the feature is cut or given completely different semantics than you will need to revisit test cases, but simply changing how the UI works or adding a new way to execute the action generally won't have any repercussions on your existing test cases.


*** Comments, questions, feedback? Let's talk: michhu at microsoft dot com.