51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 2962|回复: 3
打印 上一主题 下一主题

[原创] Is 4TEST a real OO language?

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2005-6-9 10:38:39 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
看了篇文章
http://www.weirdness.org/jeff/articles/a_classes.html
然后试验了把,发觉4test 对类的初始化,支持的不是很好,尤其是对非GUI的类。不过毕竟这个不是编程语言,呵呵。简单的来说,他初始化一个类一般是根据WindowTag 来读入初始化参数,如果有多个的话,只好用引用或解析分割符的方式去实现了。。。
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏

该用户从未签到

2#
发表于 2005-6-13 19:12:20 | 只看该作者
不知楼主说的"4test对类的初始化,支持的不是很好,尤其是对非GUI的类".可否举个例子让我可以直接明白.本人用silktest有一段日子了,还不清楚哪些是silktest不很支持的类.希望楼主能指明,也好让我在以后遇到类似的情况时能及时找到解决方法~~~~谢谢了~~~
回复 支持 反对

使用道具 举报

该用户从未签到

3#
 楼主| 发表于 2005-6-14 14:46:13 | 只看该作者
呵呵,http://www.weirdness.org/jeff/articles/a_classes.html 里面有具体的例子的也~
回复 支持 反对

使用道具 举报

该用户从未签到

4#
发表于 2005-6-14 14:50:20 | 只看该作者
Classes, Objects, Dynamic Instantiation and Constructors

Is 4Test Really an Object-Oriented Language?
Contrary to what most seem to believe, it is possible in SilkTest to create a class that is not derived from any pre-defined 4Test classes. It is also possible to create objects from that class either during compilation or runtime. What's more, in a sort of back-door-kind-of-way, you can even do something that is similar to constructors and destructors. This amounts to being able to use SilkTest in a truly object-oriented way, albeit perhaps somewhat less elegantly then, say, Java or C++.

What is dynamic instantiation? How is it related to object-oriented programming? How can it be useful in Automation programming? Can Segue's 4Test language do dynamic instantiation? Can constructors be used in 4Test?

Dynamic Instantiation
Before diving into the answers to these questions, let's consider the difference between a class and an object is. Starting here is a good place because it will help define the relationship between classes and objects and how they apply to the 4Test programming language. A class is basically a data type that contains a template for data and methods to operate on that data. An object is an instance of that class. The object is the thing that exists from the template. An example might be a sphere and a ball where the sphere is the class and the object is the ball. A sphere is actually something that only exists in concept, where as a ball is something you can touch. If you were to ask me how to create a ball, I might say something like "using a rubber material, make an object in the shape of a sphere."

Instantiation is the process of creating an object from a class template, so the process of creating the ball would be instantiating it. In programming, to dynamically instantiate an object is to do so during run time. Otherwise we call it static instantiation. From here let's look at an example of a class and how we might create an object from that class.

The developers of the application we are testing have been generous enough to litter assert traps all over the application under test (AUT). This is good, but the captions and message text are different; so we cannot declare just one without using a cumbersome multitag. The elements in the Assert dialog are otherwise the same. This is a simple example of a situation where a class might be useful. From our class we will declare each of the assert dialogs.

Each assert has an Abort, Retry and Ignore button as well as a StaticText field. In our case we always want to get the text and then click the Abort button. Our class will look like this:

Code Example 1.: Simple Class Definition

  winclass AssertDlg : DialogBox
  {
    PushButton Abort {tag "Abort";}
    PushButton Retry {tag "Retry";}
    PushButton Ignore {tag "Ignore";}
    StaticText Message {tag "#1";}

    string
    Dismiss ()
    {
      string sRetVal = "";
      if this.Exists ()
      {
        sRetVal = this.Message.GetText ();
        this.Abort.Click();
      }
      return sRetVal;
    }
  }

Notice that our class doesn't have a tag associated with it. This will be provided by the declaration. We derive this class from the SilkTest DialogBox class allowing us to use the methods associated with it and its parents, like the Exists method. In SilkTest, we don't actually have to derive from any class, but in this case it is helpful. This will be important later on. Here is an example of one declaration of an Assert dialog.

Code Example 2.: Window Declaration

  window AssertDlg DrawWindowAssert
  {
        tag "Draw Window Assert";
  }

We don't declare any PushButtons or StaticText, since they are already part of the class. The object we declare from the class automatically has them. We do give it a tag, though. This is the only thing that differentiates the objects. Now it is ready to use. The Dismiss () method will operate on the data provided by the class template, the push buttons and static text, and is callable during runtime like this:

