Bots: 高级特性

这些高级特性大多数在oTree Studio中不被支持。

命令行bots

一种可选的在浏览器中运行bots的方式是在命令行中运行。命令行bots运行更快且需要更少的安装步骤。

运行下面的代码:

otree test mysession

使用指定参与者人数进行测试(否则默认为 num_demo_participants):

otree test mysession 6

在所有session config上运行测试:

otree test

导出数据

使用 --export 选项来将结果导出为CSV文件:

otree test mysession --export

为指定数据所保存的文件夹,可使用:

otree test mysession --export=myfolder

命令行浏览器bots

你可以在命令行启动bots,通过使用 otree browser_bots

  • 确保已安装了Google Chrome,或在 settings.py 中设置了 BROWSER_COMMAND (更多信息见下面)

  • 设置 OTREE_REST_KEY 环境变量如 REST 中所述。

  • 运行你的服务器

  • 关闭所有Chrome窗口。

  • 运行下面的代码:

    otree browser_bots mysession
    

这会启动数个Chrome标签页并运行bots。当完成后,标签页会自动关闭,并且你会在终端窗口中看到一份报告。

如果Chrome没有正常关闭窗口,确保你在运行指令之前已经关闭了所有Chrome窗口。

远程服务器(例如 Heroku)上的命令行bots

如果服务器不是运行在通常的主机/端口 http://localhost:8000 上,你需要传递 --server-url 参数。例如,如果在Heroku上,你需要这样做:

otree browser_bots mysession --server-url=https://YOUR-SITE.herokuapp.com

选择session config与人数

你可以指定参与人的人数:

otree browser_bots mysession 6

为测试所有的session config,运行:

otree browser_bots

浏览器bots:杂项

你可以使用非Chrome浏览器,需在 settings.py 中设置 BROWSER_COMMAND 。然后,oTree会自动启动浏览器,通过类似 subprocess.Popen(settings.BROWSER_COMMAND) 的操作。

测试用例

你可以在你的PlayerBot类中定义属性 cases 来列出不同的测试用例。例如,在公共品博弈中,你可能想要测试三种场景:

  • 所有玩家贡献其初始值的一半
  • 所有玩家均不做任何贡献
  • 所有玩家贡献出他们全部的初始值(100点)

你可以分别将3个测试用例命名为”basic”,”min” 和 “max”,并将他们放在 cases 中。然后,oTree会自动执行bot3次,每次一个测试用例。每一次执行时, cases 中的不同值都会在bot中被赋值给 self.case

例如:

class PlayerBot(Bot):

    cases = ['basic', 'min', 'max']

    def play_round(self):
        yield (pages.Introduction)

        if self.case == 'basic':
            assert self.player.payoff == None

        if self.case == 'basic':
            if self.player.id_in_group == 1:
                for invalid_contribution in [-1, 101]:
                    yield SubmissionMustFail(pages.Contribute, {'contribution': invalid_contribution})
        contribution = {
            'min': 0,
            'max': 100,
            'basic': 50,
        }[self.case]

        yield (pages.Contribute, {"contribution": contribution})
        yield (pages.Results)

        if self.player.id_in_group == 1:

            if self.case == 'min':
                expected_payoff = 110
            elif self.case == 'max':
                expected_payoff = 190
            else:
                expected_payoff = 150
            assert self.player.payoff == expected_payoff

注解

如果你使用测试用例,更推荐使用 命令行bots 因为浏览器bots仅会执行一个单独的用例。

cases 必须是列表,但可以包含任何数据类型,如字符串,整数,甚至字典。下面是一个信任博弈,它使用字典作为用例。

class PlayerBot(Bot):

    cases = [
        {'offer': 0, 'return': 0, 'p1_payoff': 10, 'p2_payoff': 0},
        {'offer': 5, 'return': 10, 'p1_payoff': 15, 'p2_payoff': 5},
        {'offer': 10, 'return': 30, 'p1_payoff': 30, 'p2_payoff': 0}
    ]

    def play_round(self):
        case = self.case
        if self.player.id_in_group == 1:
            yield (pages.Send, {"sent_amount": case['offer']})

        else:
            for invalid_return in [-1, case['offer'] * C.MULTIPLICATION_FACTOR + 1]:
                yield SubmissionMustFail(pages.SendBack, {'sent_back_amount': invalid_return})
            yield (pages.SendBack, {'sent_back_amount': case['return']})

        yield (pages.Results)


        if self.player.id_in_group == 1:
            expected_payoff = case['p1_payoff']
        else:
            expected_payoff = case['p2_payoff']

        assert self.player.payoff == expected_payoff

error_fields

当在多个字段的表单上使用 SubmissionMustFail 时,你可以使用 error_fields 获得额外的提示。

例如,假定我们提交了一个合法的 age,但同时提交了非法的 weightheight:

yield SubmissionMustFail(
    pages.Survey,
    dict(
        age=20,
        weight=-1,
        height=-1,
    )
)

Bot系统不会告诉我们具体 为什么 提交会失败,这是缺失的信息。是 weight 还是 height 还是两者一起出错? error_fields 可以解决这种模棱两可的情况:

yield SubmissionMustFail(
    pages.Survey,
    dict(
        age=20,
        weight=-1,
        height=-1,
    ),
    error_fields=['weight', 'height']
)

它会查对出 weightheight 包含错误,但是 age 没有。

如果 error_message 返回一个错误,那么 error_fields 将会是 ['__all__']

杂项

在bots中,赋值语句 player = self.player (或 participant = self.participant,等等)是有风险的,即使这种代码在别处是被鼓励的。

因为如果有一个 yield 语句在中间,数据可能没有更新:

player = self.player
expect(player.money_left, cu(10))
yield pages.Contribute, dict(contribution=cu(1))
# don't do this!
# "player" variable still has the data from BEFORE pages.Contribute was submitted.
expect(player.money_left, cu(9))

直接使用 self.player.money_left 是安全的,因为 self.player 会从数据库中获取最新的数据。

实时页面

为使用机器人测试实时方法,在 tests.py 中定义顶级函数 call_live_method 。(在oTree Studio中不可用。)此函数可模拟一系列对你的 live_method 的调用。参数 method 模拟你的玩家模型中的实时方法。例如, method(3, 'hello') 调用了玩家3上的实时方法,并且 data 设置为 'hello'。例如:

def call_live_method(method, **kwargs):
    method(1, {"offer": 50})
    method(2, {"accepted": False})
    method(1, {"offer": 60})
    retval = method(2, {"accepted": True})
    # you can do asserts on retval

kwargs 至少包含下列参数。

  • case测试用例 中所述。
  • page_class: 当前页面类,例如 pages.MyPage
  • round_number

当小组中最快的bot到达一个有 live_method 的页面时 call_live_method 会被自动执行。(其他bot此时可能在前一个页面,除非你使用等待页面限制了这种情况。)