Test fixtures or equivalent for test data with Smalltalk Seaside?
Asked Answered
M

2

5

I've been using Test Driven Development in a Seaside app I've been playing with, and all of my data is stored as objects in the image (as opposed to a database).

So when I run my tests I've had to be careful to store away the real data before it gets trashed with test data, like this:

ToDoTest>>setUp 
    savedTasks := Task tasklist.
    Task deleteAllTasks.

    savedProjects := ToDoProject projectlist.
    ToDoProject deleteAllProjects.

    savedPeople := Person peoplelist.
    Person deleteAllPeople.

And:

ToDoTest>>tearDown
    Task tasklist: savedTasks.
    ToDoProject projectlist: savedProjects.
    Person peoplelist: savedPeople

The problem comes when my tests fail, which of course they do, this pops up the debugger, and I can then fix away, but the tearDown doesn't always get called and so I can lose my real data.

I do save the data out to files, so it's not a huge problem, but it is not as smooth and automated as I'd like it to be.

Anyway I can improve this?

Motile answered 27/4, 2013 at 20:41 Comment(0)
A
6

I'm not sure if there is a scenario that will fix the problem completely. The real problem is that the model is global. That is convenient and nice but it fails easily within such a scenario. So I would consider changing the model from something global to a more localized variant so you can create your model solely for testing purpose without interfering with production data.

To fix it within your current setup you need to add an ensure: block somewhere. An ensure block "ensures" you that something is executed regardless if everything went ok or an error happened. The problem is that you need to do it before and after a test.

In this case I would overwrite TestCase>>#runCase in your own test class with something like

runCase
   [ self saveRealModel.
      super runCase ]
      ensure: [ self restoreRealModel ]
Archaism answered 29/4, 2013 at 9:46 Comment(2)
Interesting. I think the idea of partitioning the data in some way may help here. For example my simple to-do app currently has no concept of users, I could add these and then create a test user for the unit tests.Motile
There are many ways to do it. If you want to localize your data an easy way is moving things from class side to instance side. If ToDoProject is your main class move the class side methods to the instance side. You would have ToDoProject>>#taskList, ToDoProject>>#projectList,... In a first step you could make ToDoProject a singleton so that ToDoProject class>>#default will return the ToDoProject instance with your real data. Your seaside component will has an instVar "project". Then you configure your component with "ToDoProject default" for real use and for testing you set "ToDoProject new"Archaism
D
2

Ah, that's a nice test smell. Norbert is right in pointing out that your tested model should probably not be global. Most tests should be on the interaction between individual objects. In StoryBoard we have users

DEUser subclass: #SBUser
    instanceVariableNames: 'email initials projects invitations'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'StoryBoard-Data'

with class instancevar users as an entrypoint. Projects are only reachable through the users.

users
    ^users ifNil: [ users := OrderedCollection with: (SBAdministrator new
        userid: 'admin';
        password: 'admin';
        yourself)
    ]

and a way to clear them

resetUsers
    " SBUser resetUsers "
    users := nil

Often we can pass in dependencies on creation for domain objects

Iteration>on: aProject
    ^self new
        project: aProject;
        yourself

This allows a testcase to pass in itself or a separate (mock) object

Drill answered 12/5, 2013 at 16:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.