Code Example 3.: Using Object Method

  string sMessage = DrawWindowAssert.Dissmiss();

Alternatively, we could cycle through a list of previously defined assert windows like this:

Code Example 4.: Cycling Through List of Window

  string sMessage = "";
  window w;
  for each w in lwAssertDialogs  // list contains all of our asserts
  {
    sMessage =  w.Dissmiss ();
    if sMessage != ""
      break;
  }

Since the Dismiss method is defined in the class AssertDlg, we can use it for any dialog that is defined as an AsserDlg. This example has illustrated how to statically instantiate the object: our class is AssertDlg and we have created an instance of it in the object DrawWindowAssert. In this case, declaring a window is instantiating an object. Notice that the following code is completely legal and compiles:

Code Example 5.: Object Type Definition and Use

  type object is window;
  object TextWindowAssert = AssertDlg("Text Window Assert");

In this case we declare a type called object that is a window. Then we instantiate a new AssertDlg called TextWindowAssert. I will use the type "object" in most of my examples to illustrate the class / object relationship. Please note that by defining type object is window, does not mean that object and window are interchangeable. For instance, the following code will NOT compile.

Code Example 6.: Window and Object are not Interchangeable

  type object is window;
  object AssertDlg DrawWindowAssert
  {
    tag "Draw Window Assert";
  }

In the 4Test documentation window holds the status of keyword.

Since dynamic instantiation simply means to instantiate during runtime, we can use the code in Code Example 5 to illustrate how you might create an object during runtime:

Code Example 7.: Dynamic Instantiation

  type object is window;
  main ()
  {
    object TextWindowAssert = AssertDlg("Text Window Assert");
  }

Now we have done dynamic instantiation. Right away you probably recognize that this is not far removed from creating a window variable during runtime and initializing that variable with a value. However, from here we can easily extend this to classes that are not related on screen GUI windows.

To instantiate an object in Java, dynamically or statically, we would use the key word new, then the class constructor. So in Java the same line from Code Example 7 might look like this:

Code Example 8.: Java Instantiation

  AssertDlg TextWindowAssert = new AssertDlg("Text Window Assert");

The constructor in Java is the same name as the class, and we can use the syntax above to pass into our object any data that it needs. Also note that by creating useful object type names (myObject is type window), we can get very close to the above syntax in 4Test.

Constructors and Non-GUI Classes
Dealing with files is common to many automation testing scenarios. SilkTest provides functions to deal with files, but we might want to extend those capabilities, or even provide custom file IO abilities. This is a simple next step, so we will also include the concept of constructors in the following examples. In object-oriented programming a constructor is an initialization method that is generally named the same thing as the class it is associated with, and takes any necessary arguments needed to initialize, or instantiate, the object.

We have already used constructors without really thinking of them that way. Look back at Code Example 7. When we created a window using the AssertDlg class, we passed it "Text Window Assert" as the window's caption. The 4Test language stores that data as a tag for GUI object identification. That string is effectively constructor data. In cases where we have classes that are not related to GUI objects we can still have access to that string and can use that data for our own purposes. Let's take a look at how we can use the window tag as a constructor.

Code Example 9.: File Object Instantiation

  type FileObject is window;
  FileObject file = FileClass ("c:\data\text\ex001.dat");

If we have a class called FileClass (see Code Example 10) and we instantiate it with a string, then we can use that string as constructor data. For this example, we'll assume that the FileClass is derived from the SilkTest class AnyWin. We can always get the constructor data by using the AnyWin property WndTag. Inside of the FileClass we probably want a method that verifies if a file exists.

Code Example 10.: FileClass from AnyWin

  type FileObject is window;
  winclass FileClass : AnyWin
  {
    boolean
    Exists ()
    {
      boolean bRetVal = false;
      if SYS_FileExists (this.WndTag)
        bRetVal = true;
      return bRetVal;
    }
  }

  main ()
  {
    FileObject f = FileClass ("c:\data\text\ex001.dat");
    if f.Exists ()
      CallSomeCodeSomePlace ();
  }

Code Example 10 illustrates a few interesting points: use of constructor data, overriding a method, and, as seen before, dynamic instantiation. The path and file name passed in as a window tag is used as the file name for this object. We access it using the WndTag property that is part of the AnyWin class. The Exists method that is part of the AnyWin class would not find a file, so we write our own; this is called overriding. Our Exists method is overriding the Exists method associated with the AnyWin class. By doing this, we could have this file object in a list of windows and cycle through the list checking to see if all of the objects exist or not because we now have a valid Exists method for our file class. One last thing to note here: the file object has nothing to do with any GUI objects or windows. Given that, it may be desirable to disassociate our FileClass from AnyWin.

