pytest学习笔记

1、直接在anaconda prompt运行pytest(前提是已经安装pytest,也可以用colab

2、若要使用–json-report等argument,需要安装pytest插件

pip install pytest-json-report --upgrade 

3、若要使用–suppress-tests-failed-exit-code

!pip install pytest-custom-exit-code

4、若要使用jq,需要安装

以colab为例,colab用的是Ubuntu 18.04.6 LTS (Bionic Beaver)容器

!cat /etc/os-release

在Ubuntu下可以使用如下命令安装

!apt-get install jq

如果要安装的是pyjq,则需要先安装autoconf libtool

!apt-get install autoconf libtool

然后安装pyjq

!pip install pyjq

案例

将测试写入.sh文件,可以看到里面用了各种arguments以及jq工具

#!/bin/bash

OUTPUT=/tmp/hw1p1.log
REPORT=report.json

PYTEST_OPTS=""
PYTEST_OPTS="$PYTEST_OPTS --cache-clear -rA --capture=no --show-capture=no --tb=short"
PYTEST_OPTS="$PYTEST_OPTS --json-report --json-report-file=$REPORT --json-report-omit collectors keywords"
PYTEST_OPTS="$PYTEST_OPTS --suppress-tests-failed-exit-code"

start=`date +%s`
pytest $PYTEST_OPTS . > "$OUTPUT" 2>&1
#python3 -u hw1p1-soln.py 
status=$?
end=`date +%s`

echo "Runtime: $((end-start))"
cat $OUTPUT

# checkout output status
if [ ${status} -ne 0 ]; then
    echo "Failure: fails or returns nonzero exit status of ${status}"
    echo '{"scores": {"Success": 0}}'
    exit
fi

# include any post-parsing here

NFAIL=$(jq -r '.summary.failed | select(.!=null)' < report.json)
NPASS=$(jq -r '.summary.passed | select(.!=null)' < report.json)
NTOTAL=$(jq -r '.summary.total | select(.!=null)' < report.json)

echo '{"scores": {"Success":1, "Pass":'${NPASS:=0}', "Fail":'${NFAIL:=0}', "Score":'$((100 * $NPASS/${NTOTAL:=1}))'}}'

exit

辅助文件 test_hw1p1.py

import subprocess, string
import pytest

EXEC='python3'
SCRIPT_TO_TEST='./hw1p1.py'

# PROBLEM_ID: STRING
STRINGS_TO_TEST = [
  'string',
  'space string',
  'stringstring',
  'stringstringstring',
  'aaaaaaaaaaaaaaaaaa',
  'foo1bar',
  '',
  'Loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliqua',
  '.',
  ' ',
  'invalid^'
]

EXPECT_RETURNCODE = 0


# only supports text stdout/stderr
def run_test_script(args):
  # check args list
  if not isinstance(args, list):
    args = [args]
  run = [EXEC, SCRIPT_TO_TEST] + args

  # start script
  ps = subprocess.Popen(run, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  ps.wait()

  [stdout, stderr] = [x.decode('utf-8').strip() for x in ps.communicate()]
  returncode = int(ps.returncode)

  return (returncode, stdout, stderr)


# r = k!
def factorial(k):
  r = 1
  for i in range(1, k+1):
    r *= i
  return r


def count_anagrams(s):
  # normalize
  s = s.lower()

  # "histogram"
  hist = [0] * 26
  for k, letter in enumerate(string.ascii_lowercase):
    hist[k] = s.count(letter)

  # multinomial log-implementation
  perm1 = factorial(sum(hist))
  # note, long strings need arbitrary precision
  # published first without, so return both and count either correct
  perm2 = perm1

  for count in hist:
    perm1 /= factorial(count)
    perm2 //= factorial(count)

  return [int(perm1), int(perm2)]


def is_valid(s):
  return not s or s.isalpha()

# does not check valid, only anagram count => number / empty
def assert_stdout(s, stdout):
  if not is_valid(s):
    assert stdout == '', f'string "{s}" STDOUT: expected=, actual={stdout}'
  elif len(s) == 0:
    assert stdout == 'empty', f'string "{s}" STDOUT: expected=empty, actual={stdout}'
  else:
    # note, see comment above
    count1, count2 = count_anagrams(s)
    assert stdout == f'{count1}' or stdout == f'{count2}'

def assert_stderr(s, stderr):
  if not is_valid(s):
    assert stderr == 'invalid', f'string "{s}" STDERR: expected=invalid, actual={stderr}'
  else:
    assert stderr == '', f'string "{s}" STDERR: expected=, actual={stderr}'


@pytest.mark.parametrize('s', [s for s in STRINGS_TO_TEST])
def test_string(s):
  returncode, stdout, stderr = run_test_script(s)

  # if return code does not match AND == 1, assume program crashed, return error
  if returncode != EXPECT_RETURNCODE and returncode == 1:
    raise Exception(stderr)

  assert returncode == EXPECT_RETURNCODE, f'string "{s}" returncode:expected={EXPECT_RETURNCODE}, actual={returncode}'
  assert_stderr(s, stderr)
  assert_stdout(s, stdout)

report.json

{"created": 1662063651.473876, "duration": 0.5840983390808105, "exitcode": 0, "root": "/content", "environment": {"Python": "3.7.13", "Platform": "Linux-5.4.188+-x86_64-with-Ubuntu-18.04-bionic", "Packages": {"pytest": "7.1.2", "py": "1.11.0", "pluggy": "1.0.0"}, "Plugins": {"json-report": "1.5.0", "custom-exit-code": "0.3.0", "metadata": "2.0.2", "typeguard": "2.7.1"}}, "summary": {"passed": 11, "total": 11, "collected": 11}, "tests": [{"nodeid": "test_hw1p1.py::test_string[string]", "lineno": 92, "outcome": "passed", "setup": {"duration": 0.0003476919999911843, "outcome": "passed"}, "call": {"duration": 0.04421803299999283, "outcome": "passed"}, "teardown": {"duration": 0.0002795460000015737, "outcome": "passed"}}, {"nodeid": "test_hw1p1.py::test_string[space string]", "lineno": 92, "outcome": "passed", "setup": {"duration": 0.00032319900003585644, "outcome": "passed"}, "call": {"duration": 0.041243673000053604, "outcome": "passed"}, "teardown": {"duration": 0.00024495399998158973, "outcome": "passed"}}, {"nodeid": "test_hw1p1.py::test_string[stringstring]", "lineno": 92, "outcome": "passed", "setup": {"duration": 0.00033566100000825827, "outcome": "passed"}, "call": {"duration": 0.04028028899995206, "outcome": "passed"}, "teardown": {"duration": 0.00024724699994749244, "outcome": "passed"}}, {"nodeid": "test_hw1p1.py::test_string[stringstringstring]", "lineno": 92, "outcome": "passed", "setup": {"duration": 0.000330002000055174, "outcome": "passed"}, "call": {"duration": 0.0426553290000129, "outcome": "passed"}, "teardown": {"duration": 0.00026272699994933646, "outcome": "passed"}}, {"nodeid": "test_hw1p1.py::test_string[aaaaaaaaaaaaaaaaaa]", "lineno": 92, "outcome": "passed", "setup": {"duration": 0.0003462529999751496, "outcome": "passed"}, "call": {"duration": 0.04282293200003551, "outcome": "passed"}, "teardown": {"duration": 0.0002480279999872437, "outcome": "passed"}}, {"nodeid": "test_hw1p1.py::test_string[foo1bar]", "lineno": 92, "outcome": "passed", "setup": {"duration": 0.00032949400008419616, "outcome": "passed"}, "call": {"duration": 0.04206459699992138, "outcome": "passed"}, "teardown": {"duration": 0.00026792599999225786, "outcome": "passed"}}, {"nodeid": "test_hw1p1.py::test_string[]", "lineno": 92, "outcome": "passed", "setup": {"duration": 0.0003373019999344251, "outcome": "passed"}, "call": {"duration": 0.0413927589999048, "outcome": "passed"}, "teardown": {"duration": 0.00025381800003287935, "outcome": "passed"}}, {"nodeid": "test_hw1p1.py::test_string[Loremipsumdolorsitametconsecteturadipiscingelitseddoeiusmodtemporincididuntutlaboreetdoloremagnaaliqua]", "lineno": 92, "outcome": "passed", "setup": {"duration": 0.000335223999968548, "outcome": "passed"}, "call": {"duration": 0.04252076099999158, "outcome": "passed"}, "teardown": {"duration": 0.00024685599998974794, "outcome": "passed"}}, {"nodeid": "test_hw1p1.py::test_string[.]", "lineno": 92, "outcome": "passed", "setup": {"duration": 0.0003379639999820938, "outcome": "passed"}, "call": {"duration": 0.04881728899999871, "outcome": "passed"}, "teardown": {"duration": 0.00025473300001976895, "outcome": "passed"}}, {"nodeid": "test_hw1p1.py::test_string[ ]", "lineno": 92, "outcome": "passed", "setup": {"duration": 0.00035387100001571525, "outcome": "passed"}, "call": {"duration": 0.043154117000085535, "outcome": "passed"}, "teardown": {"duration": 0.0002428340000051321, "outcome": "passed"}}, {"nodeid": "test_hw1p1.py::test_string[invalid^]", "lineno": 92, "outcome": "passed", "setup": {"duration": 0.0003739240000868449, "outcome": "passed"}, "call": {"duration": 0.04142785100009405, "outcome": "passed"}, "teardown": {"duration": 0.00030338299995946727, "outcome": "passed"}}]}

被测试代码 hw1p1.py

import sys

def isalpha(s):
    len_ = len(s)
    isletter = lambda ch: True if 97<=ord(ch)<=122 else False
    if sum(list(map(isletter, s))) == len_:
        return True
    return False

def validate():
    # check if empty
    try:
        # check input string is there
        assert len(sys.argv) > 1
        s = sys.argv[1].lower()
        assert len(s)
    except:
        sys.stdout.write("empty")
        sys.exit()
    # check if valid format
    try:
        s = sys.argv[1].lower()
        assert isalpha(s)
    except:
        sys.stderr.write("invalid")
        sys.exit()

def factorial(n):
    return 1 if n == 0 or n == 1 else n*factorial(n-1)
    
def combination(n, r):
    return factorial(n)//(factorial(r)*factorial(n-r))

def permutation(n, r):
    return factorial(n)//factorial(n-r)
        
def count_unique_anagram(s: str) -> int:
    # start from the smallest anagram
    s = sorted(s)

    # accumulate count, and initialize room for rearrangement
    count, room = 1, len(s)
    # count letters
    ch2count = [0]*26 # 0: a, 1: b, ..., 25: z
    for ch in s:
        ch2count[ord(ch)-97] += 1
    # sort for combination first
    ch2count.sort(reverse=True)
    
    num_of_1 = 0
    for i, c in enumerate(ch2count):
        if c > 1: # combination first for letters that appears multiple
            count *= combination(room, c)
            room -= c
        elif c > 0: # count number of letter that only appears once
            num_of_1 += 1
    
    # do permuatation
    if num_of_1:
        count *= permutation(room, num_of_1)
    
    return count
    
if __name__ == "__main__":
    validate()
    # case-insensitive
    unique_num = count_unique_anagram(sys.argv[1].lower())
    sys.stdout.write(f"{unique_num}")
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值