Code Example 11.: FileClass not Derived from AnyWin

  type FileObject is window;
  winclass FileClass
  {
    boolean
    Exists ()
    {
      boolean bRetVal = false;
      if SYS_FileExists (WindowTag(this))
        bRetVal = true;
      return bRetVal;
    }
  }

  main ()
  {
    FileObject f = FileClass ("c:\data\text\ex001.dat");
    if f.Exists ()
      CallSomeCodeSomePlace ();
  }

Since the FileClass is not derived from AnyWin, it no longer has the WndTag property available. We could create a WndTag property for our class, but that might not make sense because our class has nothing to do with GUI window objects. However, we do need to get the constructor data that SilkTest internally calls a tag. To do this we can use the WindowTag () function. The WindowTag () function takes a window as an argument, so we pass it the this key word. This is a 4Test key word that refers to the object in which the this key word is being used.

The next modification that we want to make is to be able to change the name of the file during run time. Remember that the data we pass in upon instantiation is stored in the tag property that is intrinsic to all classes in SilkTest. Here is one way to allow users to update the name of the file:

Code Example 12.: File Name Change

  type FileObject is window;
  winclass FileClass
  {
    string sFileName = "";

    property FileName
    {
      string Get()
      {
        if (sFileName == "")
        {
          //remove fwd slash
          sFileName = SubStr (WindowTag(this), 2);
        }
        return sFileName;
      }
      void Set (string s)
      {
        sFileName = s;
      }
    }
  }

This example declares a string variable called sFileName and a property called FileName. The FileName property gets and sets the sFileName variable and is how we reference the file information once the object has been created. In this example, the tag of the object will remain unchanged even if we change the file name, but the only way to get to the tag is with the WindowTag () function. Remember that since FileClass isn't derived from AnyWin we don't have access to the WndTag property. This is important to note because we don't want users of our class to change the file name and then access the tag thinking that it contains the new file name. If we document and publish the FileName property as the only access point, users don't have to understand what the tag actually contains or even that a tag is in any way related to our class.

In the Get method of the FileName property, if sFileName has not been set, we get this object's window tag, which was passed in upon the object's creation, and set sFileName to the value in the tag. If we have already called the get method once, then sFileName is set and we return that. The Set method simply sets sFileName to the given value. Keep in mind that if no constructor data is passed in, then the tag will be an empty string and FileName will return an empty string until the user sets it. Consider the following code, assuming that the FileClass is from Code Example 12:

Code Example 13.: No Constructor Data

  main ()
  {
    FileObject f = FileClass();
    Print (WindowTag(f));  // prints nothing
    Print (f.sFileName); // prints nothing
    Print (f.FileName); // prints nothing
    f.FileName = "c:\autoexec.bat";
    Print (WindowTag(f)); // prints nothing
    Print (f.sFileName); // prints "c:\autoexec.bat"
    Print (f.FileName); // prints "c:\autoexec.bat"
  }

First an object is created with no value passed in. Nothing will get printed in the next few lines because the object contains no data. After setting the FileName, the actual tag associated with this object is still an empty string, but sFileName contains the necessary data and any subsequent calls to FileName will return the correct information.

Code Example 14.: Changing the FileName

  main ()
  {
    FileObject f = FileClass("c:\autoexec.bat");
    Print (WindowTag(f));  // prints "/c:\autoexec.bat"
    Print (f.sFileName); // prints nothing
    Print (f.FileName); // prints "c:\autoexec.bat"
    f.FileName = "c:\config.sys";
    Print (WindowTag(f)); // prints "/c:\autoexec.bat"
    Print (f.sFileName); // prints "c:\config.sys"
    Print (f.FileName); // prints "c:\config.sys"
  }

In Code Example 14, the first line printed will be "/c:\autoexec.bat". SilkTest is appending the forward slash indicating that the window is a child of the desktop. This is actually irrelevant in our case, since our class isn't a GUI object. So in Code Example 12, the FileName property removes the slash. The next line printed will be blank because the sFileName variable contains an empty string and hasn't yet been set by the FileName property. Finally, the third line prints the value we want to see. If we now check the sFileName variable we would see that it has been set and it contains the expected value.

The next line in our example uses the FileName property to change the name of the file contained in our object. Since we are not changing the actual tag, it will still contain the original value. The sFileName and FileName do contain the expected string. Hopefully, this example illustrates why we want users to use the FileName property as opposed to the object's tag or sFileName. In many other languages we would be able to enforce scope control, but here we cannot.

There may be certain cases where we have a class that requires the object to have more data upon instantiation. In this case we can send in a string and parse that string for the data we need. SilkTest provides several useful string functions that make this easy. Code Example 15 is an example of a class that needs more information then just a single string. The following example incorporates all that we have covered so far, and adds a few methods that we would document as the classes interface methods. These are the ConstructorData, Name and Age properties.

Code Example 15.: Constructor Data Parsing

  type ConstructorObject is window;
  winclass ConstructorClassEx
  {
    string sConstructorData = "";
    string sName = "";
    int iAge = 0;

    property ConstructorData
    {
      string Get()
      {
        if (sConstructorData == "")
        {
          sConstructorData = SubStr (WindowTag(this), 2);
          init ();
        }
        return sConstructorData;
      }
      void Set (string s)
      {
        sConstructorData = s;
        init ();
      }
    }

    property Name
    {
      string Get()
      {
        if (sName == "")
          init ();
        return sName;
      }
    }

    property Age
    {
      integer Get()
      {
        if (iAge == 0)
          init ();
        return iAge;
      }
    }

    init ()
    {
      // ensures that the sConstructorData is set prior to any use
      if (sConstructorData == "")
        sConstructorData = ConstructorData;
      sName = GetField(sConstructorData, "|", 1);
      iAge = Val(GetField(sConstructorData, "|", 2));
    }
  }

  main ()
  {
    ConstructorObject co = ConstructorClassEx("Andrea|27");
    Print ("{co.Name} is at least {co.Age} years old.");
  }

When the constructor of this class is used to instantiate an object, the user would pass in a string with a name and an age separated with a pipe ("|"). When we call the Name above, it forces the init() method to be called and the data is parsed and put in the right place. This example uses only two pieces of data, but you could pass in and parse as much data as your class requires. You can also use the de-reference 4Test operator and pass in a string that points to a preset record or object. In the following example we create a record and an unset global variable. During runtime we set the records values and then pass in the name of the record as a string. While this may not appear terribly useful for two pieces of data, it may be necessary in large data sets.

Code Example 16.: Record Data and Passing Constructor Data

  type ConstructorObject is window;
  ConstructorData data;  // global var avail to all classes
  type ConstructorData is record
  {
    string sName;
    integer iAge;
  }

  main ()
  {
    data = {"Samy", 30};
    ConstructorObject co = ConstructorClassEx ("data");
    Print (co.Name, co.Age);
  }

The class is unchanged except for the init method, which will de-reference the string and extract the data.

Code Example 17.: De-referencing Constructor Data

  init ()
  {
    if (sConstructorData == "")
      sConstructorData = ConstructorData;
    sName = @(sConstructorData).sName;
    iAge = @(sConstructorData).iAge;
  }

Of course if the data variable is global you could access it directly, so you don't have to pass it in as a constructor. Still, this approach may be useful with large data sets that are set during runtime.

Scope, Encapsulation and Destructors
Java and C++ allow users to set the scope of variables, properties and methods in classes to private, meaning that they can only be accessed inside the class. You can also set the scope of variables, properties and methods to public, effectively making them available interfaces for the class. This sort of scope enforcement is referred to in OOP jargon as encapsulation. In 4Test you cannot set a variable, property or methods scope as such, but you can set variables to private for a given file. In Code Example 12, we could have declared sFileName as private if its declaration was outside of the class definition. If the class is contained in its own "inc" file, the users that include the class definition file would not be able to access it. As far as I know, there isn't a way to enforce the same scope control for methods or properties. In this way 4Test only partly supports encapsulation.

However, the scope of an object turns out to be the same as the scope of a variable defined as a string, integer, window or any other type. If you instantiate an object or variable inside a function or method, only that function or method can access it. If defined outside of any methods, functions or main then it is global and everyone can access it. Given that, you can create an object whose scope is limited to a file.

To destroy an object I have been setting the object equal to null, which effectively renders the data contained within irretrievable.

There are certainly many ways to achieve a desired result. However, this article is too short to cover all the possibilities. But, hopefully, it has inspired the reader to consider automation issues with an eye for objectifying. The point isn't to use object-oriented methodology anywhere it will fit, but rather where it solves problems and makes writing automation easier, more robust, more efficient or perhaps just more elegant, and thus more readable. In the case of the FileClass, methods can be added to copy, move, rename and delete files, return path, file name, and extension, open for reading, writing, appending and closing the file. Certainly there are many cases where this approach is useful. Happy automating!
回复 支持 反对

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-11-27 16:13 , Processed in 0.064129 second(s), 27 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表